diff --git a/app/components/automations/index_component.rb b/app/components/automations/index_component.rb new file mode 100644 index 00000000000..e5494ea9bfa --- /dev/null +++ b/app/components/automations/index_component.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Automations + class IndexComponent < ::TableComponent + columns :name, :triggers, :conditions, :actions, :sort + + def headers + [ + ["name", { caption: Automation.human_attribute_name(:name) }], + ["triggers", { caption: I18n.t("automations.triggers.name") }], + ["conditions", { caption: I18n.t("automations.conditions") }], + ["actions", { caption: I18n.t("automations.actions.name") }], + ["sort", { caption: I18n.t(:label_sort) }] + ] + end + + def sortable? + false + end + + def inline_create_link + link_to new_automation_path, + aria: { label: t("automations.new") }, + class: "wp-inline-create--add-link", + title: t("automations.new") do + helpers.op_icon("icon icon-add") + end + end + end +end diff --git a/app/components/automations/row_component.rb b/app/components/automations/row_component.rb new file mode 100644 index 00000000000..7dc948ac4ce --- /dev/null +++ b/app/components/automations/row_component.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +module Automations + class RowComponent < ::RowComponent + def automation + row + end + + def name + link_to automation.name, edit_automation_path(automation) + end + + def triggers + automation.triggers.map do |trigger| + case trigger + when Automations::Triggers::Manual + I18n.t("automations.triggers.manual.label") + else + trigger.type.demodulize + end + end.join(", ") + end + + def conditions + automation.conditions.map(&:human_name).join(", ") + end + + def actions + automation.actions.map(&:human_name).join(", ") + end + + def sort + helpers.reorder_links("automation", { action: "update", id: automation }, method: :put) + end + + def button_links + [ + edit_link, + delete_link + ] + end + + def edit_link + link_to( + helpers.op_icon("icon icon-edit"), + helpers.edit_automation_path(automation), + title: t(:button_edit) + ) + end + + def delete_link + link_to( + helpers.op_icon("icon icon-delete"), + helpers.automation_path(automation), + data: { + turbo_method: :delete, + turbo_confirm: I18n.t(:text_are_you_sure) + }, + title: t(:button_delete) + ) + end + end +end diff --git a/app/components/custom_actions/row_component.rb b/app/components/custom_actions/row_component.rb deleted file mode 100644 index 2f3f065f605..00000000000 --- a/app/components/custom_actions/row_component.rb +++ /dev/null @@ -1,74 +0,0 @@ -# frozen_string_literal: true - -#-- copyright -# OpenProject is an open source project management software. -# Copyright (C) the OpenProject GmbH -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License version 3. -# -# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -# Copyright (C) 2006-2013 Jean-Philippe Lang -# Copyright (C) 2010-2013 the ChiliProject Team -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# See COPYRIGHT and LICENSE files for more details. -#++ - -module CustomActions - class RowComponent < ::RowComponent - def action - row - end - - def name - link_to action.name, edit_custom_action_path(action) - end - - delegate :description, to: :action - - def sort - helpers.reorder_links("custom_action", { action: "update", id: action }, method: :put) - end - - def button_links - [ - edit_link, - delete_link - ] - end - - def edit_link - link_to( - helpers.op_icon("icon icon-edit"), - helpers.edit_custom_action_path(action), - title: t(:button_edit) - ) - end - - def delete_link - link_to( - helpers.op_icon("icon icon-delete"), - helpers.custom_action_path(action), - data: { - turbo_method: :delete, - turbo_confirm: I18n.t(:text_are_you_sure) - }, - title: t(:button_delete) - ) - end - end -end diff --git a/app/components/custom_actions/table_component.rb b/app/components/custom_actions/table_component.rb deleted file mode 100644 index 324f8f7dc57..00000000000 --- a/app/components/custom_actions/table_component.rb +++ /dev/null @@ -1,56 +0,0 @@ -# frozen_string_literal: true - -#-- copyright -# OpenProject is an open source project management software. -# Copyright (C) the OpenProject GmbH -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License version 3. -# -# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -# Copyright (C) 2006-2013 Jean-Philippe Lang -# Copyright (C) 2010-2013 the ChiliProject Team -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# See COPYRIGHT and LICENSE files for more details. -#++ - -module CustomActions - class TableComponent < ::TableComponent - columns :name, :description, :sort - - def headers - [ - ["name", { caption: CustomAction.human_attribute_name(:name) }], - ["description", { caption: CustomAction.human_attribute_name(:description) }], - ["sort", { caption: I18n.t(:label_sort) }] - ] - end - - def sortable? - false - end - - def inline_create_link - link_to new_custom_action_path, - aria: { label: t("custom_actions.new") }, - class: "wp-inline-create--add-link", - title: t("custom_actions.new") do - helpers.op_icon("icon icon-add") - end - end - end -end diff --git a/app/contracts/automations/cu_contract.rb b/app/contracts/automations/cu_contract.rb new file mode 100644 index 00000000000..258a3f63bd8 --- /dev/null +++ b/app/contracts/automations/cu_contract.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require "model_contract" + +module Automations + class CuContract < ::ModelContract + def self.model + Automation + end + + attribute :name + attribute :description + + attribute :actions do + errors.add(:actions, :empty) if model.actions.empty? + model.actions.each { |action| action.validate(errors) } + end + + attribute :conditions do + model.conditions.each { |condition| condition.validate(errors) } + end + end +end diff --git a/app/contracts/automations/execute_contract.rb b/app/contracts/automations/execute_contract.rb new file mode 100644 index 00000000000..5e3bdadd7c5 --- /dev/null +++ b/app/contracts/automations/execute_contract.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Automations + class ExecuteContract < BaseContract + property :lock_version + property :work_package_id + + validates :work_package_id, presence: true + validate :work_package_visible + validate :automation_conditions_fulfilled + + private + + def work_package_visible + return unless model.work_package_id + + errors.add(:work_package_id, :does_not_exist) unless WorkPackage.visible(user).where(id: model.work_package_id).exists? + end + + def automation_conditions_fulfilled + return unless model.work_package_id + return unless options[:automation] + + work_package = WorkPackage.visible(user).find_by(id: model.work_package_id) + return unless work_package + + errors.add(:base, :error_unauthorized) unless options[:automation].conditions_fulfilled?(work_package, user) + end + end +end diff --git a/app/contracts/custom_actions/cu_contract.rb b/app/contracts/custom_actions/cu_contract.rb deleted file mode 100644 index d9a479bbf9f..00000000000 --- a/app/contracts/custom_actions/cu_contract.rb +++ /dev/null @@ -1,62 +0,0 @@ -# frozen_string_literal: true - -#-- copyright -# OpenProject is an open source project management software. -# Copyright (C) the OpenProject GmbH -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License version 3. -# -# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -# Copyright (C) 2006-2013 Jean-Philippe Lang -# Copyright (C) 2010-2013 the ChiliProject Team -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# See COPYRIGHT and LICENSE files for more details. -#++ - -require "model_contract" - -# Contract for create (c) and update (u) -module CustomActions - class CuContract < ::ModelContract - def self.model - CustomAction - end - - def initialize(model, user = nil) - super - end - - attribute :name - attribute :description - - attribute :actions do - if model.actions.empty? - errors.add :actions, :empty - end - model.actions.each do |action| - action.validate(errors) - end - end - - attribute :conditions do - model.conditions.each do |condition| - condition.validate(errors) - end - end - end -end diff --git a/app/contracts/custom_actions/execute_contract.rb b/app/contracts/custom_actions/execute_contract.rb deleted file mode 100644 index 8f327dd7806..00000000000 --- a/app/contracts/custom_actions/execute_contract.rb +++ /dev/null @@ -1,62 +0,0 @@ -# frozen_string_literal: true - -#-- copyright -# OpenProject is an open source project management software. -# Copyright (C) the OpenProject GmbH -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License version 3. -# -# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -# Copyright (C) 2006-2013 Jean-Philippe Lang -# Copyright (C) 2010-2013 the ChiliProject Team -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# See COPYRIGHT and LICENSE files for more details. -#++ - -module CustomActions - class ExecuteContract < BaseContract - property :lock_version - property :work_package_id - - validates :work_package_id, presence: true - validate :work_package_visible - validate :custom_action_conditions_fulfilled - - private - - def work_package_visible - return unless model.work_package_id - - unless WorkPackage.visible(user).where(id: model.work_package_id).exists? - errors.add(:work_package_id, :does_not_exist) - end - end - - def custom_action_conditions_fulfilled - return unless model.work_package_id - return unless options[:custom_action] - - work_package = WorkPackage.visible(user).find_by(id: model.work_package_id) - return unless work_package - - unless options[:custom_action].conditions_fulfilled?(work_package, user) - errors.add(:base, :error_unauthorized) - end - end - end -end diff --git a/app/controllers/automations_controller.rb b/app/controllers/automations_controller.rb new file mode 100644 index 00000000000..0b0666171a5 --- /dev/null +++ b/app/controllers/automations_controller.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +class AutomationsController < ApplicationController + before_action :require_admin + + guard_enterprise_feature(:custom_actions, only: %i[new create edit update]) do + redirect_to action: :index + end + + before_action :find_automation, only: %i[edit update destroy] + before_action :pad_params, only: %i[create update] + + layout "admin" + + def index + @automations = Automation.order_by_position.includes(:triggers) + end + + def new + @automation = Automation.new + @automation.triggers.build(type: "Automations::Triggers::Manual") + end + + def edit; end + + def create + Automations::CreateService + .new(user: current_user) + .call(attributes: permitted_params.automation.to_h, + &index_or_render(:new)) + end + + def update + Automations::UpdateService + .new(action: @automation, user: current_user) + .call(attributes: permitted_params.automation.to_h, + &index_or_render(:edit)) + end + + def destroy + @automation.destroy + + redirect_to automations_path, status: :see_other + end + + private + + def find_automation + @automation = Automation.find(params[:id]) + end + + def index_or_render(render_action) + ->(call) { + call.on_success do + redirect_to automations_path, status: :see_other + end + + call.on_failure do + @automation = call.result + render action: render_action, status: :unprocessable_entity + end + } + end + + def pad_params + return if !params[:automation] || params[:automation][:move_to] + + params[:automation][:conditions] ||= {} + params[:automation][:actions] ||= {} + params[:automation][:triggers_attributes] ||= [{ type: "Automations::Triggers::Manual", options: { button_label: params[:automation][:name] } }] + end +end diff --git a/app/controllers/custom_actions_controller.rb b/app/controllers/custom_actions_controller.rb deleted file mode 100644 index 62c766a5896..00000000000 --- a/app/controllers/custom_actions_controller.rb +++ /dev/null @@ -1,102 +0,0 @@ -# frozen_string_literal: true - -#-- copyright -# OpenProject is an open source project management software. -# Copyright (C) the OpenProject GmbH -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License version 3. -# -# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -# Copyright (C) 2006-2013 Jean-Philippe Lang -# Copyright (C) 2010-2013 the ChiliProject Team -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# See COPYRIGHT and LICENSE files for more details. -#++ - -class CustomActionsController < ApplicationController - before_action :require_admin - - guard_enterprise_feature(:custom_actions, only: %i[new create edit update]) do - redirect_to action: :index - end - - before_action :find_custom_action, only: %i(edit update destroy) - before_action :pad_params, only: %i(create update) - - layout "admin" - - def index - @custom_actions = CustomAction.order_by_position - end - - def new - @custom_action = CustomAction.new - end - - def edit; end - - def create - CustomActions::CreateService - .new(user: current_user) - .call(attributes: permitted_params.custom_action.to_h, - &index_or_render(:new)) - end - - def update - CustomActions::UpdateService - .new(action: @custom_action, user: current_user) - .call(attributes: permitted_params.custom_action.to_h, - &index_or_render(:edit)) - end - - def destroy - @custom_action.destroy - - redirect_to custom_actions_path, status: :see_other - end - - private - - def find_custom_action - @custom_action = CustomAction.find(params[:id]) - end - - def index_or_render(render_action) - ->(call) { - call.on_success do - redirect_to custom_actions_path, status: :see_other - end - - call.on_failure do - @custom_action = call.result - render action: render_action, status: :unprocessable_entity - end - } - end - - # If no action/condition is set in the view, the - # actions/conditions already existing on a custom action should be removed. - # But because it is not feasible to have an empty and hidden hash object in a form - # we have to pad the params here. - def pad_params - return if !params[:custom_action] || params[:custom_action][:move_to] - - params[:custom_action][:conditions] ||= {} - params[:custom_action][:actions] ||= {} - end -end diff --git a/app/models/custom_action.rb b/app/models/automation.rb similarity index 51% rename from app/models/custom_action.rb rename to app/models/automation.rb index c37d6ddb831..4458809a59b 100644 --- a/app/models/custom_action.rb +++ b/app/models/automation.rb @@ -1,40 +1,24 @@ # 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 CustomAction < ApplicationRecord +class Automation < ApplicationRecord validates :name, length: { maximum: 255, minimum: 1 } - serialize :actions, coder: CustomActions::Actions::Serializer - has_and_belongs_to_many :status_conditions, class_name: "Status" - has_and_belongs_to_many :role_conditions, class_name: "Role" - has_and_belongs_to_many :type_conditions, class_name: "Type" - has_and_belongs_to_many :project_conditions, class_name: "Project" + validate :must_have_at_least_one_trigger + validate :must_not_have_more_than_one_manual_trigger + + serialize :actions, coder: Automations::Actions::Serializer + before_validation :ensure_manual_trigger + + has_and_belongs_to_many :status_conditions, class_name: "Status", join_table: :automations_statuses + has_and_belongs_to_many :role_conditions, class_name: "Role", join_table: :automations_roles + has_and_belongs_to_many :type_conditions, class_name: "Type", join_table: :automations_types + has_and_belongs_to_many :project_conditions, class_name: "Project", join_table: :automations_projects + + has_many :triggers, + -> { order(:position, :id) }, + class_name: "Automations::Triggers::Base", + dependent: :destroy, + inverse_of: :automation + accepts_nested_attributes_for :triggers after_save :persist_conditions @@ -43,19 +27,18 @@ class CustomAction < ApplicationRecord acts_as_list + scope :with_manual_trigger, -> { + joins(:triggers).where(automation_triggers: { type: "Automations::Triggers::Manual" }).distinct + } + def initialize(*args) ret = super - - if actions.nil? - self.actions = [] - end - + self.actions ||= [] ret end def reload(*args) @conditions = nil - super end @@ -77,7 +60,7 @@ class CustomAction < ApplicationRecord end def available_actions - ::CustomActions::Register.actions.map(&:all).flatten + ::Automations::Register.actions.map(&:all).flatten end def all_conditions @@ -104,11 +87,17 @@ class CustomAction < ApplicationRecord end def self.available_conditions - ::CustomActions::Register.conditions + ::Automations::Register.conditions end private + def ensure_manual_trigger + return unless triggers.reject(&:marked_for_destruction?).empty? + + triggers.build(type: "Automations::Triggers::Manual", options: { button_label: name }) + end + def all_of(availables, actual) availables.map do |available| existing = actual.detect { |a| a.key == available.key } @@ -124,4 +113,13 @@ class CustomAction < ApplicationRecord condition_class.setter(self, condition) end end + + def must_have_at_least_one_trigger + errors.add(:triggers, :blank) if triggers.reject(&:marked_for_destruction?).empty? + end + + def must_not_have_more_than_one_manual_trigger + manual_triggers = triggers.reject(&:marked_for_destruction?).count { |trigger| trigger.type == "Automations::Triggers::Manual" } + errors.add(:triggers, :invalid) if manual_triggers > 1 + end end diff --git a/app/models/custom_actions/actions/assigned_to.rb b/app/models/automations/actions/assigned_to.rb similarity index 92% rename from app/models/custom_actions/actions/assigned_to.rb rename to app/models/automations/actions/assigned_to.rb index 2d0655e161b..87923628d76 100644 --- a/app/models/custom_actions/actions/assigned_to.rb +++ b/app/models/automations/actions/assigned_to.rb @@ -28,8 +28,8 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class CustomActions::Actions::AssignedTo < CustomActions::Actions::Base - include CustomActions::Actions::Strategies::MeAssociated +class Automations::Actions::AssignedTo < Automations::Actions::Base + include Automations::Actions::Strategies::MeAssociated def self.key :assigned_to diff --git a/app/models/custom_actions/actions/base.rb b/app/models/automations/actions/base.rb similarity index 95% rename from app/models/custom_actions/actions/base.rb rename to app/models/automations/actions/base.rb index 3b131d6f80d..aae8e4bb925 100644 --- a/app/models/custom_actions/actions/base.rb +++ b/app/models/automations/actions/base.rb @@ -28,7 +28,7 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class CustomActions::Actions::Base +class Automations::Actions::Base attr_reader :values DEFAULT_PRIORITY = 100 @@ -98,10 +98,6 @@ class CustomActions::Actions::Base private - def deconstruct_keys(*) - { type:, custom_field_based: respond_to?(:custom_field) } - end - def validate_value_required(errors) if required? && values.empty? errors.add :actions, diff --git a/app/models/custom_actions/actions/custom_field.rb b/app/models/automations/actions/custom_field.rb similarity index 81% rename from app/models/custom_actions/actions/custom_field.rb rename to app/models/automations/actions/custom_field.rb index dd796273c4f..4b5dca1692b 100644 --- a/app/models/custom_actions/actions/custom_field.rb +++ b/app/models/automations/actions/custom_field.rb @@ -28,7 +28,7 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class CustomActions::Actions::CustomField < CustomActions::Actions::Base +class Automations::Actions::CustomField < Automations::Actions::Base class << self def key custom_field.attribute_name.to_sym @@ -40,7 +40,7 @@ class CustomActions::Actions::CustomField < CustomActions::Actions::Base def all WorkPackageCustomField - .usable_as_custom_action + .usable_as_automation .map do |cf| create_subclass(cf) end @@ -57,7 +57,7 @@ class CustomActions::Actions::CustomField < CustomActions::Actions::Base private def create_subclass(custom_field) - klass = Class.new(CustomActions::Actions::CustomField) + klass = Class.new(Automations::Actions::CustomField) klass.define_singleton_method(:custom_field) do custom_field end @@ -69,23 +69,23 @@ class CustomActions::Actions::CustomField < CustomActions::Actions::Base def strategy(custom_field) case custom_field.field_format when "string" - CustomActions::Actions::Strategies::String + Automations::Actions::Strategies::String when "text" - CustomActions::Actions::Strategies::Text + Automations::Actions::Strategies::Text when "link" - CustomActions::Actions::Strategies::Link + Automations::Actions::Strategies::Link when "int" - CustomActions::Actions::Strategies::Integer + Automations::Actions::Strategies::Integer when "float" - CustomActions::Actions::Strategies::Float + Automations::Actions::Strategies::Float when "date" - CustomActions::Actions::Strategies::Date + Automations::Actions::Strategies::Date when "bool" - CustomActions::Actions::Strategies::Boolean + Automations::Actions::Strategies::Boolean when "user" - CustomActions::Actions::Strategies::UserCustomField + Automations::Actions::Strategies::UserCustomField when "list", "version" - CustomActions::Actions::Strategies::AssociatedCustomField + Automations::Actions::Strategies::AssociatedCustomField end end end diff --git a/app/models/custom_actions/actions/date.rb b/app/models/automations/actions/date.rb similarity index 92% rename from app/models/custom_actions/actions/date.rb rename to app/models/automations/actions/date.rb index 7e4f3bee380..571788b3313 100644 --- a/app/models/custom_actions/actions/date.rb +++ b/app/models/automations/actions/date.rb @@ -28,8 +28,8 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class CustomActions::Actions::Date < CustomActions::Actions::Base - include CustomActions::Actions::Strategies::Date +class Automations::Actions::Date < Automations::Actions::Base + include Automations::Actions::Strategies::Date def self.key :date diff --git a/app/models/custom_actions/actions/done_ratio.rb b/app/models/automations/actions/done_ratio.rb similarity index 92% rename from app/models/custom_actions/actions/done_ratio.rb rename to app/models/automations/actions/done_ratio.rb index c885bb79e4b..d51ecec36b9 100644 --- a/app/models/custom_actions/actions/done_ratio.rb +++ b/app/models/automations/actions/done_ratio.rb @@ -28,8 +28,8 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class CustomActions::Actions::DoneRatio < CustomActions::Actions::Base - include CustomActions::Actions::Strategies::Integer +class Automations::Actions::DoneRatio < Automations::Actions::Base + include Automations::Actions::Strategies::Integer def self.key :done_ratio diff --git a/app/models/custom_actions/actions/due_date.rb b/app/models/automations/actions/due_date.rb similarity index 90% rename from app/models/custom_actions/actions/due_date.rb rename to app/models/automations/actions/due_date.rb index e0836b1f032..658e346291d 100644 --- a/app/models/custom_actions/actions/due_date.rb +++ b/app/models/automations/actions/due_date.rb @@ -28,8 +28,8 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class CustomActions::Actions::DueDate < CustomActions::Actions::Base - include CustomActions::Actions::Strategies::DateProperty +class Automations::Actions::DueDate < Automations::Actions::Base + include Automations::Actions::Strategies::DateProperty def self.key :due_date diff --git a/app/models/custom_actions/actions/estimated_hours.rb b/app/models/automations/actions/estimated_hours.rb similarity index 91% rename from app/models/custom_actions/actions/estimated_hours.rb rename to app/models/automations/actions/estimated_hours.rb index dcec6856db9..1c2b18e79b3 100644 --- a/app/models/custom_actions/actions/estimated_hours.rb +++ b/app/models/automations/actions/estimated_hours.rb @@ -28,8 +28,8 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class CustomActions::Actions::EstimatedHours < CustomActions::Actions::Base - include CustomActions::Actions::Strategies::Float +class Automations::Actions::EstimatedHours < Automations::Actions::Base + include Automations::Actions::Strategies::Float def self.key :estimated_hours diff --git a/app/models/custom_actions/actions/inexistent.rb b/app/models/automations/actions/inexistent.rb similarity index 94% rename from app/models/custom_actions/actions/inexistent.rb rename to app/models/automations/actions/inexistent.rb index 215f95a1cae..f73665e3456 100644 --- a/app/models/custom_actions/actions/inexistent.rb +++ b/app/models/automations/actions/inexistent.rb @@ -28,7 +28,7 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class CustomActions::Actions::Inexistent < CustomActions::Actions::Base +class Automations::Actions::Inexistent < Automations::Actions::Base def self.key :inexistent end diff --git a/app/models/custom_actions/actions/notify.rb b/app/models/automations/actions/notify.rb similarity index 93% rename from app/models/custom_actions/actions/notify.rb rename to app/models/automations/actions/notify.rb index e88a50edf5d..dda26006920 100644 --- a/app/models/custom_actions/actions/notify.rb +++ b/app/models/automations/actions/notify.rb @@ -28,8 +28,8 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class CustomActions::Actions::Notify < CustomActions::Actions::Base - include CustomActions::Actions::Strategies::Associated +class Automations::Actions::Notify < Automations::Actions::Base + include Automations::Actions::Strategies::Associated def apply(work_package) comment = principals.where(id: values).map do |p| diff --git a/app/models/custom_actions/actions/priority.rb b/app/models/automations/actions/priority.rb similarity index 91% rename from app/models/custom_actions/actions/priority.rb rename to app/models/automations/actions/priority.rb index 7826a99c87e..97011b57fc2 100644 --- a/app/models/custom_actions/actions/priority.rb +++ b/app/models/automations/actions/priority.rb @@ -28,8 +28,8 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class CustomActions::Actions::Priority < CustomActions::Actions::Base - include CustomActions::Actions::Strategies::Associated +class Automations::Actions::Priority < Automations::Actions::Base + include Automations::Actions::Strategies::Associated def associated IssuePriority diff --git a/app/models/custom_actions/actions/project.rb b/app/models/automations/actions/project.rb similarity index 92% rename from app/models/custom_actions/actions/project.rb rename to app/models/automations/actions/project.rb index 5c893303fd0..c1d6f78ba9c 100644 --- a/app/models/custom_actions/actions/project.rb +++ b/app/models/automations/actions/project.rb @@ -28,8 +28,8 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class CustomActions::Actions::Project < CustomActions::Actions::Base - include CustomActions::Actions::Strategies::Associated +class Automations::Actions::Project < Automations::Actions::Base + include Automations::Actions::Strategies::Associated PRIORITY = 10 diff --git a/app/models/custom_actions/actions/responsible.rb b/app/models/automations/actions/responsible.rb similarity index 92% rename from app/models/custom_actions/actions/responsible.rb rename to app/models/automations/actions/responsible.rb index c0b8e59f09f..c83d1531620 100644 --- a/app/models/custom_actions/actions/responsible.rb +++ b/app/models/automations/actions/responsible.rb @@ -28,8 +28,8 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class CustomActions::Actions::Responsible < CustomActions::Actions::Base - include CustomActions::Actions::Strategies::MeAssociated +class Automations::Actions::Responsible < Automations::Actions::Base + include Automations::Actions::Strategies::MeAssociated def type :user diff --git a/app/models/custom_actions/actions/serializer.rb b/app/models/automations/actions/serializer.rb similarity index 93% rename from app/models/custom_actions/actions/serializer.rb rename to app/models/automations/actions/serializer.rb index 81ca8efab3e..1905419e896 100644 --- a/app/models/custom_actions/actions/serializer.rb +++ b/app/models/automations/actions/serializer.rb @@ -28,7 +28,7 @@ # See COPYRIGHT and LICENSE files for more details. #++ -module CustomActions::Actions::Serializer +module Automations::Actions::Serializer module_function def load(value) @@ -39,13 +39,13 @@ module CustomActions::Actions::Serializer .filter_map do |key, values| klass = nil - CustomActions::Register + Automations::Register .actions .detect do |a| klass = a.for(key) end - klass ||= CustomActions::Actions::Inexistent + klass ||= Automations::Actions::Inexistent klass.new(values) end diff --git a/app/models/custom_actions/actions/start_date.rb b/app/models/automations/actions/start_date.rb similarity index 90% rename from app/models/custom_actions/actions/start_date.rb rename to app/models/automations/actions/start_date.rb index 9d5fc185d48..5fd880de18b 100644 --- a/app/models/custom_actions/actions/start_date.rb +++ b/app/models/automations/actions/start_date.rb @@ -28,8 +28,8 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class CustomActions::Actions::StartDate < CustomActions::Actions::Base - include CustomActions::Actions::Strategies::DateProperty +class Automations::Actions::StartDate < Automations::Actions::Base + include Automations::Actions::Strategies::DateProperty def self.key :start_date diff --git a/app/models/custom_actions/actions/status.rb b/app/models/automations/actions/status.rb similarity index 91% rename from app/models/custom_actions/actions/status.rb rename to app/models/automations/actions/status.rb index 30a939d3813..e1942004e50 100644 --- a/app/models/custom_actions/actions/status.rb +++ b/app/models/automations/actions/status.rb @@ -28,8 +28,8 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class CustomActions::Actions::Status < CustomActions::Actions::Base - include CustomActions::Actions::Strategies::Associated +class Automations::Actions::Status < Automations::Actions::Base + include Automations::Actions::Strategies::Associated def self.key :status diff --git a/app/models/custom_actions/actions/strategies/associated.rb b/app/models/automations/actions/strategies/associated.rb similarity index 92% rename from app/models/custom_actions/actions/strategies/associated.rb rename to app/models/automations/actions/strategies/associated.rb index e608b67b638..ffbeb2615de 100644 --- a/app/models/custom_actions/actions/strategies/associated.rb +++ b/app/models/automations/actions/strategies/associated.rb @@ -28,9 +28,9 @@ # See COPYRIGHT and LICENSE files for more details. #++ -module CustomActions::Actions::Strategies::Associated - include CustomActions::ValidateAllowedValue - include CustomActions::ValuesToInteger +module Automations::Actions::Strategies::Associated + include Automations::ValidateAllowedValue + include Automations::ValuesToInteger def allowed_values @allowed_values ||= begin diff --git a/app/models/custom_actions/actions/strategies/associated_custom_field.rb b/app/models/automations/actions/strategies/associated_custom_field.rb similarity index 88% rename from app/models/custom_actions/actions/strategies/associated_custom_field.rb rename to app/models/automations/actions/strategies/associated_custom_field.rb index 97ccda753dc..ee731e0be06 100644 --- a/app/models/custom_actions/actions/strategies/associated_custom_field.rb +++ b/app/models/automations/actions/strategies/associated_custom_field.rb @@ -28,9 +28,9 @@ # See COPYRIGHT and LICENSE files for more details. #++ -module CustomActions::Actions::Strategies::AssociatedCustomField - include CustomActions::Actions::Strategies::Associated - include CustomActions::Actions::Strategies::CustomField +module Automations::Actions::Strategies::AssociatedCustomField + include Automations::Actions::Strategies::Associated + include Automations::Actions::Strategies::CustomField def associated custom_field diff --git a/app/models/custom_actions/actions/strategies/boolean.rb b/app/models/automations/actions/strategies/boolean.rb similarity index 93% rename from app/models/custom_actions/actions/strategies/boolean.rb rename to app/models/automations/actions/strategies/boolean.rb index 96ed10bbbbd..80d559d3fff 100644 --- a/app/models/custom_actions/actions/strategies/boolean.rb +++ b/app/models/automations/actions/strategies/boolean.rb @@ -21,8 +21,8 @@ # See COPYRIGHT and LICENSE files for more details. #++ -module CustomActions::Actions::Strategies::Boolean - include CustomActions::ValidateAllowedValue +module Automations::Actions::Strategies::Boolean + include Automations::ValidateAllowedValue def allowed_values [ diff --git a/app/models/custom_actions/actions/strategies/custom_field.rb b/app/models/automations/actions/strategies/custom_field.rb similarity index 96% rename from app/models/custom_actions/actions/strategies/custom_field.rb rename to app/models/automations/actions/strategies/custom_field.rb index 3d9c2071f9d..f9fbe832e28 100644 --- a/app/models/custom_actions/actions/strategies/custom_field.rb +++ b/app/models/automations/actions/strategies/custom_field.rb @@ -28,7 +28,7 @@ # See COPYRIGHT and LICENSE files for more details. #++ -module CustomActions::Actions::Strategies::CustomField +module Automations::Actions::Strategies::CustomField def apply(work_package) if work_package.respond_to?(custom_field.attribute_setter) set_custom_field_value(work_package) diff --git a/app/models/custom_actions/actions/strategies/date.rb b/app/models/automations/actions/strategies/date.rb similarity index 97% rename from app/models/custom_actions/actions/strategies/date.rb rename to app/models/automations/actions/strategies/date.rb index 2ad203fafa8..2b80e9fe16e 100644 --- a/app/models/custom_actions/actions/strategies/date.rb +++ b/app/models/automations/actions/strategies/date.rb @@ -28,7 +28,7 @@ # See COPYRIGHT and LICENSE files for more details. #++ -module CustomActions::Actions::Strategies::Date +module Automations::Actions::Strategies::Date def values=(values) super(Array(values).map { |v| to_date_or_nil(v) }.uniq) end diff --git a/app/models/custom_actions/actions/strategies/date_property.rb b/app/models/automations/actions/strategies/date_property.rb similarity index 92% rename from app/models/custom_actions/actions/strategies/date_property.rb rename to app/models/automations/actions/strategies/date_property.rb index e599ca8263d..dfe6c63f652 100644 --- a/app/models/custom_actions/actions/strategies/date_property.rb +++ b/app/models/automations/actions/strategies/date_property.rb @@ -28,6 +28,6 @@ # See COPYRIGHT and LICENSE files for more details. #++ -module CustomActions::Actions::Strategies::DateProperty - include CustomActions::Actions::Strategies::Date +module Automations::Actions::Strategies::DateProperty + include Automations::Actions::Strategies::Date end diff --git a/app/models/custom_actions/actions/strategies/float.rb b/app/models/automations/actions/strategies/float.rb similarity index 93% rename from app/models/custom_actions/actions/strategies/float.rb rename to app/models/automations/actions/strategies/float.rb index 6059086a3fe..be893101601 100644 --- a/app/models/custom_actions/actions/strategies/float.rb +++ b/app/models/automations/actions/strategies/float.rb @@ -28,8 +28,8 @@ # See COPYRIGHT and LICENSE files for more details. #++ -module CustomActions::Actions::Strategies::Float - include CustomActions::Actions::Strategies::ValidateInRange +module Automations::Actions::Strategies::Float + include Automations::Actions::Strategies::ValidateInRange def values=(values) super(Array(values).map { |v| to_float_or_nil(v) }.uniq) diff --git a/app/models/custom_actions/actions/strategies/integer.rb b/app/models/automations/actions/strategies/integer.rb similarity index 89% rename from app/models/custom_actions/actions/strategies/integer.rb rename to app/models/automations/actions/strategies/integer.rb index d69938c4f85..58942e37a38 100644 --- a/app/models/custom_actions/actions/strategies/integer.rb +++ b/app/models/automations/actions/strategies/integer.rb @@ -28,9 +28,9 @@ # See COPYRIGHT and LICENSE files for more details. #++ -module CustomActions::Actions::Strategies::Integer - include CustomActions::ValuesToInteger - include CustomActions::Actions::Strategies::ValidateInRange +module Automations::Actions::Strategies::Integer + include Automations::ValuesToInteger + include Automations::Actions::Strategies::ValidateInRange def type :integer_property diff --git a/app/models/custom_actions/actions/strategies/link.rb b/app/models/automations/actions/strategies/link.rb similarity index 93% rename from app/models/custom_actions/actions/strategies/link.rb rename to app/models/automations/actions/strategies/link.rb index 94fc5e11523..5c822ead478 100644 --- a/app/models/custom_actions/actions/strategies/link.rb +++ b/app/models/automations/actions/strategies/link.rb @@ -28,8 +28,8 @@ # See COPYRIGHT and LICENSE files for more details. #++ -module CustomActions::Actions::Strategies::Link - include CustomActions::Actions::Strategies::ValuesToString +module Automations::Actions::Strategies::Link + include Automations::Actions::Strategies::ValuesToString def type :link_property diff --git a/app/models/custom_actions/actions/strategies/me_associated.rb b/app/models/automations/actions/strategies/me_associated.rb similarity index 92% rename from app/models/custom_actions/actions/strategies/me_associated.rb rename to app/models/automations/actions/strategies/me_associated.rb index 241df7b15be..9d36a8fb6d5 100644 --- a/app/models/custom_actions/actions/strategies/me_associated.rb +++ b/app/models/automations/actions/strategies/me_associated.rb @@ -28,8 +28,8 @@ # See COPYRIGHT and LICENSE files for more details. #++ -module CustomActions::Actions::Strategies::MeAssociated - include ::CustomActions::Actions::Strategies::Associated +module Automations::Actions::Strategies::MeAssociated + include ::Automations::Actions::Strategies::Associated def me_value [current_user_value_key, current_user_name] @@ -66,7 +66,7 @@ module CustomActions::Actions::Strategies::MeAssociated end def current_user_name - I18n.t("custom_actions.actions.assigned_to.executing_user_value") + I18n.t("automations.actions.assigned_to.executing_user_value") end def has_me_value? diff --git a/app/models/custom_actions/actions/strategies/string.rb b/app/models/automations/actions/strategies/string.rb similarity index 92% rename from app/models/custom_actions/actions/strategies/string.rb rename to app/models/automations/actions/strategies/string.rb index ca8707e5c29..1bf1ff84f92 100644 --- a/app/models/custom_actions/actions/strategies/string.rb +++ b/app/models/automations/actions/strategies/string.rb @@ -28,8 +28,8 @@ # See COPYRIGHT and LICENSE files for more details. #++ -module CustomActions::Actions::Strategies::String - include CustomActions::Actions::Strategies::ValuesToString +module Automations::Actions::Strategies::String + include Automations::Actions::Strategies::ValuesToString def type :string_property diff --git a/app/models/custom_actions/actions/strategies/text.rb b/app/models/automations/actions/strategies/text.rb similarity index 92% rename from app/models/custom_actions/actions/strategies/text.rb rename to app/models/automations/actions/strategies/text.rb index 1ceea830858..d733868aad1 100644 --- a/app/models/custom_actions/actions/strategies/text.rb +++ b/app/models/automations/actions/strategies/text.rb @@ -28,8 +28,8 @@ # See COPYRIGHT and LICENSE files for more details. #++ -module CustomActions::Actions::Strategies::Text - include CustomActions::Actions::Strategies::ValuesToString +module Automations::Actions::Strategies::Text + include Automations::Actions::Strategies::ValuesToString def type :text_property diff --git a/app/models/custom_actions/actions/strategies/user_custom_field.rb b/app/models/automations/actions/strategies/user_custom_field.rb similarity index 93% rename from app/models/custom_actions/actions/strategies/user_custom_field.rb rename to app/models/automations/actions/strategies/user_custom_field.rb index 022c723cf85..8609e9e117e 100644 --- a/app/models/custom_actions/actions/strategies/user_custom_field.rb +++ b/app/models/automations/actions/strategies/user_custom_field.rb @@ -28,9 +28,9 @@ # See COPYRIGHT and LICENSE files for more details. #++ -module CustomActions::Actions::Strategies::UserCustomField - include ::CustomActions::Actions::Strategies::CustomField - include ::CustomActions::Actions::Strategies::MeAssociated +module Automations::Actions::Strategies::UserCustomField + include ::Automations::Actions::Strategies::CustomField + include ::Automations::Actions::Strategies::MeAssociated def type :user diff --git a/app/models/custom_actions/actions/strategies/validate_in_range.rb b/app/models/automations/actions/strategies/validate_in_range.rb similarity index 97% rename from app/models/custom_actions/actions/strategies/validate_in_range.rb rename to app/models/automations/actions/strategies/validate_in_range.rb index 51b83d11120..b90e13800b6 100644 --- a/app/models/custom_actions/actions/strategies/validate_in_range.rb +++ b/app/models/automations/actions/strategies/validate_in_range.rb @@ -28,7 +28,7 @@ # See COPYRIGHT and LICENSE files for more details. #++ -module CustomActions::Actions::Strategies::ValidateInRange +module Automations::Actions::Strategies::ValidateInRange def minimum nil end diff --git a/app/models/custom_actions/actions/strategies/values_to_string.rb b/app/models/automations/actions/strategies/values_to_string.rb similarity index 96% rename from app/models/custom_actions/actions/strategies/values_to_string.rb rename to app/models/automations/actions/strategies/values_to_string.rb index 94e26c46905..d2e90002ad8 100644 --- a/app/models/custom_actions/actions/strategies/values_to_string.rb +++ b/app/models/automations/actions/strategies/values_to_string.rb @@ -28,7 +28,7 @@ # See COPYRIGHT and LICENSE files for more details. #++ -module CustomActions::Actions::Strategies::ValuesToString +module Automations::Actions::Strategies::ValuesToString def values=(values) super(Array(values).map { |v| to_string_or_nil(v) }.uniq) end diff --git a/app/models/custom_actions/actions/type.rb b/app/models/automations/actions/type.rb similarity index 92% rename from app/models/custom_actions/actions/type.rb rename to app/models/automations/actions/type.rb index 530dd86d854..412f7047dbd 100644 --- a/app/models/custom_actions/actions/type.rb +++ b/app/models/automations/actions/type.rb @@ -28,8 +28,8 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class CustomActions::Actions::Type < CustomActions::Actions::Base - include CustomActions::Actions::Strategies::Associated +class Automations::Actions::Type < Automations::Actions::Base + include Automations::Actions::Strategies::Associated PRIORITY = 20 diff --git a/app/models/custom_actions/conditions/base.rb b/app/models/automations/conditions/base.rb similarity index 77% rename from app/models/custom_actions/conditions/base.rb rename to app/models/automations/conditions/base.rb index 9852d382fbc..594094626fd 100644 --- a/app/models/custom_actions/conditions/base.rb +++ b/app/models/automations/conditions/base.rb @@ -28,11 +28,11 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class CustomActions::Conditions::Base +class Automations::Conditions::Base attr_reader :values - prepend CustomActions::ValuesToInteger - include CustomActions::ValidateAllowedValue + prepend Automations::ValuesToInteger + include Automations::ValidateAllowedValue def initialize(values = nil) self.values = values @@ -74,38 +74,38 @@ class CustomActions::Conditions::Base validate_allowed_value(errors, :conditions) end - def self.getter(custom_action) - ids = custom_action.send(association_ids) + def self.getter(automation) + ids = automation.send(association_ids) new(ids) if ids.any? end - def self.setter(custom_action, condition) + def self.setter(automation, condition) if condition - custom_action.send(:"#{association_ids}=", condition.values) + automation.send(:"#{association_ids}=", condition.values) else - custom_action.send(:"#{association_key}").clear + automation.send(:"#{association_key}").clear end end - def self.custom_action_scope(work_packages, user) - custom_action_scope_has_current(work_packages, user) - .or(custom_action_scope_has_no) + def self.automation_scope(work_packages, user) + automation_scope_has_current(work_packages, user) + .or(automation_scope_has_no) end - def self.custom_action_scope_has_current(work_packages, _user) - CustomAction + def self.automation_scope_has_current(work_packages, _user) + Automation .includes(association_key) .where(habtm_table => { key_id => Array(work_packages).map { |w| w.send(key_id) }.uniq }) end - private_class_method :custom_action_scope_has_current + private_class_method :automation_scope_has_current - def self.custom_action_scope_has_no - CustomAction + def self.automation_scope_has_no + Automation .includes(association_key) .where(habtm_table => { key_id => nil }) end - private_class_method :custom_action_scope_has_no + private_class_method :automation_scope_has_no def self.pluralized_key key.to_s.pluralize.to_sym @@ -113,7 +113,7 @@ class CustomActions::Conditions::Base private_class_method :pluralized_key def self.habtm_table - :"custom_actions_#{pluralized_key}" + :"automations_#{pluralized_key}" end private_class_method :habtm_table diff --git a/app/models/custom_actions/conditions/inexistent.rb b/app/models/automations/conditions/inexistent.rb similarity index 94% rename from app/models/custom_actions/conditions/inexistent.rb rename to app/models/automations/conditions/inexistent.rb index 5cbf25aac5f..04702b51e0d 100644 --- a/app/models/custom_actions/conditions/inexistent.rb +++ b/app/models/automations/conditions/inexistent.rb @@ -28,7 +28,7 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class CustomActions::Conditions::Inexistent < CustomActions::Conditions::Base +class Automations::Conditions::Inexistent < Automations::Conditions::Base def self.key :inexistent end diff --git a/app/models/custom_actions/conditions/project.rb b/app/models/automations/conditions/project.rb similarity index 95% rename from app/models/custom_actions/conditions/project.rb rename to app/models/automations/conditions/project.rb index 807a7bdfc79..296532dfc3b 100644 --- a/app/models/custom_actions/conditions/project.rb +++ b/app/models/automations/conditions/project.rb @@ -28,7 +28,7 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class CustomActions::Conditions::Project < CustomActions::Conditions::Base +class Automations::Conditions::Project < Automations::Conditions::Base def self.key :project end diff --git a/app/models/custom_actions/conditions/role.rb b/app/models/automations/conditions/role.rb similarity index 89% rename from app/models/custom_actions/conditions/role.rb rename to app/models/automations/conditions/role.rb index 77af34115ff..49f85b115d7 100644 --- a/app/models/custom_actions/conditions/role.rb +++ b/app/models/automations/conditions/role.rb @@ -28,7 +28,7 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class CustomActions::Conditions::Role < CustomActions::Conditions::Base +class Automations::Conditions::Role < Automations::Conditions::Base def fulfilled_by?(work_package, user) values.empty? || (self.class.roles_in_project(work_package, user).map(&:id) & values).any? @@ -49,8 +49,8 @@ class CustomActions::Conditions::Role < CustomActions::Conditions::Base private - def custom_action_scope_has_current(work_packages, user) - CustomAction + def automation_scope_has_current(work_packages, user) + Automation .includes(association_key) .where(habtm_table => { key_id => roles_in_project(work_packages, user) }) end @@ -67,11 +67,11 @@ class CustomActions::Conditions::Role < CustomActions::Conditions::Base end def with_request_store(projects) - RequestStore.store[:custom_actions_role] ||= Hash.new do |hash, hash_projects| + RequestStore.store[:automations_role] ||= Hash.new do |hash, hash_projects| hash[hash_projects] = yield hash_projects end - RequestStore.store[:custom_actions_role][projects] + RequestStore.store[:automations_role][projects] end end diff --git a/app/models/custom_actions/conditions/status.rb b/app/models/automations/conditions/status.rb similarity index 94% rename from app/models/custom_actions/conditions/status.rb rename to app/models/automations/conditions/status.rb index c6b246e0d65..c5069e596d8 100644 --- a/app/models/custom_actions/conditions/status.rb +++ b/app/models/automations/conditions/status.rb @@ -28,7 +28,7 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class CustomActions::Conditions::Status < CustomActions::Conditions::Base +class Automations::Conditions::Status < Automations::Conditions::Base def self.key :status end diff --git a/app/models/custom_actions/conditions/type.rb b/app/models/automations/conditions/type.rb similarity index 95% rename from app/models/custom_actions/conditions/type.rb rename to app/models/automations/conditions/type.rb index 37c3410516d..3510cd75ba8 100644 --- a/app/models/custom_actions/conditions/type.rb +++ b/app/models/automations/conditions/type.rb @@ -28,7 +28,7 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class CustomActions::Conditions::Type < CustomActions::Conditions::Base +class Automations::Conditions::Type < Automations::Conditions::Base def self.key :type end diff --git a/app/models/custom_actions/register.rb b/app/models/automations/register.rb similarity index 64% rename from app/models/custom_actions/register.rb rename to app/models/automations/register.rb index e49123fd52c..0645fa7dc24 100644 --- a/app/models/custom_actions/register.rb +++ b/app/models/automations/register.rb @@ -28,32 +28,32 @@ # See COPYRIGHT and LICENSE files for more details. #++ -module CustomActions::Register +module Automations::Register class << self def actions [ - CustomActions::Actions::AssignedTo, - CustomActions::Actions::Responsible, - CustomActions::Actions::Status, - CustomActions::Actions::Priority, - CustomActions::Actions::CustomField, - CustomActions::Actions::Type, - CustomActions::Actions::Project, - CustomActions::Actions::Notify, - CustomActions::Actions::DoneRatio, - CustomActions::Actions::EstimatedHours, - CustomActions::Actions::StartDate, - CustomActions::Actions::DueDate, - CustomActions::Actions::Date + Automations::Actions::AssignedTo, + Automations::Actions::Responsible, + Automations::Actions::Status, + Automations::Actions::Priority, + Automations::Actions::CustomField, + Automations::Actions::Type, + Automations::Actions::Project, + Automations::Actions::Notify, + Automations::Actions::DoneRatio, + Automations::Actions::EstimatedHours, + Automations::Actions::StartDate, + Automations::Actions::DueDate, + Automations::Actions::Date ] end def conditions [ - CustomActions::Conditions::Status, - CustomActions::Conditions::Role, - CustomActions::Conditions::Type, - CustomActions::Conditions::Project + Automations::Conditions::Status, + Automations::Conditions::Role, + Automations::Conditions::Type, + Automations::Conditions::Project ] end end diff --git a/app/models/automations/triggers/base.rb b/app/models/automations/triggers/base.rb new file mode 100644 index 00000000000..e441e5734b2 --- /dev/null +++ b/app/models/automations/triggers/base.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Automations + module Triggers + class Base < ApplicationRecord + self.table_name = "automation_triggers" + + belongs_to :automation, inverse_of: :triggers + + acts_as_list scope: :automation + end + end +end diff --git a/app/models/automations/triggers/manual.rb b/app/models/automations/triggers/manual.rb new file mode 100644 index 00000000000..5cbd85f787f --- /dev/null +++ b/app/models/automations/triggers/manual.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Automations + module Triggers + class Manual < Base + store_attribute :options, :button_label, :string + + validates :button_label, presence: true, length: { maximum: 255 } + end + end +end diff --git a/app/models/custom_actions/validate_allowed_value.rb b/app/models/automations/validate_allowed_value.rb similarity index 96% rename from app/models/custom_actions/validate_allowed_value.rb rename to app/models/automations/validate_allowed_value.rb index b85f7678b06..8b85beb754e 100644 --- a/app/models/custom_actions/validate_allowed_value.rb +++ b/app/models/automations/validate_allowed_value.rb @@ -21,7 +21,7 @@ # See COPYRIGHT and LICENSE files for more details. #++ -module CustomActions::ValidateAllowedValue +module Automations::ValidateAllowedValue private def validate_allowed_value(errors, attribute) diff --git a/app/models/custom_actions/values_to_integer.rb b/app/models/automations/values_to_integer.rb similarity index 97% rename from app/models/custom_actions/values_to_integer.rb rename to app/models/automations/values_to_integer.rb index c36aa8fbea6..6894a3f90c7 100644 --- a/app/models/custom_actions/values_to_integer.rb +++ b/app/models/automations/values_to_integer.rb @@ -28,7 +28,7 @@ # See COPYRIGHT and LICENSE files for more details. #++ -module CustomActions::ValuesToInteger +module Automations::ValuesToInteger def values=(values) super(Array(values).map { |v| to_integer_or_nil(v) }.uniq) end diff --git a/app/models/permitted_params.rb b/app/models/permitted_params.rb index 6934800f519..d5959c9f066 100644 --- a/app/models/permitted_params.rb +++ b/app/models/permitted_params.rb @@ -92,12 +92,12 @@ class PermittedParams params.require(:custom_field).permit(*self.class.permitted_attributes[:custom_field]) end - def custom_action + def automation whitelisted = params - .require(:custom_action) - .permit(*self.class.permitted_attributes[:custom_action]) + .require(:automation) + .permit(*self.class.permitted_attributes[:automation]) - whitelisted.merge(params[:custom_action].slice(:actions, :conditions).permit!) + whitelisted.merge(params[:automation].slice(:actions, :conditions).permit!) end def custom_field_type @@ -506,11 +506,12 @@ class PermittedParams hexcode move_to ), - custom_action: %i( - name - description - move_to - ), + automation: [ + :name, + :description, + :move_to, + { triggers_attributes: [:id, :type, :position, { options: {} }, :_destroy] } + ], custom_field: [ :editable, :field_format, diff --git a/app/models/work_package.rb b/app/models/work_package.rb index d4a110bbdd4..2afc54bf2c1 100644 --- a/app/models/work_package.rb +++ b/app/models/work_package.rb @@ -36,7 +36,7 @@ class WorkPackage < ApplicationRecord include WorkPackage::AskBeforeDestruction include WorkPackage::TimeEntriesCleaner include WorkPackage::Ancestors - include WorkPackage::CustomActioned + include WorkPackage::Automatable include WorkPackage::Hooks include WorkPackages::DerivedDates include WorkPackages::SpentTime diff --git a/app/models/work_package/automatable.rb b/app/models/work_package/automatable.rb new file mode 100644 index 00000000000..132c80953ef --- /dev/null +++ b/app/models/work_package/automatable.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module WorkPackage::Automatable + extend ActiveSupport::Concern + + included do + def automations(user) + @automations = Automation + .available_conditions + .inject(Automation.all) do |scope, condition| + scope.merge(condition.automation_scope(self, user)) + end + end + + # API compatibility for /api/v3/custom_actions + def custom_actions(user) + automations(user) + end + end +end diff --git a/app/models/work_package/custom_actioned.rb b/app/models/work_package/custom_actioned.rb deleted file mode 100644 index 0d88d078c31..00000000000 --- a/app/models/work_package/custom_actioned.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -#-- copyright -# OpenProject is an open source project management software. -# Copyright (C) the OpenProject GmbH -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License version 3. -# -# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -# Copyright (C) 2006-2013 Jean-Philippe Lang -# Copyright (C) 2010-2013 the ChiliProject Team -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# See COPYRIGHT and LICENSE files for more details. -#++ - -module WorkPackage::CustomActioned - extend ActiveSupport::Concern - - included do - def custom_actions(user) - @custom_actions = CustomAction - .available_conditions - .inject(CustomAction.all) do |scope, condition| - scope.merge(condition.custom_action_scope(self, user)) - end - end - end -end diff --git a/app/models/work_package_custom_field.rb b/app/models/work_package_custom_field.rb index a021fc7084e..ad79998aaae 100644 --- a/app/models/work_package_custom_field.rb +++ b/app/models/work_package_custom_field.rb @@ -43,7 +43,7 @@ class WorkPackageCustomField < CustomField scopes :visible, :on_visible_type_and_project - scope :usable_as_custom_action, -> { + scope :usable_as_automation, -> { where.not(field_format: %w[hierarchy weighted_item_list]) .order(:name) } diff --git a/app/services/custom_actions/base_service.rb b/app/services/automations/base_service.rb similarity index 52% rename from app/services/custom_actions/base_service.rb rename to app/services/automations/base_service.rb index dfc18ab4f7e..cb5e6fda85d 100644 --- a/app/services/custom_actions/base_service.rb +++ b/app/services/automations/base_service.rb @@ -1,34 +1,6 @@ # 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 CustomActions::BaseService +class Automations::BaseService include Shared::BlockService attr_accessor :user @@ -38,7 +10,7 @@ class CustomActions::BaseService &) set_attributes(action, attributes) - contract = CustomActions::CuContract.new(action) + contract = Automations::CuContract.new(action) result = ServiceResult.new(success: contract.validate && action.save, result: action, errors: contract.errors) @@ -51,10 +23,12 @@ class CustomActions::BaseService def set_attributes(action, attributes) actions_attributes = attributes.delete(:actions) conditions_attributes = attributes.delete(:conditions) - action.attributes = attributes + triggers_attributes = attributes.delete(:triggers_attributes) + action.attributes = attributes set_actions(action, actions_attributes.symbolize_keys) if actions_attributes set_conditions(action, conditions_attributes.symbolize_keys) if conditions_attributes + set_triggers(action, triggers_attributes) if triggers_attributes end def set_actions(action, actions_attributes) @@ -66,21 +40,15 @@ class CustomActions::BaseService end def remove_actions(action, keys) - keys.each do |key| - remove_action(action, key) - end + keys.each { |key| remove_action(action, key) } end def update_actions(action, key_values) - key_values.each do |key, values| - update_action(action, key, values) - end + key_values.each { |key, values| update_action(action, key, values) } end def add_actions(action, key_values) - key_values.each do |key, values| - add_action(action, key, values) - end + key_values.each { |key, values| add_action(action, key, values) } end def update_action(action, key, values) @@ -102,10 +70,14 @@ class CustomActions::BaseService end def available_action_for(action, key) - action.available_actions.detect { |a| a.key == key } || CustomActions::Actions::Inexistent + action.available_actions.detect { |a| a.key == key } || Automations::Actions::Inexistent end def available_condition_for(action, key) - action.available_conditions.detect { |a| a.key == key } || CustomActions::Conditions::Inexistent + action.available_conditions.detect { |a| a.key == key } || Automations::Conditions::Inexistent + end + + def set_triggers(action, attributes) + action.assign_attributes(triggers_attributes: attributes) end end diff --git a/app/services/automations/create_service.rb b/app/services/automations/create_service.rb new file mode 100644 index 00000000000..96897a7478d --- /dev/null +++ b/app/services/automations/create_service.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class Automations::CreateService < Automations::BaseService + def initialize(user:) + self.user = user + end + + def call(attributes:, action: Automation.new, &block) + super + end +end diff --git a/app/services/automations/update_service.rb b/app/services/automations/update_service.rb new file mode 100644 index 00000000000..f718a3631f2 --- /dev/null +++ b/app/services/automations/update_service.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class Automations::UpdateService < Automations::BaseService + attr_accessor :user, + :action + + def initialize(action:, user:) + self.action = action + self.user = user + end + + def call(attributes:, &) + super(attributes:, action:, &) + end +end diff --git a/app/services/automations/update_work_package_service.rb b/app/services/automations/update_work_package_service.rb new file mode 100644 index 00000000000..44af8e6a69d --- /dev/null +++ b/app/services/automations/update_work_package_service.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +class Automations::UpdateWorkPackageService + include Shared::BlockService + include Contracted + + attr_accessor :user, + :action + + def initialize(action:, user:) + self.action = action + self.user = user + self.contract_class = ::WorkPackages::UpdateContract + end + + def call(work_package:, &) + apply_actions(work_package, action.actions) + + result = ::WorkPackages::UpdateService + .new(user:, + model: work_package) + .call + + block_with_result(result, &) + end + + private + + def apply_actions(work_package, actions) + changes_before = work_package.changes.dup + apply_actions_sorted(work_package, actions) + + success, errors = validate(work_package, user) + retry_apply_actions(work_package, actions, errors, changes_before) unless success + end + + def retry_apply_actions(work_package, actions, errors, changes_before) + new_actions = without_invalid_actions(actions, errors) + + if new_actions.any? && actions.length != new_actions.length + work_package.restore_attributes(work_package.changes.keys - changes_before.keys) + apply_actions(work_package, new_actions) + end + end + + def without_invalid_actions(actions, errors) + invalid_keys = errors.attribute_names.map { |k| append_id(k) } + actions.reject { |a| invalid_keys.include?(append_id(a.key)) } + end + + def apply_actions_sorted(work_package, actions) + actions.sort_by(&:priority).each { |a| a.apply(work_package) } + end + + def append_id(sym) + "#{sym.to_s.chomp('_id')}_id" + end +end diff --git a/app/services/custom_actions/create_service.rb b/app/services/custom_actions/create_service.rb deleted file mode 100644 index 1feb0a00397..00000000000 --- a/app/services/custom_actions/create_service.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -#-- copyright -# OpenProject is an open source project management software. -# Copyright (C) the OpenProject GmbH -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License version 3. -# -# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -# Copyright (C) 2006-2013 Jean-Philippe Lang -# Copyright (C) 2010-2013 the ChiliProject Team -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# See COPYRIGHT and LICENSE files for more details. -#++ - -class CustomActions::CreateService < CustomActions::BaseService - def initialize(user:) - self.user = user - end - - def call(attributes:, - action: CustomAction.new, - &block) - super - end -end diff --git a/app/services/custom_actions/update_service.rb b/app/services/custom_actions/update_service.rb deleted file mode 100644 index 12d1d04add6..00000000000 --- a/app/services/custom_actions/update_service.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -#-- copyright -# OpenProject is an open source project management software. -# Copyright (C) the OpenProject GmbH -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License version 3. -# -# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -# Copyright (C) 2006-2013 Jean-Philippe Lang -# Copyright (C) 2010-2013 the ChiliProject Team -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# See COPYRIGHT and LICENSE files for more details. -#++ - -class CustomActions::UpdateService < CustomActions::BaseService - attr_accessor :user, - :action - - def initialize(action:, user:) - self.action = action - self.user = user - end - - def call(attributes:, &) - super(attributes:, action:, &) - end -end diff --git a/app/services/custom_actions/update_work_package_service.rb b/app/services/custom_actions/update_work_package_service.rb deleted file mode 100644 index d4058deaf33..00000000000 --- a/app/services/custom_actions/update_work_package_service.rb +++ /dev/null @@ -1,92 +0,0 @@ -# frozen_string_literal: true - -#-- copyright -# OpenProject is an open source project management software. -# Copyright (C) the OpenProject GmbH -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License version 3. -# -# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -# Copyright (C) 2006-2013 Jean-Philippe Lang -# Copyright (C) 2010-2013 the ChiliProject Team -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# See COPYRIGHT and LICENSE files for more details. -#++ - -class CustomActions::UpdateWorkPackageService - include Shared::BlockService - include Contracted - - attr_accessor :user, - :action - - def initialize(action:, user:) - self.action = action - self.user = user - self.contract_class = ::WorkPackages::UpdateContract - end - - def call(work_package:, &) - apply_actions(work_package, action.actions) - - result = ::WorkPackages::UpdateService - .new(user:, - model: work_package) - .call - - block_with_result(result, &) - end - - private - - def apply_actions(work_package, actions) - changes_before = work_package.changes.dup - - apply_actions_sorted(work_package, actions) - - success, errors = validate(work_package, user) - unless success - retry_apply_actions(work_package, actions, errors, changes_before) - end - end - - def retry_apply_actions(work_package, actions, errors, changes_before) - new_actions = without_invalid_actions(actions, errors) - - if new_actions.any? && actions.length != new_actions.length - work_package.restore_attributes(work_package.changes.keys - changes_before.keys) - apply_actions(work_package, new_actions) - end - end - - def without_invalid_actions(actions, errors) - invalid_keys = errors.attribute_names.map { |k| append_id(k) } - - actions.reject { |a| invalid_keys.include?(append_id(a.key)) } - end - - def apply_actions_sorted(work_package, actions) - actions - .sort_by(&:priority) - .each { |a| a.apply(work_package) } - end - - def append_id(sym) - "#{sym.to_s.chomp('_id')}_id" - end -end diff --git a/app/views/automations/_form.html.erb b/app/views/automations/_form.html.erb new file mode 100644 index 00000000000..0671cc69182 --- /dev/null +++ b/app/views/automations/_form.html.erb @@ -0,0 +1,120 @@ +<% active_section_keys = @automation.actions.map(&:key) %> +<% trigger = @automation.triggers.detect { |t| t.is_a?(Automations::Triggers::Manual) } || @automation.triggers.build(type: "Automations::Triggers::Manual") %> + +
+ <%= f.text_field :name, required: true, container_class: "-middle" %> +
+
+ <%= f.text_area :description, container_class: "-middle" %> +
+ +
+ <%= t("automations.trigger") %> +
+ <%= styled_label_tag "automation_trigger_type", t("automations.triggers.name"), class: "-top" %> +
+ <%= styled_text_field_tag "automation_trigger_type", + t("automations.triggers.manual.label"), + disabled: true, + container_class: "-middle" %> +
+
+ <%= render partial: "trigger_fields_manual", locals: { trigger: } %> +
+ +
+ <%= t("automations.conditions") %> + + <% @automation.all_conditions.each do |condition| %> +
+ <%= styled_label_tag("automation_conditions_#{condition.key}", condition.human_name, class: "-top") %> + <% input_name = "automation[conditions][#{condition.key}]" %> +
+
+ <% if condition.key == :project %> + <%= angular_component_tag "opce-project-autocompleter", + inputs: { + multiple: true, + filters: [{ name: "active", operator: "=", values: ["t"] }], + resource: "projects", + inputName: input_name, + labelForId: "automation_conditions_#{condition.key}", + inputValue: condition.values + } %> + <% else %> + <%= angular_component_tag "opce-autocompleter", + inputs: { + multiple: true, + defaultData: false, + items: condition.allowed_values.map { |v| { id: v[:value], name: v[:label] } }, + model: condition.value_objects.map { |v| { id: v[:value], name: v[:label] } }, + inputName: input_name, + bindLabel: "name", + labelForId: "automation_conditions_#{condition.key}" + } %> + <% end %> +
+
+
+ <% end %> +
+ +
+ <%= t("automations.actions.name") %> + +
+ <% @automation.all_actions.each do |action| %> +
> +
+ <%= styled_label_tag("automation_actions_#{action.key}", action.human_name, class: "-top") %> + <% input_name = "automation[actions][#{action.key}]" %> + + <% if %i(associated_property boolean user project).include?(action.type) %> +
+
+ <% selected = action.value_objects.map { |v| { id: v[:value], name: v[:label] } } %> + <%= angular_component_tag "opce-autocompleter", + inputs: { + multiple: action.multi_value?, + hideSelected: true, + defaultData: false, + items: action.allowed_values.map { |v| { id: v[:value], name: v[:label] } }, + model: action.multi_value? ? selected : selected.first, + inputName: input_name, + bindLabel: "name", + labelForId: "automation_actions_#{action.key}" + } %> +
+
+ <% elsif %i(string_property text_property link_property float_property integer_property).include?(action.type) %> +
+ <%= styled_text_field_tag input_name, action.values, container_class: "-slim" %> +
+ <% end %> + + <%= render(Primer::Beta::IconButton.new(icon: :x, size: :small, scheme: :invisible, type: :button, aria: { label: t(:button_close) }, data: { action: "click->hide-sections#hide" })) %> +
+
+ <% end %> +
+ +
+ + + + <%= select_tag "add_action", + options_for_select(@automation.all_actions.map { |a| [a.human_name, a.key] }, disabled: active_section_keys), + id: "add-automation-action-select", + prompt: t(:"automations.actions.add"), + data: { "hide-sections-target": "select", action: "change->hide-sections#add" }, + class: "form--select" %> + + +
+
diff --git a/app/views/automations/_trigger_fields_manual.html.erb b/app/views/automations/_trigger_fields_manual.html.erb new file mode 100644 index 00000000000..2f62d327221 --- /dev/null +++ b/app/views/automations/_trigger_fields_manual.html.erb @@ -0,0 +1,13 @@ +<%= hidden_field_tag "automation[triggers_attributes][0][type]", "Automations::Triggers::Manual" %> +<%= hidden_field_tag "automation[triggers_attributes][0][id]", trigger.id if trigger.persisted? %> + +
+ <%= styled_label_tag "automation_triggers_attributes_0_options_button_label", t("automations.triggers.manual.button_label"), class: "-top" %> +
+ <%= styled_text_field_tag "automation[triggers_attributes][0][options][button_label]", + trigger.button_label || @automation.name, + id: "automation_triggers_attributes_0_options_button_label", + container_class: "-middle", + required: true %> +
+
diff --git a/app/views/automations/edit.html.erb b/app/views/automations/edit.html.erb new file mode 100644 index 00000000000..9cce7d1ac97 --- /dev/null +++ b/app/views/automations/edit.html.erb @@ -0,0 +1,23 @@ +<% html_title t(:label_administration), t("automations.edit", name: @automation.name) %> + +<%= + render Primer::OpenProject::PageHeader.new do |header| + header.with_title { @automation.name } + header.with_breadcrumbs( + [{ href: admin_index_path, text: t("label_administration") }, + { href: admin_settings_work_packages_general_path, text: t(:label_work_package_plural) }, + { href: automations_path, text: t("automations.plural") }, + @automation.name] + ) + end +%> + +<%= error_messages_for @automation %> + +<% content_controller "hide-sections" %> + +<%= labelled_tabular_form_for @automation, + data: { "hide-sections-target": "form" } do |f| %> + <%= render partial: "form", locals: { f: f } %> + <%= styled_button_tag t(:button_save), class: "-primary -with-icon icon-checkmark" %> +<% end %> diff --git a/app/views/custom_actions/index.html.erb b/app/views/automations/index.html.erb similarity index 61% rename from app/views/custom_actions/index.html.erb rename to app/views/automations/index.html.erb index d1239f92b94..b5479223109 100644 --- a/app/views/custom_actions/index.html.erb +++ b/app/views/automations/index.html.erb @@ -1,12 +1,12 @@ -<% html_title t(:label_administration), t("custom_actions.plural") -%> +<% html_title t(:label_administration), t("automations.plural") -%> <%= render Primer::OpenProject::PageHeader.new do |header| - header.with_title { t("custom_actions.plural") } + header.with_title { t("automations.plural") } header.with_breadcrumbs( [{ href: admin_index_path, text: t("label_administration") }, { href: admin_settings_work_packages_general_path, text: t(:label_work_package_plural) }, - t("custom_actions.plural")] + t("automations.plural")] ) end %> @@ -17,15 +17,15 @@ subheader.with_action_button( scheme: :primary, leading_icon: :plus, - label: t("custom_actions.new"), - test_selector: "op-admin-custom-actions--button-new", + label: t("automations.new"), + test_selector: "op-admin-automations--button-new", tag: :a, disabled: !EnterpriseToken.allows_to?(:custom_actions), - href: new_custom_action_path + href: new_automation_path ) do - CustomAction.model_name.human + Automation.model_name.human end end %> - <%= render(::CustomActions::TableComponent.new(rows: @custom_actions)) %> + <%= render(::Automations::IndexComponent.new(rows: @automations)) %> <% end %> diff --git a/app/views/automations/new.html.erb b/app/views/automations/new.html.erb new file mode 100644 index 00000000000..e01237d7f05 --- /dev/null +++ b/app/views/automations/new.html.erb @@ -0,0 +1,23 @@ +<% html_title t(:label_administration), t("automations.new") %> + +<%= + render Primer::OpenProject::PageHeader.new do |header| + header.with_title { t("automations.new") } + header.with_breadcrumbs( + [{ href: admin_index_path, text: t("label_administration") }, + { href: admin_settings_work_packages_general_path, text: t(:label_work_package_plural) }, + { href: automations_path, text: t("automations.plural") }, + t("automations.new")] + ) + end +%> + +<%= error_messages_for @automation %> + +<% content_controller "hide-sections" %> + +<%= labelled_tabular_form_for @automation, + data: { "hide-sections-target": "form" } do |f| %> + <%= render partial: "form", locals: { f: f } %> + <%= styled_button_tag t(:button_create), class: "-primary -with-icon icon-checkmark" %> +<% end %> diff --git a/app/views/custom_actions/_form.html.erb b/app/views/custom_actions/_form.html.erb deleted file mode 100644 index 90bfaa92e1f..00000000000 --- a/app/views/custom_actions/_form.html.erb +++ /dev/null @@ -1,200 +0,0 @@ -<% active_section_keys = @custom_action.actions.map(&:key) %> - -
- <%= f.text_field :name, required: true, container_class: "-middle" %> -
-
- <%= f.text_area :description, container_class: "-middle" %> -
- -
- - <%= t("custom_actions.conditions") %> - - - <% @custom_action.all_conditions.each do |condition| %> -
- <%= styled_label_tag("custom_action_conditions_#{condition.key}", condition.human_name, class: "-top") %> - <% input_name = "custom_action[conditions][#{condition.key}]" %> - -
-
- <% if condition.key == :project %> - <%= angular_component_tag "opce-project-autocompleter", - inputs: { - multiple: true, - filters: [{ name: "active", operator: "=", values: ["t"] }], - resource: "projects", - inputName: input_name, - labelForId: "custom_action_actions_#{condition.key}", - inputValue: condition.values - } %> - <% else %> - <%= angular_component_tag "opce-autocompleter", - inputs: { - multiple: true, - defaultData: false, - items: condition.allowed_values.map { |v| { id: v[:value], name: v[:label] } }, - model: condition.value_objects.map { |v| { id: v[:value], name: v[:label] } }, - inputName: input_name, - bindLabel: "name", - labelForId: "custom_action_conditions_#{condition.key}" - } %> - <% end %> -
-
-
-
- <% end %> -
- -
- - <%= t("custom_actions.actions.name") %> - - -
- <% @custom_action.all_actions.each do |action| %> -
> -
- <%= styled_label_tag("custom_action_actions_#{action.key}", action.human_name, class: "-top") %> - - <% input_name = "custom_action[actions][#{action.key}]" %> - <% if %i(associated_property boolean user project).include?(action.type) %> -
-
- <% case action %> - <% in { type: :project } %> - <%= angular_component_tag "opce-project-autocompleter", - inputs: { - multiple: false, - filters: [{ name: "active", operator: "=", values: ["t"] }], - resource: "projects", - inputName: input_name, - appendTo: "body", - labelForId: "custom_action_actions_#{action.key}", - inputValue: action.values.first - } %> - <% in { type: :user, custom_field_based: false } %> - <% selected = action.value_objects.map { |v| { id: v[:value], name: v[:label] } } %> - <%= angular_component_tag "opce-user-autocompleter", - inputs: { - multiple: action.multi_value?, - hideSelected: true, - defaultData: false, - placeholder: I18n.t(:label_user_search), - resource: "principals", - url: ::API::V3::Utilities::PathHelper::ApiV3Path.principals, - model: action.multi_value? ? selected : selected.first, - inputName: input_name, - filters: [ - { name: "status", operator: "!", values: [Principal.statuses["locked"].to_s] } - ], - additionalOptions: [ - { id: nil, name: I18n.t("placeholders.default") }, - { id: action.current_user_value_key, name: action.current_user_name } - ], - searchKey: "any_name_attribute", - labelForId: "custom_action_actions_#{action.key}", - focusDirectly: false - } %> - <% else %> - <% selected = action.value_objects.map { |v| { id: v[:value], name: v[:label] } } %> - <%= angular_component_tag "opce-autocompleter", - inputs: { - multiple: action.multi_value?, - hideSelected: true, - defaultData: false, - items: action.allowed_values.map { |v| { id: v[:value], name: v[:label] } }, - model: action.multi_value? ? selected : selected.first, - inputName: input_name, - bindLabel: "name", - labelForId: "custom_action_actions_#{action.key}" - } %> - <% end %> -
-
- <% elsif %i(date_property).include?(action.type) %> -
- <% date = action.values.first %> - <%= angular_component_tag "opce-custom-date-action-admin", - data: { "field-value": date.try(:iso8601) || date, "field-name": input_name } %> -
- <% elsif %i(string_property text_property).include?(action.type) %> -
- <%= styled_text_field_tag input_name, - action.values, - container_class: "-slim", - step: "any" %> -
- <% elsif %i(link_property).include?(action.type) %> -
- <%= styled_url_field_tag input_name, - action.values, - container_class: "-slim", - step: "any" %> -
- <% elsif action.type == :float_property %> -
- <%= styled_number_field_tag input_name, - action.values, - container_class: "-slim", - min: action.minimum, - max: action.maximum, - step: "any" %> -
- <% elsif action.type == :integer_property %> -
- <%= styled_number_field_tag input_name, - action.values, - container_class: "-slim", - min: action.minimum, - max: action.maximum, - step: 1 %> -
- <% end %> - <%= - render( - Primer::Beta::IconButton.new( - icon: :x, - size: :small, - scheme: :invisible, - type: :button, - aria: { label: t(:button_close) }, - data: { action: "click->hide-sections#hide" } - ) - ) - %> -
-
- <% end %> -
- -
- -
- - - - <%= select_tag "add_action", - options_for_select( - @custom_action.all_actions.map { |a| [a.human_name, a.key] }, - disabled: active_section_keys - ), - id: "add-custom-action-select", - prompt: t(:"custom_actions.actions.add"), - data: { - "hide-sections-target": "select", - action: "change->hide-sections#add" - }, - class: "form--select" %> - - -
-
diff --git a/app/views/custom_actions/edit.html.erb b/app/views/custom_actions/edit.html.erb deleted file mode 100644 index 765b47d7fc9..00000000000 --- a/app/views/custom_actions/edit.html.erb +++ /dev/null @@ -1,52 +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. - -++#%> - -<% html_title t(:label_administration), t("custom_actions.edit", name: @custom_action.name) %> - -<%= - render Primer::OpenProject::PageHeader.new do |header| - header.with_title { @custom_action.name } - header.with_breadcrumbs( - [{ href: admin_index_path, text: t("label_administration") }, - { href: admin_settings_work_packages_general_path, text: t(:label_work_package_plural) }, - { href: custom_actions_path, text: t("custom_actions.plural") }, - @custom_action.name] - ) - end -%> - -<%= error_messages_for @custom_action %> - -<% content_controller "hide-sections" %> - -<%= labelled_tabular_form_for @custom_action, - data: { "hide-sections-target": "form" } do |f| %> - <%= render partial: "form", locals: { f: f } %> - <%= styled_button_tag t(:button_save), class: "-primary -with-icon icon-checkmark" %> -<% end %> diff --git a/app/views/custom_actions/new.html.erb b/app/views/custom_actions/new.html.erb deleted file mode 100644 index ce3f147df66..00000000000 --- a/app/views/custom_actions/new.html.erb +++ /dev/null @@ -1,52 +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. - -++#%> - -<% html_title t(:label_administration), t("custom_actions.new") %> - -<%= - render Primer::OpenProject::PageHeader.new do |header| - header.with_title { t("custom_actions.new") } - header.with_breadcrumbs( - [{ href: admin_index_path, text: t("label_administration") }, - { href: admin_settings_work_packages_general_path, text: t(:label_work_package_plural) }, - { href: custom_actions_path, text: t("custom_actions.plural") }, - t("custom_actions.new")] - ) - end -%> - -<%= error_messages_for @custom_action %> - -<% content_controller "hide-sections" %> - -<%= labelled_tabular_form_for @custom_action, - data: { "hide-sections-target": "form" } do |f| %> - <%= render partial: "form", locals: { f: f } %> - <%= styled_button_tag t(:button_create), class: "-primary -with-icon icon-checkmark" %> -<% end %> diff --git a/config/initializers/menus.rb b/config/initializers/menus.rb index 162c2712345..ee7d94b7e99 100644 --- a/config/initializers/menus.rb +++ b/config/initializers/menus.rb @@ -475,10 +475,10 @@ Redmine::MenuManager.map :admin_menu do |menu| icon: "op-custom-fields", html: { class: "custom_fields" } - menu.push :custom_actions, - { controller: "/custom_actions" }, + menu.push :automations, + { controller: "/automations" }, if: ->(_) { User.current.admin? }, - caption: :"custom_actions.plural", + caption: :"automations.plural", parent: :admin_work_packages, enterprise_feature: "custom_actions" diff --git a/config/locales/en.yml b/config/locales/en.yml index 9f8e0446a38..1042943d45a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -658,6 +658,29 @@ en: edit: "Edit custom action %{name}" execute: "Execute %{name}" + automations: + trigger: "Trigger" + triggers: + name: "Triggers" + manual: + label: "Manual button click" + button_label: "Button label" + actions: + name: "Actions" + add: "Add action" + assigned_to: + executing_user_value: "(Assign to executing user)" + conditions: "Conditions" + plural: "Automations" + new: "New automation" + edit: "Edit automation %{name}" + summary: + title: "Summary" + when: "When" + if: "If" + then: "Then" + none: "None" + custom_fields: admin: custom_field_projects: @@ -2807,6 +2830,7 @@ en: principal: "User or group" # kept for backwards compatibility issue: "Work package" + inexistent: "Inexistent" journal: "Journal" journal_notes: "Comment" lastname: "Last name" diff --git a/config/routes.rb b/config/routes.rb index e12cf76bc09..7db1a2972d2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -661,7 +661,7 @@ Rails.application.routes.draw do end end - resources :custom_actions, except: :show + resources :automations, except: :show namespace :oauth do resources :applications do diff --git a/db/migrate/20260511110000_convert_custom_actions_to_automations.rb b/db/migrate/20260511110000_convert_custom_actions_to_automations.rb new file mode 100644 index 00000000000..40e05ac1196 --- /dev/null +++ b/db/migrate/20260511110000_convert_custom_actions_to_automations.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +class ConvertCustomActionsToAutomations < ActiveRecord::Migration[8.1] + class MigrationAutomation < ApplicationRecord + self.table_name = "automations" + end + + class MigrationTrigger < ApplicationRecord + self.table_name = "automation_triggers" + end + + def up + rename_table :custom_actions, :automations + + rename_habtm_table :custom_actions_statuses, :automations_statuses, :custom_action_id, :automation_id + rename_habtm_table :custom_actions_roles, :automations_roles, :custom_action_id, :automation_id + rename_habtm_table :custom_actions_types, :automations_types, :custom_action_id, :automation_id + rename_habtm_table :custom_actions_projects, :automations_projects, :custom_action_id, :automation_id + + create_table :automation_triggers do |t| + t.references :automation, null: false, foreign_key: true, index: true + t.string :type, null: false + t.jsonb :options, null: false, default: {} + t.integer :position + + t.timestamps + end + + MigrationAutomation.reset_column_information + MigrationTrigger.reset_column_information + + MigrationAutomation.find_each do |automation| + MigrationTrigger.create!( + automation_id: automation.id, + type: "Automations::Triggers::Manual", + options: { button_label: automation.name }, + position: 1 + ) + end + end + + def down + drop_table :automation_triggers + + rename_habtm_table :automations_projects, :custom_actions_projects, :automation_id, :custom_action_id + rename_habtm_table :automations_types, :custom_actions_types, :automation_id, :custom_action_id + rename_habtm_table :automations_roles, :custom_actions_roles, :automation_id, :custom_action_id + rename_habtm_table :automations_statuses, :custom_actions_statuses, :automation_id, :custom_action_id + + rename_table :automations, :custom_actions + end + + private + + def rename_habtm_table(from, to, old_fk, new_fk) + rename_table from, to + rename_column to, old_fk, new_fk + rename_fk_index(to, from, to, old_fk, new_fk) + end + + def rename_fk_index(table, from_name, to_name, old_fk, new_fk) + old_index_name = "index_#{from_name}_on_#{old_fk}" + new_index_name = "index_#{to_name}_on_#{new_fk}" + + if index_name_exists?(table, old_index_name) + rename_index table, old_index_name, new_index_name + elsif !index_name_exists?(table, new_index_name) + add_index table, new_fk, name: new_index_name + end + end +end diff --git a/lib/api/v3/custom_actions/custom_action_representer.rb b/lib/api/v3/custom_actions/custom_action_representer.rb index 29328e86157..846a6ee3449 100644 --- a/lib/api/v3/custom_actions/custom_action_representer.rb +++ b/lib/api/v3/custom_actions/custom_action_representer.rb @@ -33,7 +33,7 @@ module API link :executeImmediately do { href: api_v3_paths.custom_action_execute(represented.id), - title: I18n.t("custom_actions.execute", name: represented.name), + title: I18n.t("custom_actions.execute", name: name), method: "post" } end @@ -44,6 +44,10 @@ module API property :description, render_nil: true + def name + represented.triggers.detect { |trigger| trigger.is_a?(::Automations::Triggers::Manual) }&.button_label || represented.name + end + def _type "CustomAction" end diff --git a/lib/api/v3/custom_actions/custom_actions_api.rb b/lib/api/v3/custom_actions/custom_actions_api.rb index 9bcb30992ef..931903360a7 100644 --- a/lib/api/v3/custom_actions/custom_actions_api.rb +++ b/lib/api/v3/custom_actions/custom_actions_api.rb @@ -34,7 +34,7 @@ module API route_param :id, type: Integer, desc: "Custom action ID" do helpers do def custom_action - @custom_action ||= CustomAction.find(params[:id]) + @custom_action ||= Automation.with_manual_trigger.find(params[:id]) end end @@ -63,8 +63,8 @@ module API end after_validation do - contract = ::CustomActions::ExecuteContract.new(parsed_params, current_user, - options: { custom_action: }) + contract = ::Automations::ExecuteContract.new(parsed_params, current_user, + options: { automation: custom_action }) unless contract.valid? fail ::API::Errors::ErrorBase.create_and_merge_errors(contract.errors) @@ -75,7 +75,7 @@ module API work_package = WorkPackage.visible.find_by(id: parsed_params.work_package_id) work_package.lock_version = parsed_params.lock_version - ::CustomActions::UpdateWorkPackageService + ::Automations::UpdateWorkPackageService .new(user: current_user, action: custom_action) .call(work_package:) do |call| diff --git a/lib/api/v3/work_packages/eager_loading/custom_action.rb b/lib/api/v3/work_packages/eager_loading/automation.rb similarity index 62% rename from lib/api/v3/work_packages/eager_loading/custom_action.rb rename to lib/api/v3/work_packages/eager_loading/automation.rb index 4a12af1d7a8..65e67dfc153 100644 --- a/lib/api/v3/work_packages/eager_loading/custom_action.rb +++ b/lib/api/v3/work_packages/eager_loading/automation.rb @@ -30,40 +30,43 @@ module API module V3 module WorkPackages module EagerLoading - class CustomAction < Base + class Automation < Base def apply(work_package) - applicable_actions = custom_actions.select do |action| - action.conditions_fulfilled?(work_package, User.current) + applicable_automations = automations.select do |automation| + automation.conditions_fulfilled?(work_package, User.current) end - work_package.custom_actions = applicable_actions + work_package.automations = applicable_automations end def self.module - CustomActionAccessor + AutomationAccessor end private - def custom_actions - @custom_actions ||= ::CustomAction - .available_conditions - .inject(::CustomAction.all) do |scope, condition| - scope.merge(condition.custom_action_scope(work_packages, User.current)) + def automations + @automations ||= ::Automation + .available_conditions + .inject(::Automation.with_manual_trigger.includes(:triggers)) do |scope, condition| + scope.merge(condition.automation_scope(work_packages, User.current)) end end - module CustomActionAccessor + module AutomationAccessor extend ActiveSupport::Concern included do - attr_writer :custom_actions + attr_writer :automations - # Hiding the work_package's own custom_actions method - # to profit from the eager loaded actions - def custom_actions(_user) - @custom_actions + # Hiding the work_package's own automations method + # to profit from the eager loaded automations + def automations(_user) + @automations end + + # API compatibility for /api/v3/custom_actions + def custom_actions(user) = automations(user) end end end diff --git a/lib/api/v3/work_packages/work_package_eager_loading_wrapper.rb b/lib/api/v3/work_packages/work_package_eager_loading_wrapper.rb index 79c41206e8b..da7ff6363a2 100644 --- a/lib/api/v3/work_packages/work_package_eager_loading_wrapper.rb +++ b/lib/api/v3/work_packages/work_package_eager_loading_wrapper.rb @@ -78,7 +78,7 @@ module API ::API::V3::WorkPackages::EagerLoading::Principals, ::API::V3::WorkPackages::EagerLoading::Checksum, ::API::V3::WorkPackages::EagerLoading::CustomValue, - ::API::V3::WorkPackages::EagerLoading::CustomAction, + ::API::V3::WorkPackages::EagerLoading::Automation, # Have the historic attributes last as they require the custom values # to be loaded first in order to create the diffs between the current # and the historic values without loading the custom fields (Acts::Journalized::Differ). diff --git a/spec/contracts/custom_actions/cu_contract_spec.rb b/spec/contracts/automations/cu_contract_spec.rb similarity index 85% rename from spec/contracts/custom_actions/cu_contract_spec.rb rename to spec/contracts/automations/cu_contract_spec.rb index 4a87e4dafeb..bccd37e6f72 100644 --- a/spec/contracts/custom_actions/cu_contract_spec.rb +++ b/spec/contracts/automations/cu_contract_spec.rb @@ -31,13 +31,13 @@ require "spec_helper" require "contracts/shared/model_contract_shared_context" -RSpec.describe CustomActions::CuContract do +RSpec.describe Automations::CuContract do include_context "ModelContract shared context" let(:user) { build_stubbed(:user) } let(:action) do - build_stubbed(:custom_action, actions: - [CustomActions::Actions::AssignedTo.new]) + build_stubbed(:automation, actions: + [Automations::Actions::AssignedTo.new]) end let(:contract) { described_class.new(action) } @@ -65,7 +65,7 @@ RSpec.describe CustomActions::CuContract do describe "actions" do it "is writable" do - responsible_action = CustomActions::Actions::Responsible.new + responsible_action = Automations::Actions::Responsible.new action.actions = [responsible_action] @@ -79,13 +79,13 @@ RSpec.describe CustomActions::CuContract do end it "requires a value if the action requires one" do - action.actions = [CustomActions::Actions::Status.new([])] + action.actions = [Automations::Actions::Status.new([])] expect_contract_invalid actions: :empty end it "allows only the allowed values" do - status_action = CustomActions::Actions::Status.new([0]) + status_action = Automations::Actions::Status.new([0]) allow(status_action) .to receive(:allowed_values) .and_return([{ value: nil, label: "-" }, @@ -97,7 +97,7 @@ RSpec.describe CustomActions::CuContract do end it "is not allowed to have an inexistent action" do - action.actions = [CustomActions::Actions::Inexistent.new] + action.actions = [Automations::Actions::Inexistent.new] expect_contract_invalid actions: :does_not_exist end @@ -112,7 +112,7 @@ RSpec.describe CustomActions::CuContract do end it "allows only the allowed values" do - status_condition = CustomActions::Conditions::Status.new([0]) + status_condition = Automations::Conditions::Status.new([0]) allow(status_condition) .to receive(:allowed_values) .and_return([{ value: nil, label: "-" }, @@ -124,7 +124,7 @@ RSpec.describe CustomActions::CuContract do end it "is not allowed to have an inexistent condition" do - action.conditions = [CustomActions::Conditions::Inexistent.new] + action.conditions = [Automations::Conditions::Inexistent.new] expect_contract_invalid conditions: :does_not_exist end diff --git a/spec/controllers/custom_actions_controller_spec.rb b/spec/controllers/automations_controller_spec.rb similarity index 89% rename from spec/controllers/custom_actions_controller_spec.rb rename to spec/controllers/automations_controller_spec.rb index e3473ab68a6..b1eef4cdc49 100644 --- a/spec/controllers/custom_actions_controller_spec.rb +++ b/spec/controllers/automations_controller_spec.rb @@ -30,12 +30,12 @@ require "spec_helper" -RSpec.describe CustomActionsController, with_ee: %i[custom_actions] do +RSpec.describe AutomationsController, with_ee: %i[automations] do let(:admin) { build(:admin) } let(:non_admin) { build(:user) } - let(:action) { build_stubbed(:custom_action) } + let(:action) { build_stubbed(:automation) } let(:params) do - { custom_action: { name: "blubs", + { automation: { name: "blubs", actions: { assigned_to: 1 } } } end @@ -72,7 +72,7 @@ RSpec.describe CustomActionsController, with_ee: %i[custom_actions] do let(:call) { get :index } before do - allow(CustomAction) + allow(Automation) .to receive(:order_by_position) .and_return([action]) end @@ -94,8 +94,8 @@ RSpec.describe CustomActionsController, with_ee: %i[custom_actions] do .to render_template("index") end - it "assigns the custom actions" do - expect(assigns(:custom_actions)) + it "assigns the automations" do + expect(assigns(:automations)) .to contain_exactly(action) end end @@ -122,7 +122,7 @@ RSpec.describe CustomActionsController, with_ee: %i[custom_actions] do before do login_as(admin) - allow(CustomAction) + allow(Automation) .to receive(:new) .and_return(action) @@ -139,8 +139,8 @@ RSpec.describe CustomActionsController, with_ee: %i[custom_actions] do .to render_template("new") end - it "assigns custom_action" do - expect(assigns(:custom_action)) + it "assigns automation" do + expect(assigns(:automation)) .to eql action end end @@ -156,14 +156,14 @@ RSpec.describe CustomActionsController, with_ee: %i[custom_actions] do let(:permitted_params) do ActionController::Parameters .new(params) - .require(:custom_action) + .require(:automation) .permit(:name) .merge(ActionController::Parameters.new(actions: { assigned_to: "1" }).permit!) end let!(:service) do service = double("create service") - allow(CustomActions::CreateService) + allow(Automations::CreateService) .to receive(:new) .with(user: admin) .and_return(service) @@ -190,7 +190,7 @@ RSpec.describe CustomActionsController, with_ee: %i[custom_actions] do context "on success" do it "redirects to index" do expect(response) - .to redirect_to(custom_actions_path) + .to redirect_to(automations_path) end end @@ -203,7 +203,7 @@ RSpec.describe CustomActionsController, with_ee: %i[custom_actions] do end it "assigns custom action" do - expect(assigns[:custom_action]) + expect(assigns[:automation]) .to eql action end end @@ -222,7 +222,7 @@ RSpec.describe CustomActionsController, with_ee: %i[custom_actions] do end before do - allow(CustomAction) + allow(Automation) .to receive(:find) .with(params[:id]) .and_return(action) @@ -245,15 +245,15 @@ RSpec.describe CustomActionsController, with_ee: %i[custom_actions] do .to render_template("edit") end - it "assigns custom_action" do - expect(assigns(:custom_action)) + it "assigns automation" do + expect(assigns(:automation)) .to eql action end end context "for admins on invalid id" do before do - allow(CustomAction) + allow(Automation) .to receive(:find) .with(params[:id]) .and_raise(ActiveRecord::RecordNotFound) @@ -280,19 +280,19 @@ RSpec.describe CustomActionsController, with_ee: %i[custom_actions] do let(:permitted_params) do ActionController::Parameters .new(params) - .require(:custom_action) + .require(:automation) .permit(:name) .merge(ActionController::Parameters.new(actions: { assigned_to: "1" }).permit!) end let(:params) do - { custom_action: { name: "blubs", + { automation: { name: "blubs", actions: { assigned_to: 1 } }, id: "42" } end let!(:service) do service = double("update service") - allow(CustomActions::UpdateService) + allow(Automations::UpdateService) .to receive(:new) .with(user: admin, action:) .and_return(service) @@ -310,7 +310,7 @@ RSpec.describe CustomActionsController, with_ee: %i[custom_actions] do end before do - allow(CustomAction) + allow(Automation) .to receive(:find) .with(params[:id]) .and_return(action) @@ -326,7 +326,7 @@ RSpec.describe CustomActionsController, with_ee: %i[custom_actions] do context "on success" do it "redirects to index" do expect(response) - .to redirect_to(custom_actions_path) + .to redirect_to(automations_path) end end @@ -339,7 +339,7 @@ RSpec.describe CustomActionsController, with_ee: %i[custom_actions] do end it "assigns the action" do - expect(assigns[:custom_action]) + expect(assigns[:automation]) .to eql(action) end end @@ -347,7 +347,7 @@ RSpec.describe CustomActionsController, with_ee: %i[custom_actions] do context "for admins on invalid id" do before do - allow(CustomAction) + allow(Automation) .to receive(:find) .with(params[:id]) .and_raise(ActiveRecord::RecordNotFound) @@ -375,7 +375,7 @@ RSpec.describe CustomActionsController, with_ee: %i[custom_actions] do end before do - allow(CustomAction) + allow(Automation) .to receive(:find) .with(params[:id]) .and_return(action) @@ -394,13 +394,13 @@ RSpec.describe CustomActionsController, with_ee: %i[custom_actions] do it "redirects to index" do expect(response) - .to redirect_to(custom_actions_path) + .to redirect_to(automations_path) end end context "for admins on invalid id" do before do - allow(CustomAction) + allow(Automation) .to receive(:find) .with(params[:id]) .and_raise(ActiveRecord::RecordNotFound) diff --git a/spec/factories/custom_action_factory.rb b/spec/factories/automation_factory.rb similarity index 75% rename from spec/factories/custom_action_factory.rb rename to spec/factories/automation_factory.rb index 1fa4b6cc3f3..4ccb699c8f6 100644 --- a/spec/factories/custom_action_factory.rb +++ b/spec/factories/automation_factory.rb @@ -29,8 +29,23 @@ #++ FactoryBot.define do - factory :custom_action do + factory :automation do sequence(:name) { |n| "Custom action #{n} - name" } sequence(:description) { |n| "Custom action #{n} - description" } + + after(:build) do |automation| + next if automation.triggers.any? { |trigger| trigger.is_a?(Automations::Triggers::Manual) } + + automation.triggers << build(:manual_trigger, automation:) + end + + trait :with_manual_trigger end + + factory :manual_trigger, class: "Automations::Triggers::Manual" do + automation + button_label { "Run automation" } + end + + factory :automation_with_manual_trigger, parent: :automation end diff --git a/spec/features/work_packages/custom_actions/custom_actions_link_value_spec.rb b/spec/features/work_packages/automations/automations_link_value_spec.rb similarity index 90% rename from spec/features/work_packages/custom_actions/custom_actions_link_value_spec.rb rename to spec/features/work_packages/automations/automations_link_value_spec.rb index 8bd29bb4279..d67a424173a 100644 --- a/spec/features/work_packages/custom_actions/custom_actions_link_value_spec.rb +++ b/spec/features/work_packages/automations/automations_link_value_spec.rb @@ -30,7 +30,7 @@ require "spec_helper" -RSpec.describe "Custom actions link cf value", :js, with_ee: %i[custom_actions] do +RSpec.describe "Automations link cf value", :js, with_ee: %i[automations] do shared_let(:admin) { create(:admin) } let(:permissions) { %i(view_work_packages edit_work_packages) } @@ -51,7 +51,7 @@ RSpec.describe "Custom actions link cf value", :js, with_ee: %i[custom_actions] let(:default_priority) do create(:default_priority, name: "Normal") end - let(:new_ca_page) { Pages::Admin::CustomActions::New.new } + let(:new_ca_page) { Pages::Admin::Automations::New.new } before do login_as(admin) @@ -65,7 +65,7 @@ RSpec.describe "Custom actions link cf value", :js, with_ee: %i[custom_actions] new_ca_page.create - assign = CustomAction.last + assign = Automation.last expect(assign.actions.length).to eq(1) expect(assign.conditions.length).to eq(0) expect(assign.actions.first.values).to eq(["https://example.com"]) @@ -73,8 +73,8 @@ RSpec.describe "Custom actions link cf value", :js, with_ee: %i[custom_actions] login_as user wp_page.visit! - wp_page.expect_custom_action("Set Link CF") - wp_page.click_custom_action("Set Link CF") + wp_page.expect_automation("Set Link CF") + wp_page.click_automation("Set Link CF") wp_page.expect_attributes "customField#{custom_field.id}": "https://example.com" end end diff --git a/spec/features/work_packages/custom_actions/custom_actions_me_value_spec.rb b/spec/features/work_packages/automations/automations_me_value_spec.rb similarity index 86% rename from spec/features/work_packages/custom_actions/custom_actions_me_value_spec.rb rename to spec/features/work_packages/automations/automations_me_value_spec.rb index 30473b9f170..d475c518767 100644 --- a/spec/features/work_packages/custom_actions/custom_actions_me_value_spec.rb +++ b/spec/features/work_packages/automations/automations_me_value_spec.rb @@ -30,7 +30,7 @@ require "spec_helper" -RSpec.describe "Custom actions me value", :js, with_ee: %i[custom_actions] do +RSpec.describe "Automations me value", :js, with_ee: %i[automations] do shared_let(:admin) { create(:admin) } let(:permissions) { %i(view_work_packages edit_work_packages) } @@ -51,7 +51,7 @@ RSpec.describe "Custom actions me value", :js, with_ee: %i[custom_actions] do let(:default_priority) do create(:default_priority, name: "Normal") end - let(:index_ca_page) { Pages::Admin::CustomActions::Index.new } + let(:index_ca_page) { Pages::Admin::Automations::Index.new } before do login_as(admin) @@ -63,11 +63,11 @@ RSpec.describe "Custom actions me value", :js, with_ee: %i[custom_actions] do new_ca_page = index_ca_page.new 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")) + new_ca_page.add_action(custom_field.name, I18n.t("automations.actions.assigned_to.executing_user_value")) new_ca_page.create - assign = CustomAction.last + assign = Automation.last expect(assign.actions.length).to eq(1) expect(assign.conditions.length).to eq(0) expect(assign.actions.first.values).to eq(["current_user"]) @@ -75,8 +75,8 @@ RSpec.describe "Custom actions me value", :js, with_ee: %i[custom_actions] do 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_automation("Set CF to me") + wp_page.click_automation("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/custom_actions_spec.rb b/spec/features/work_packages/automations/automations_spec.rb similarity index 86% rename from spec/features/work_packages/custom_actions/custom_actions_spec.rb rename to spec/features/work_packages/automations/automations_spec.rb index 6eaf22a01c8..24fff29eb88 100644 --- a/spec/features/work_packages/custom_actions/custom_actions_spec.rb +++ b/spec/features/work_packages/automations/automations_spec.rb @@ -30,7 +30,7 @@ require "spec_helper" -RSpec.describe "Custom actions", :js, with_ee: %i[custom_actions] do +RSpec.describe "Automations", :js, with_ee: %i[automations] do shared_let(:admin) { create(:admin) } shared_let(:permissions) { %i(view_work_packages edit_work_packages move_work_packages work_package_assigned) } @@ -135,7 +135,7 @@ RSpec.describe "Custom actions", :js, with_ee: %i[custom_actions] do work_package.type.custom_fields << cf end end - let(:index_ca_page) { Pages::Admin::CustomActions::Index.new } + let(:index_ca_page) { Pages::Admin::Automations::Index.new } let(:activity_tab) { Components::WorkPackages::Activities.new(work_package) } before do @@ -161,7 +161,7 @@ RSpec.describe "Custom actions", :js, with_ee: %i[custom_actions] do index_ca_page.expect_current_path index_ca_page.expect_listed("Unassign") - unassign = CustomAction.last + unassign = Automation.last expect(unassign.actions.length).to eq(1) expect(unassign.conditions.length).to eq(0) @@ -186,7 +186,7 @@ RSpec.describe "Custom actions", :js, with_ee: %i[custom_actions] do index_ca_page.expect_current_path index_ca_page.expect_listed("Unassign", "Close") - close = CustomAction.last + close = Automation.last expect(close.actions.length).to eq(1) expect(close.conditions.length).to eq(2) @@ -210,7 +210,7 @@ RSpec.describe "Custom actions", :js, with_ee: %i[custom_actions] do index_ca_page.expect_current_path index_ca_page.expect_listed("Unassign", "Close", "Escalate") - escalate = CustomAction.last + escalate = Automation.last expect(escalate.actions.length).to eq(3) expect(escalate.conditions.length).to eq(0) @@ -241,7 +241,7 @@ RSpec.describe "Custom actions", :js, with_ee: %i[custom_actions] do index_ca_page.expect_current_path index_ca_page.expect_listed("Unassign", "Close", "Escalate", "Reset") - reset = CustomAction.last + reset = Automation.last expect(reset.actions.length).to eq(4) expect(reset.conditions.length).to eq(1) @@ -261,7 +261,7 @@ RSpec.describe "Custom actions", :js, with_ee: %i[custom_actions] do index_ca_page.expect_current_path index_ca_page.expect_listed("Unassign", "Close", "Escalate", "Reset", "Other roles action") - other_roles_action = CustomAction.last + other_roles_action = Automation.last expect(other_roles_action.actions.length).to eq(1) expect(other_roles_action.conditions.length).to eq(1) @@ -282,7 +282,7 @@ RSpec.describe "Custom actions", :js, with_ee: %i[custom_actions] do end date = (Date.current + 5.days) - find("#custom_action_actions_custom_field_#{date_custom_field.id}_visible").click + find("#automation_actions_custom_field_#{date_custom_field.id}_visible").click datepicker = Components::Datepicker.new "body" datepicker.set_date date @@ -301,7 +301,7 @@ RSpec.describe "Custom actions", :js, with_ee: %i[custom_actions] do index_ca_page.expect_current_path index_ca_page.expect_listed("Unassign", "Close", "Escalate", "Reset", "Other roles action", "Move project") - move_project = CustomAction.last + move_project = Automation.last expect(move_project.actions.length).to eq(3) expect(move_project.conditions.length).to eq(1) @@ -310,13 +310,13 @@ RSpec.describe "Custom actions", :js, with_ee: %i[custom_actions] do wp_page.visit! - wp_page.expect_custom_action("Unassign") - wp_page.expect_custom_action("Close") - wp_page.expect_custom_action("Escalate") - wp_page.expect_custom_action("Move project") - wp_page.expect_no_custom_action("Reset") - wp_page.expect_no_custom_action("Other roles action") - wp_page.expect_custom_action_order("Unassign", "Close", "Escalate", "Move project") + wp_page.expect_automation("Unassign") + wp_page.expect_automation("Close") + wp_page.expect_automation("Escalate") + wp_page.expect_automation("Move project") + wp_page.expect_no_automation("Reset") + wp_page.expect_no_automation("Other roles action") + wp_page.expect_automation_order("Unassign", "Close", "Escalate", "Move project") within(".custom-actions") do # When hovering over the button, the description is displayed @@ -324,11 +324,11 @@ RSpec.describe "Custom actions", :js, with_ee: %i[custom_actions] do .to have_button("Unassign", title: "Removes the assignee") end - wp_page.click_custom_action("Unassign") + wp_page.click_automation("Unassign") wp_page.expect_attributes assignee: "-" activity_tab.expect_journal_details_header(text: user.name) - wp_page.click_custom_action("Escalate") + wp_page.click_automation("Escalate") wp_page.expect_attributes priority: immediate_priority.name, status: default_status.name, assignee: "-", @@ -336,14 +336,14 @@ RSpec.describe "Custom actions", :js, with_ee: %i[custom_actions] do activity_tab.expect_journal_mention(text: other_member_user.name) - wp_page.click_custom_action("Close") + wp_page.click_automation("Close") wp_page.expect_attributes status: closed_status.name, priority: immediate_priority.name - wp_page.expect_custom_action("Reset") - wp_page.expect_no_custom_action("Close") + wp_page.expect_automation("Reset") + wp_page.expect_no_automation("Close") - wp_page.click_custom_action("Reset") + wp_page.click_automation("Reset") wp_page.expect_attributes priority: default_priority.name, status: default_status.name, @@ -375,7 +375,7 @@ RSpec.describe "Custom actions", :js, with_ee: %i[custom_actions] do index_ca_page.expect_current_path index_ca_page.expect_listed("Unassign", "Close", "Escalate", "Reject") - reset = CustomAction.find_by(name: "Reject") + reset = Automation.find_by(name: "Reject") expect(reset.actions.length).to eq(3) expect(reset.conditions.length).to eq(1) @@ -389,21 +389,21 @@ RSpec.describe "Custom actions", :js, with_ee: %i[custom_actions] do wp_page.visit! - wp_page.expect_custom_action("Unassign") - wp_page.expect_custom_action("Close") - wp_page.expect_custom_action("Escalate") - wp_page.expect_custom_action("Move project") - wp_page.expect_custom_action("Reject") - wp_page.expect_no_custom_action("Reset") - wp_page.expect_custom_action_order("Move project", "Close", "Reject", "Unassign", "Escalate") + wp_page.expect_automation("Unassign") + wp_page.expect_automation("Close") + wp_page.expect_automation("Escalate") + wp_page.expect_automation("Move project") + wp_page.expect_automation("Reject") + wp_page.expect_no_automation("Reset") + wp_page.expect_automation_order("Move project", "Close", "Reject", "Unassign", "Escalate") - wp_page.click_custom_action("Reject") + wp_page.click_automation("Reject") wp_page.expect_attributes assignee: "-", status: rejected_status.name, priority: default_priority.name - wp_page.expect_custom_action("Close") - wp_page.expect_no_custom_action("Reject") + wp_page.expect_automation("Close") + wp_page.expect_no_automation("Reject") # Delete 'Reject' from list of actions login_as(admin) @@ -419,13 +419,13 @@ RSpec.describe "Custom actions", :js, with_ee: %i[custom_actions] do wp_page.visit! - wp_page.expect_no_custom_action("Unassign") - wp_page.expect_custom_action("Close") - wp_page.expect_custom_action("Escalate") - wp_page.expect_no_custom_action("Reject") + wp_page.expect_no_automation("Unassign") + wp_page.expect_automation("Close") + wp_page.expect_automation("Escalate") + wp_page.expect_no_automation("Reject") # Move project - wp_page.click_custom_action("Move project") + wp_page.click_automation("Move project") wp_page.expect_attributes assignee: "-", status: rejected_status.name, @@ -437,7 +437,7 @@ RSpec.describe "Custom actions", :js, with_ee: %i[custom_actions] do ## Bump the lockVersion and by that force a conflict. work_package.reload.touch - wp_page.click_custom_action("Escalate", expect_success: false) + wp_page.click_automation("Escalate", expect_success: false) wp_page.expect_conflict_error_banner end @@ -457,12 +457,12 @@ RSpec.describe "Custom actions", :js, with_ee: %i[custom_actions] do index_ca_page.expect_current_path index_ca_page.expect_listed("Current date") - date_action = CustomAction.last + date_action = Automation.last expect(date_action.actions.length).to eq(1) expect(date_action.conditions.length).to eq(0) index_ca_page.edit("Current date") - expect(page).to have_select("custom_action_actions_date", selected: "Current date") + expect(page).to have_select("automation_actions_date", selected: "Current date") end it "editing a status custom action (Regression #61888)" do @@ -480,12 +480,12 @@ RSpec.describe "Custom actions", :js, with_ee: %i[custom_actions] do index_ca_page.expect_current_path index_ca_page.expect_listed("Status") - date_action = CustomAction.last + date_action = Automation.last expect(date_action.actions.length).to eq(1) expect(date_action.actions.first.value_objects).to contain_exactly(label: "Closed", value: closed_status.id) edit_page = index_ca_page.edit("Status") - page.within "#custom-actions-form--actions" do + page.within "#automations-form--actions" do edit_page.expect_selected_option "Close" end end @@ -505,7 +505,7 @@ RSpec.describe "Custom actions", :js, with_ee: %i[custom_actions] do index_ca_page.expect_current_path index_ca_page.expect_listed("Unassign") - unassign = CustomAction.last + unassign = Automation.last expect(unassign.actions.length).to eq(1) expect(unassign.conditions.length).to eq(0) @@ -515,13 +515,13 @@ RSpec.describe "Custom actions", :js, with_ee: %i[custom_actions] do wp_page.ensure_page_loaded wait_for_network_idle - wp_page.expect_custom_action("Unassign") + wp_page.expect_automation("Unassign") # Stop sending ajax requests in order to test disabled fields upon submit wp_page.disable_ajax_requests - wp_page.click_custom_action("Unassign", expect_success: false) - wp_page.expect_custom_action_disabled("Unassign") + wp_page.click_automation("Unassign", expect_success: false) + wp_page.expect_automation_disabled("Unassign") find('[data-field-name="estimatedTime"]').click expect(page).to have_css("#wp-#{work_package.id}-inline-edit--field-estimatedTime[disabled]") end @@ -537,8 +537,8 @@ RSpec.describe "Custom actions", :js, with_ee: %i[custom_actions] do end before do - create(:custom_action, - actions: [CustomActions::Actions::AssignedTo.new(value: nil)], + create(:automation, + actions: [Automations::Actions::AssignedTo.new(value: nil)], name: "Unassign") end @@ -549,7 +549,7 @@ RSpec.describe "Custom actions", :js, with_ee: %i[custom_actions] do wp_page.ensure_page_loaded - wp_page.click_custom_action("Unassign", expect_success: true) + wp_page.click_automation("Unassign", expect_success: true) end end diff --git a/spec/helpers/no_results_helper_spec.rb b/spec/helpers/no_results_helper_spec.rb index 4fabb3c60ea..e743d2f746e 100644 --- a/spec/helpers/no_results_helper_spec.rb +++ b/spec/helpers/no_results_helper_spec.rb @@ -50,14 +50,13 @@ RSpec.describe NoResultsHelper do expect(no_results_box).to have_link "Add some foo", href: "/" end - it "contains title and content_link with custom text" do + it "contains title with custom text" do no_results_box = helper.no_results_box(action_url: root_path, display_action: true, - custom_title: "This is a different title about foo", - custom_action_text: "Link to nowhere") + custom_title: "This is a different title about foo") expect(no_results_box).to have_content "This is a different title about foo" - expect(no_results_box).to have_link "Link to nowhere", href: "/" + expect(no_results_box).to have_link "Add some foo", href: "/" end end end diff --git a/spec/lib/api/v3/custom_actions/custom_action_representer_generation_spec.rb b/spec/lib/api/v3/custom_actions/custom_action_representer_generation_spec.rb index af9baad6cb9..7e35d9ae6bf 100644 --- a/spec/lib/api/v3/custom_actions/custom_action_representer_generation_spec.rb +++ b/spec/lib/api/v3/custom_actions/custom_action_representer_generation_spec.rb @@ -33,11 +33,11 @@ require "spec_helper" RSpec.describe API::V3::CustomActions::CustomActionRepresenter do include API::V3::Utilities::PathHelper - let(:custom_action) { build_stubbed(:custom_action) } + let(:automation) { build_stubbed(:automation) } let(:user) { build_stubbed(:user) } let(:representer) do - described_class.new(custom_action, current_user: user, embed_links: true) + described_class.new(automation, current_user: user, embed_links: true) end subject { representer.to_json } @@ -51,13 +51,13 @@ RSpec.describe API::V3::CustomActions::CustomActionRepresenter do it "has a name property" do expect(subject) - .to be_json_eql(custom_action.name.to_json) + .to be_json_eql(automation.name.to_json) .at_path("name") end it "has a description property" do expect(subject) - .to be_json_eql(custom_action.description.to_json) + .to be_json_eql(automation.description.to_json) .at_path("description") end end @@ -65,14 +65,14 @@ RSpec.describe API::V3::CustomActions::CustomActionRepresenter do context "links" do it_behaves_like "has a titled link" do let(:link) { "self" } - let(:href) { api_v3_paths.custom_action(custom_action.id) } - let(:title) { custom_action.name } + let(:href) { api_v3_paths.custom_action(automation.id) } + let(:title) { automation.name } end it_behaves_like "has a titled link" do let(:link) { "executeImmediately" } - let(:href) { api_v3_paths.custom_action_execute(custom_action.id) } - let(:title) { "Execute #{custom_action.name}" } + let(:href) { api_v3_paths.custom_action_execute(automation.id) } + let(:title) { "Execute #{automation.name}" } let(:method) { "post" } end end diff --git a/spec/lib/api/v3/work_packages/eager_loading/custom_actions_integration_spec.rb b/spec/lib/api/v3/work_packages/eager_loading/automations_integration_spec.rb similarity index 70% rename from spec/lib/api/v3/work_packages/eager_loading/custom_actions_integration_spec.rb rename to spec/lib/api/v3/work_packages/eager_loading/automations_integration_spec.rb index c734e757a29..f8efd624753 100644 --- a/spec/lib/api/v3/work_packages/eager_loading/custom_actions_integration_spec.rb +++ b/spec/lib/api/v3/work_packages/eager_loading/automations_integration_spec.rb @@ -31,7 +31,7 @@ require "spec_helper" require_relative "eager_loading_mock_wrapper" -RSpec.describe API::V3::WorkPackages::EagerLoading::CustomAction do +RSpec.describe API::V3::WorkPackages::EagerLoading::Automation do let!(:work_package1) { create(:work_package) } let!(:work_package2) { create(:work_package) } let!(:user) do @@ -39,13 +39,13 @@ RSpec.describe API::V3::WorkPackages::EagerLoading::CustomAction do member_with_roles: { work_package2.project => role }) end let!(:role) { create(:project_role) } - let!(:status_custom_action) do - create(:custom_action, - conditions: [CustomActions::Conditions::Status.new(work_package1.status_id.to_s)]) + let!(:status_automation) do + create(:automation, + conditions: [Automations::Conditions::Status.new(work_package1.status_id.to_s)]) end - let!(:role_custom_action) do - create(:custom_action, - conditions: [CustomActions::Conditions::Role.new(role.id)]) + let!(:role_automation) do + create(:automation, + conditions: [Automations::Conditions::Role.new(role.id)]) end before do @@ -53,19 +53,19 @@ RSpec.describe API::V3::WorkPackages::EagerLoading::CustomAction do end describe ".apply" do - it "preloads the correct custom_actions" do + it "preloads the correct automations" do wrapped = EagerLoadingMockWrapper.wrap(described_class, [work_package1, work_package2]) expect(work_package1) - .not_to receive(:custom_actions) + .not_to receive(:automations) expect(work_package2) - .not_to receive(:custom_actions) + .not_to receive(:automations) - expect(wrapped.detect { |w| w.id == work_package1.id }.custom_actions(user)) - .to contain_exactly(status_custom_action) + expect(wrapped.detect { |w| w.id == work_package1.id }.automations(user)) + .to contain_exactly(status_automation) - expect(wrapped.detect { |w| w.id == work_package2.id }.custom_actions(user)) - .to contain_exactly(role_custom_action) + expect(wrapped.detect { |w| w.id == work_package2.id }.automations(user)) + .to contain_exactly(role_automation) end end end diff --git a/spec/lib/api/v3/work_packages/work_package_representer_spec.rb b/spec/lib/api/v3/work_packages/work_package_representer_spec.rb index 78c2251de5e..02c7aed5742 100644 --- a/spec/lib/api/v3/work_packages/work_package_representer_spec.rb +++ b/spec/lib/api/v3/work_packages/work_package_representer_spec.rb @@ -1381,16 +1381,16 @@ RSpec.describe API::V3::WorkPackages::WorkPackageRepresenter do describe "customActions" do it "has a collection of customActions" do - unassign_action = build_stubbed(:custom_action, - actions: [CustomActions::Actions::AssignedTo.new(value: nil)], + unassign_action = build_stubbed(:automation, + actions: [Automations::Actions::AssignedTo.new(value: nil)], name: "Unassign") allow(work_package) - .to receive(:custom_actions) + .to receive(:automations) .and_return([unassign_action]) expected = [ { - href: api_v3_paths.custom_action(unassign_action.id), + href: api_v3_paths.automation(unassign_action.id), title: unassign_action.name } ] @@ -1496,11 +1496,11 @@ RSpec.describe API::V3::WorkPackages::WorkPackageRepresenter do describe "customActions" do it "has an array of customActions" do - unassign_action = build_stubbed(:custom_action, - actions: [CustomActions::Actions::AssignedTo.new(value: nil)], + unassign_action = build_stubbed(:automation, + actions: [Automations::Actions::AssignedTo.new(value: nil)], name: "Unassign") allow(work_package) - .to receive(:custom_actions) + .to receive(:automations) .and_return([unassign_action]) expect(subject) diff --git a/spec/models/custom_action_spec.rb b/spec/models/automation_spec.rb similarity index 84% rename from spec/models/custom_action_spec.rb rename to spec/models/automation_spec.rb index de2e5fd39c2..534a207797b 100644 --- a/spec/models/custom_action_spec.rb +++ b/spec/models/automation_spec.rb @@ -30,10 +30,10 @@ require "spec_helper" -RSpec.describe CustomAction do - let(:stubbed_instance) { build_stubbed(:custom_action) } - let(:instance) { create(:custom_action, name: "zzzzzzzzz") } - let(:other_instance) { create(:custom_action, name: "aaaaa") } +RSpec.describe Automation do + let(:stubbed_instance) { build_stubbed(:automation) } + let(:instance) { create(:automation, name: "zzzzzzzzz") } + let(:other_instance) { create(:automation, name: "aaaaa") } describe "#name" do it "can be set and read" do @@ -95,18 +95,18 @@ RSpec.describe CustomAction do end it "can be set and read" do - stubbed_instance.actions = [CustomActions::Actions::AssignedTo.new(1)] + stubbed_instance.actions = [Automations::Actions::AssignedTo.new(1)] expect(stubbed_instance.actions.map { |a| [a.key, a.values] }) .to contain_exactly([:assigned_to, [1]]) end it "can be persisted" do - instance.actions = [CustomActions::Actions::AssignedTo.new(1)] + instance.actions = [Automations::Actions::AssignedTo.new(1)] instance.save! - expect(CustomAction.find(instance.id).actions.map { |a| [a.key, a.values] }) + expect(Automation.find(instance.id).actions.map { |a| [a.key, a.values] }) .to contain_exactly([:assigned_to, [1]]) end end @@ -118,7 +118,7 @@ RSpec.describe CustomAction do end it "returns the activated actions with their selected value and all other with the default value" do - stubbed_instance.actions = [CustomActions::Actions::AssignedTo.new(1)] + stubbed_instance.actions = [Automations::Actions::AssignedTo.new(1)] expect(stubbed_instance.all_actions.map { |a| [a.key, a.values] }) .to include([:assigned_to, [1]], [:status, []]) @@ -149,25 +149,25 @@ RSpec.describe CustomAction do end it "can be set and read" do - stubbed_instance.conditions = [CustomActions::Conditions::Status.new(status.id), - CustomActions::Conditions::Role.new(role.id)] + stubbed_instance.conditions = [Automations::Conditions::Status.new(status.id), + Automations::Conditions::Role.new(role.id)] expect(stubbed_instance.conditions.map { |a| [a.key, a.values] }) .to contain_exactly([:status, [status.id]], [:role, [role.id]]) end it "can be persisted" do - instance.conditions = [CustomActions::Conditions::Status.new(status.id), - CustomActions::Conditions::Role.new(role.id)] + instance.conditions = [Automations::Conditions::Status.new(status.id), + Automations::Conditions::Role.new(role.id)] instance.save! - expect(CustomAction.find(instance.id).conditions.map { |a| [a.key, a.values] }) + expect(Automation.find(instance.id).conditions.map { |a| [a.key, a.values] }) .to contain_exactly([:status, [status.id]], [:role, [role.id]]) end it "existing permissions can be removed" do - instance.conditions = [CustomActions::Conditions::Project.new(project.id)] + instance.conditions = [Automations::Conditions::Project.new(project.id)] instance.save! @@ -175,7 +175,7 @@ RSpec.describe CustomAction do instance.save! - expect(CustomAction.find(instance.id).conditions.map { |a| [a.key, a.values] }) + expect(Automation.find(instance.id).conditions.map { |a| [a.key, a.values] }) .to be_empty end end diff --git a/spec/models/custom_actions/actions/assigned_to_spec.rb b/spec/models/automations/actions/assigned_to_spec.rb similarity index 91% rename from spec/models/custom_actions/actions/assigned_to_spec.rb rename to spec/models/automations/actions/assigned_to_spec.rb index 3b6591f2ed3..8b1d35df1c9 100644 --- a/spec/models/custom_actions/actions/assigned_to_spec.rb +++ b/spec/models/automations/actions/assigned_to_spec.rb @@ -30,7 +30,7 @@ require "spec_helper" require_relative "../shared_expectations" -RSpec.describe CustomActions::Actions::AssignedTo do +RSpec.describe Automations::Actions::AssignedTo do let(:key) { :assigned_to } let(:type) { :user } let(:allowed_values) do @@ -74,7 +74,7 @@ RSpec.describe CustomActions::Actions::AssignedTo do it "includes the value in available_values" do expect(subject.associated) - .to include([subject.current_user_value_key, I18n.t("custom_actions.actions.assigned_to.executing_user_value")]) + .to include([subject.current_user_value_key, I18n.t("automations.actions.assigned_to.executing_user_value")]) end context "when logged in" do @@ -88,7 +88,7 @@ RSpec.describe CustomActions::Actions::AssignedTo do end it "validates the me value when executing" do - errors = ActiveModel::Errors.new(CustomAction.new) + errors = ActiveModel::Errors.new(Automation.new) subject.validate errors expect(errors.symbols_for(:actions)).to be_empty end @@ -101,7 +101,7 @@ RSpec.describe CustomActions::Actions::AssignedTo do end it "validates the me value when executing" do - errors = ActiveModel::Errors.new(CustomAction.new) + errors = ActiveModel::Errors.new(Automation.new) subject.validate errors expect(errors.symbols_for(:actions)).to include :not_logged_in end diff --git a/spec/models/custom_actions/actions/custom_field_spec.rb b/spec/models/automations/actions/custom_field_spec.rb similarity index 98% rename from spec/models/custom_actions/actions/custom_field_spec.rb rename to spec/models/automations/actions/custom_field_spec.rb index 4cd0f6f459a..1960ac7fbd9 100644 --- a/spec/models/custom_actions/actions/custom_field_spec.rb +++ b/spec/models/automations/actions/custom_field_spec.rb @@ -30,7 +30,7 @@ require "spec_helper" require_relative "../shared_expectations" -RSpec.describe CustomActions::Actions::CustomField do +RSpec.describe Automations::Actions::CustomField do let(:scope) { instance_double(ActiveRecord::Relation) } let(:list_custom_field) do build_stubbed(:list_wp_custom_field, @@ -101,7 +101,7 @@ RSpec.describe CustomActions::Actions::CustomField do describe ".all" do before do allow(WorkPackageCustomField) - .to receive(:usable_as_custom_action) + .to receive(:usable_as_automation) .and_return(custom_fields) end @@ -282,7 +282,7 @@ RSpec.describe CustomActions::Actions::CustomField do 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")]) + .to include([instance.current_user_value_key, I18n.t("automations.actions.assigned_to.executing_user_value")]) end context "when logged in" do @@ -296,7 +296,7 @@ RSpec.describe CustomActions::Actions::CustomField do end it "validates the me value when executing" do - errors = ActiveModel::Errors.new(CustomAction.new) + errors = ActiveModel::Errors.new(Automation.new) instance.validate errors expect(errors.symbols_for(:actions)).to be_empty end @@ -313,7 +313,7 @@ RSpec.describe CustomActions::Actions::CustomField do end it "validates the me value when executing" do - errors = ActiveModel::Errors.new(CustomAction.new) + errors = ActiveModel::Errors.new(Automation.new) instance.validate errors expect(errors.symbols_for(:actions)).to include :not_logged_in end diff --git a/spec/models/custom_actions/actions/date_spec.rb b/spec/models/automations/actions/date_spec.rb similarity index 97% rename from spec/models/custom_actions/actions/date_spec.rb rename to spec/models/automations/actions/date_spec.rb index 1c3d2fce08e..402464d09b8 100644 --- a/spec/models/custom_actions/actions/date_spec.rb +++ b/spec/models/automations/actions/date_spec.rb @@ -31,7 +31,7 @@ require "spec_helper" require_relative "../shared_expectations" -RSpec.describe CustomActions::Actions::Date do +RSpec.describe Automations::Actions::Date do let(:key) { :date } let(:type) { :date_property } let(:value) { Date.today } diff --git a/spec/models/custom_actions/actions/done_ratio_spec.rb b/spec/models/automations/actions/done_ratio_spec.rb similarity index 96% rename from spec/models/custom_actions/actions/done_ratio_spec.rb rename to spec/models/automations/actions/done_ratio_spec.rb index 92970bcca52..5c54d646323 100644 --- a/spec/models/custom_actions/actions/done_ratio_spec.rb +++ b/spec/models/automations/actions/done_ratio_spec.rb @@ -31,7 +31,7 @@ require "spec_helper" require_relative "../shared_expectations" -RSpec.describe CustomActions::Actions::DoneRatio do +RSpec.describe Automations::Actions::DoneRatio do let(:key) { :done_ratio } let(:type) { :integer_property } @@ -58,7 +58,7 @@ RSpec.describe CustomActions::Actions::DoneRatio do describe "validate" do let(:errors) do - build_stubbed(:custom_action).errors + build_stubbed(:automation).errors end it "is valid for values between 0 and 100" do diff --git a/spec/models/custom_actions/actions/due_date_spec.rb b/spec/models/automations/actions/due_date_spec.rb similarity index 97% rename from spec/models/custom_actions/actions/due_date_spec.rb rename to spec/models/automations/actions/due_date_spec.rb index 7dccff52c77..c61f82324ba 100644 --- a/spec/models/custom_actions/actions/due_date_spec.rb +++ b/spec/models/automations/actions/due_date_spec.rb @@ -31,7 +31,7 @@ require "spec_helper" require_relative "../shared_expectations" -RSpec.describe CustomActions::Actions::DueDate do +RSpec.describe Automations::Actions::DueDate do let(:key) { :due_date } let(:type) { :date_property } let(:value) { Date.today } diff --git a/spec/models/custom_actions/actions/estimated_hours_spec.rb b/spec/models/automations/actions/estimated_hours_spec.rb similarity index 95% rename from spec/models/custom_actions/actions/estimated_hours_spec.rb rename to spec/models/automations/actions/estimated_hours_spec.rb index e2a5fe3a50d..cc5bdd0ee0f 100644 --- a/spec/models/custom_actions/actions/estimated_hours_spec.rb +++ b/spec/models/automations/actions/estimated_hours_spec.rb @@ -31,7 +31,7 @@ require "spec_helper" require_relative "../shared_expectations" -RSpec.describe CustomActions::Actions::EstimatedHours do +RSpec.describe Automations::Actions::EstimatedHours do let(:key) { :estimated_hours } let(:type) { :float_property } let(:value) { 1.0 } @@ -59,7 +59,7 @@ RSpec.describe CustomActions::Actions::EstimatedHours do describe "validate" do let(:errors) do - build_stubbed(:custom_action).errors + build_stubbed(:automation).errors end it "is valid for values equal to or greater than 0" do diff --git a/spec/models/custom_actions/actions/notify_spec.rb b/spec/models/automations/actions/notify_spec.rb similarity index 98% rename from spec/models/custom_actions/actions/notify_spec.rb rename to spec/models/automations/actions/notify_spec.rb index 00fa45e5223..9bac76f31af 100644 --- a/spec/models/custom_actions/actions/notify_spec.rb +++ b/spec/models/automations/actions/notify_spec.rb @@ -30,7 +30,7 @@ require "spec_helper" require_relative "../shared_expectations" -RSpec.describe CustomActions::Actions::Notify do +RSpec.describe Automations::Actions::Notify do let(:key) { :notify } let(:type) { :associated_property } let(:allowed_values) do diff --git a/spec/models/custom_actions/actions/priority_spec.rb b/spec/models/automations/actions/priority_spec.rb similarity index 97% rename from spec/models/custom_actions/actions/priority_spec.rb rename to spec/models/automations/actions/priority_spec.rb index 331339555be..6fa0a671b72 100644 --- a/spec/models/custom_actions/actions/priority_spec.rb +++ b/spec/models/automations/actions/priority_spec.rb @@ -30,7 +30,7 @@ require "spec_helper" require_relative "../shared_expectations" -RSpec.describe CustomActions::Actions::Priority do +RSpec.describe Automations::Actions::Priority do let(:key) { :priority } let(:type) { :associated_property } let(:allowed_values) do diff --git a/spec/models/custom_actions/actions/project_spec.rb b/spec/models/automations/actions/project_spec.rb similarity index 97% rename from spec/models/custom_actions/actions/project_spec.rb rename to spec/models/automations/actions/project_spec.rb index 1296f097347..73865455446 100644 --- a/spec/models/custom_actions/actions/project_spec.rb +++ b/spec/models/automations/actions/project_spec.rb @@ -30,7 +30,7 @@ require "spec_helper" require_relative "../shared_expectations" -RSpec.describe CustomActions::Actions::Project do +RSpec.describe Automations::Actions::Project do let(:key) { :project } let(:type) { :project } let(:priority) { 10 } diff --git a/spec/models/custom_actions/actions/responsible_spec.rb b/spec/models/automations/actions/responsible_spec.rb similarity index 91% rename from spec/models/custom_actions/actions/responsible_spec.rb rename to spec/models/automations/actions/responsible_spec.rb index 3321f569c20..9580dcd8415 100644 --- a/spec/models/custom_actions/actions/responsible_spec.rb +++ b/spec/models/automations/actions/responsible_spec.rb @@ -30,7 +30,7 @@ require "spec_helper" require_relative "../shared_expectations" -RSpec.describe CustomActions::Actions::Responsible do +RSpec.describe Automations::Actions::Responsible do let(:key) { :responsible } let(:type) { :user } let(:allowed_values) do @@ -75,7 +75,7 @@ RSpec.describe CustomActions::Actions::Responsible do it "includes the value in available_values" do expect(subject.associated) - .to include([subject.current_user_value_key, I18n.t("custom_actions.actions.assigned_to.executing_user_value")]) + .to include([subject.current_user_value_key, I18n.t("automations.actions.assigned_to.executing_user_value")]) end context "when logged in" do @@ -89,7 +89,7 @@ RSpec.describe CustomActions::Actions::Responsible do end it "validates the me value when executing" do - errors = ActiveModel::Errors.new(CustomAction.new) + errors = ActiveModel::Errors.new(Automation.new) subject.validate errors expect(errors.symbols_for(:actions)).to be_empty end @@ -102,7 +102,7 @@ RSpec.describe CustomActions::Actions::Responsible do end it "validates the me value when executing" do - errors = ActiveModel::Errors.new(CustomAction.new) + errors = ActiveModel::Errors.new(Automation.new) subject.validate errors expect(errors.symbols_for(:actions)).to include :not_logged_in end diff --git a/spec/models/custom_actions/actions/start_date_spec.rb b/spec/models/automations/actions/start_date_spec.rb similarity index 97% rename from spec/models/custom_actions/actions/start_date_spec.rb rename to spec/models/automations/actions/start_date_spec.rb index 6e01824aff4..f24b07eb33b 100644 --- a/spec/models/custom_actions/actions/start_date_spec.rb +++ b/spec/models/automations/actions/start_date_spec.rb @@ -31,7 +31,7 @@ require "spec_helper" require_relative "../shared_expectations" -RSpec.describe CustomActions::Actions::StartDate do +RSpec.describe Automations::Actions::StartDate do let(:key) { :start_date } let(:type) { :date_property } let(:value) { Date.today } diff --git a/spec/models/custom_actions/actions/status_spec.rb b/spec/models/automations/actions/status_spec.rb similarity index 97% rename from spec/models/custom_actions/actions/status_spec.rb rename to spec/models/automations/actions/status_spec.rb index c3ffb1cde5b..4f736fa4c25 100644 --- a/spec/models/custom_actions/actions/status_spec.rb +++ b/spec/models/automations/actions/status_spec.rb @@ -30,7 +30,7 @@ require "spec_helper" require_relative "../shared_expectations" -RSpec.describe CustomActions::Actions::Status do +RSpec.describe Automations::Actions::Status do let(:key) { :status } let(:type) { :associated_property } let(:allowed_values) do diff --git a/spec/models/custom_actions/actions/strategies/user_custom_field_spec.rb b/spec/models/automations/actions/strategies/user_custom_field_spec.rb similarity index 88% rename from spec/models/custom_actions/actions/strategies/user_custom_field_spec.rb rename to spec/models/automations/actions/strategies/user_custom_field_spec.rb index 6e333934a79..52eb3addc17 100644 --- a/spec/models/custom_actions/actions/strategies/user_custom_field_spec.rb +++ b/spec/models/automations/actions/strategies/user_custom_field_spec.rb @@ -5,7 +5,7 @@ require "spec_helper" -module CustomActions +module Automations module Actions module Strategies RSpec.describe UserCustomField do @@ -31,13 +31,13 @@ module CustomActions end end - let(:user_cf_action) { CustomActions::Actions::CustomField.for("custom_field_#{user_cf.id}").new } - let(:multi_user_cf_action) { CustomActions::Actions::CustomField.for("custom_field_#{multi_user_cf.id}").new } + let(:user_cf_action) { Automations::Actions::CustomField.for("custom_field_#{user_cf.id}").new } + let(:multi_user_cf_action) { Automations::Actions::CustomField.for("custom_field_#{multi_user_cf.id}").new } let(:single_user_work_package) { create(:work_package, project: single_user_project) } let(:multi_user_work_package) { create(:work_package, project: multi_user_project) } - let(:custom_action) do - create(:custom_action, + let(:automation) do + create(:automation, type_conditions: [single_user_work_package.type], project_conditions: [single_user_project], actions: [user_cf_action, multi_user_cf_action]) @@ -49,12 +49,12 @@ module CustomActions before do user_cf_action.values = users[4].id multi_user_cf_action.values = users[1..3].map(&:id) - custom_action.save + automation.save end it "fails with an error" do login_as user - result = UpdateWorkPackageService.new(user:, action: custom_action) + result = UpdateWorkPackageService.new(user:, action: automation) .call(work_package: single_user_work_package) expect(result).to be_failure @@ -70,29 +70,29 @@ module CustomActions before do user_cf_action.values = user.id multi_user_cf_action.values = users[0..3].map(&:id) - custom_action.save + automation.save login_as user end it "succeeds" do - result = UpdateWorkPackageService.new(user:, action: custom_action).call(work_package: single_user_work_package) + result = UpdateWorkPackageService.new(user:, action: automation).call(work_package: single_user_work_package) expect(result).to be_success - multi_user_result = UpdateWorkPackageService.new(user:, action: custom_action) + multi_user_result = UpdateWorkPackageService.new(user:, action: automation) .call(work_package: multi_user_work_package) expect(multi_user_result).to be_success end it "saves the custom field values on the work package" do - result = UpdateWorkPackageService.new(user:, action: custom_action) + result = UpdateWorkPackageService.new(user:, action: automation) .call(work_package: single_user_work_package) updated = result.result.reload expect(updated.send("custom_field_#{user_cf.id}")).to eq(user) expect(updated.send("custom_field_#{multi_user_cf.id}")).to eq([user]) - muti_user_result = UpdateWorkPackageService.new(user:, action: custom_action) + muti_user_result = UpdateWorkPackageService.new(user:, action: automation) .call(work_package: multi_user_work_package) updated = muti_user_result.result.reload @@ -105,12 +105,12 @@ module CustomActions before do user_cf_action.values = "current_user" multi_user_cf_action.values = ["current_user"] + users[1..3].map(&:id) - custom_action.save + automation.save login_as user end it "assigns the current user to the custom field" do - result = UpdateWorkPackageService.new(user:, action: custom_action) + result = UpdateWorkPackageService.new(user:, action: automation) .call(work_package: single_user_work_package) updated = result.result.reload diff --git a/spec/models/custom_actions/actions/type_spec.rb b/spec/models/automations/actions/type_spec.rb similarity index 97% rename from spec/models/custom_actions/actions/type_spec.rb rename to spec/models/automations/actions/type_spec.rb index eafd77c5b15..9821ab3965f 100644 --- a/spec/models/custom_actions/actions/type_spec.rb +++ b/spec/models/automations/actions/type_spec.rb @@ -30,7 +30,7 @@ require "spec_helper" require_relative "../shared_expectations" -RSpec.describe CustomActions::Actions::Type do +RSpec.describe Automations::Actions::Type do let(:key) { :type } let(:priority) { 20 } let(:type) { :associated_property } diff --git a/spec/models/custom_actions/conditions/project_spec.rb b/spec/models/automations/conditions/project_spec.rb similarity index 97% rename from spec/models/custom_actions/conditions/project_spec.rb rename to spec/models/automations/conditions/project_spec.rb index 81dd706b9d7..4b77b39c0fa 100644 --- a/spec/models/custom_actions/conditions/project_spec.rb +++ b/spec/models/automations/conditions/project_spec.rb @@ -30,7 +30,7 @@ require "spec_helper" require_relative "../shared_expectations" -RSpec.describe CustomActions::Conditions::Project do +RSpec.describe Automations::Conditions::Project do it_behaves_like "associated custom condition" do let(:key) { :project } diff --git a/spec/models/custom_actions/conditions/role_spec.rb b/spec/models/automations/conditions/role_spec.rb similarity index 98% rename from spec/models/custom_actions/conditions/role_spec.rb rename to spec/models/automations/conditions/role_spec.rb index 51d52eced8b..48ee857a3e1 100644 --- a/spec/models/custom_actions/conditions/role_spec.rb +++ b/spec/models/automations/conditions/role_spec.rb @@ -30,7 +30,7 @@ require "spec_helper" require_relative "../shared_expectations" -RSpec.describe CustomActions::Conditions::Role do +RSpec.describe Automations::Conditions::Role do it_behaves_like "associated custom condition" do let(:key) { :role } diff --git a/spec/models/custom_actions/conditions/status_spec.rb b/spec/models/automations/conditions/status_spec.rb similarity index 98% rename from spec/models/custom_actions/conditions/status_spec.rb rename to spec/models/automations/conditions/status_spec.rb index a2620eb2afc..47a5f6bcf33 100644 --- a/spec/models/custom_actions/conditions/status_spec.rb +++ b/spec/models/automations/conditions/status_spec.rb @@ -30,7 +30,7 @@ require "spec_helper" require_relative "../shared_expectations" -RSpec.describe CustomActions::Conditions::Status do +RSpec.describe Automations::Conditions::Status do it_behaves_like "associated custom condition" do let(:key) { :status } diff --git a/spec/models/custom_actions/conditions/type_spec.rb b/spec/models/automations/conditions/type_spec.rb similarity index 98% rename from spec/models/custom_actions/conditions/type_spec.rb rename to spec/models/automations/conditions/type_spec.rb index f7f6f857a45..ebbeffe1558 100644 --- a/spec/models/custom_actions/conditions/type_spec.rb +++ b/spec/models/automations/conditions/type_spec.rb @@ -30,7 +30,7 @@ require "spec_helper" require_relative "../shared_expectations" -RSpec.describe CustomActions::Conditions::Type do +RSpec.describe Automations::Conditions::Type do it_behaves_like "associated custom condition" do let(:key) { :type } diff --git a/spec/models/custom_actions/shared_expectations.rb b/spec/models/automations/shared_expectations.rb similarity index 97% rename from spec/models/custom_actions/shared_expectations.rb rename to spec/models/automations/shared_expectations.rb index 59b35e15859..26a68158699 100644 --- a/spec/models/custom_actions/shared_expectations.rb +++ b/spec/models/automations/shared_expectations.rb @@ -156,7 +156,7 @@ end RSpec.shared_examples_for "associated custom action validations" do describe "#validate" do let(:errors) do - build_stubbed(:custom_action).errors + build_stubbed(:automation).errors end it "adds an error on actions if values is blank (depending on required?)" do @@ -203,7 +203,7 @@ end RSpec.shared_examples_for "bool custom action validations" do describe "#validate" do let(:errors) do - build_stubbed(:custom_action).errors + build_stubbed(:automation).errors end it "adds an error on actions if values is blank (depending on required?)" do @@ -248,7 +248,7 @@ end RSpec.shared_examples_for "int custom action validations" do describe "#validate" do let(:errors) do - build_stubbed(:custom_action).errors + build_stubbed(:automation).errors end it "adds an error on actions if values is blank (depending on required?)" do @@ -284,7 +284,7 @@ end RSpec.shared_examples_for "float custom action validations" do describe "#validate" do let(:errors) do - build_stubbed(:custom_action).errors + build_stubbed(:automation).errors end it "adds an error on actions if values is blank (depending on required?)" do @@ -320,7 +320,7 @@ end RSpec.shared_examples_for "string custom action validations" do describe "#validate" do let(:errors) do - build_stubbed(:custom_action).errors + build_stubbed(:automation).errors end it "adds an error on actions if values is blank (depending on required?)" do @@ -361,7 +361,7 @@ RSpec.shared_examples_for "link custom action validations" do it_behaves_like "string custom action validations" let(:errors) do - build_stubbed(:custom_action).errors + build_stubbed(:automation).errors end it "adds an error on actions if value an invalid url" do @@ -377,7 +377,7 @@ end RSpec.shared_examples_for "date custom action validations" do describe "#validate" do let(:errors) do - build_stubbed(:custom_action).errors + build_stubbed(:automation).errors end it "adds an error on actions if values is blank (depending on required?)" do @@ -522,7 +522,7 @@ RSpec.shared_examples_for "associated custom condition" do describe "#validate" do let(:errors) do - build_stubbed(:custom_action).errors + build_stubbed(:automation).errors end it "adds an error on conditions if values not from list of allowed values" do diff --git a/spec/models/permitted_params_spec.rb b/spec/models/permitted_params_spec.rb index 2e1f3b52145..9c8eaa249b7 100644 --- a/spec/models/permitted_params_spec.rb +++ b/spec/models/permitted_params_spec.rb @@ -440,8 +440,8 @@ RSpec.describe PermittedParams do it_behaves_like "allows params" end - describe "#custom_action" do - let(:attribute) { :custom_action } + describe "#automation" do + let(:attribute) { :automation } let(:hash) do { "name" => "blubs", diff --git a/spec/models/work_package/work_package_custom_actions_spec.rb b/spec/models/work_package/work_package_automations_spec.rb similarity index 80% rename from spec/models/work_package/work_package_custom_actions_spec.rb rename to spec/models/work_package/work_package_automations_spec.rb index e21bde3671d..a1dc92f096f 100644 --- a/spec/models/work_package/work_package_custom_actions_spec.rb +++ b/spec/models/work_package/work_package_automations_spec.rb @@ -30,7 +30,7 @@ require "spec_helper" -RSpec.describe WorkPackage, "custom_actions" do +RSpec.describe WorkPackage, "automations" do let(:work_package) do build_stubbed(:work_package, project:) @@ -46,18 +46,18 @@ RSpec.describe WorkPackage, "custom_actions" do create(:project_role) end let(:conditions) do - [CustomActions::Conditions::Status.new([status.id])] + [Automations::Conditions::Status.new([status.id])] end - let!(:custom_action) do - action = build(:custom_action) + let!(:automation) do + action = build(:automation) action.conditions = conditions action.save! action end - describe "#custom_actions" do + describe "#automations" do context "with the custom action having no restriction" do let(:conditions) do [] @@ -68,8 +68,8 @@ RSpec.describe WorkPackage, "custom_actions" do end it "returns the action" do - expect(work_package.custom_actions(user)) - .to contain_exactly(custom_action) + expect(work_package.automations(user)) + .to contain_exactly(automation) end end @@ -80,8 +80,8 @@ RSpec.describe WorkPackage, "custom_actions" do end it "returns the action" do - expect(work_package.custom_actions(user)) - .to contain_exactly(custom_action) + expect(work_package.automations(user)) + .to contain_exactly(automation) end end @@ -91,7 +91,7 @@ RSpec.describe WorkPackage, "custom_actions" do end it "does not return the action" do - expect(work_package.custom_actions(user)) + expect(work_package.automations(user)) .to be_empty end end @@ -99,13 +99,13 @@ RSpec.describe WorkPackage, "custom_actions" do context "with a role restriction" do let(:conditions) do - [CustomActions::Conditions::Role.new(role.id)] + [Automations::Conditions::Role.new(role.id)] end context "with the user having the same role" do it "returns the action" do - expect(work_package.custom_actions(user)) - .to contain_exactly(custom_action) + expect(work_package.automations(user)) + .to contain_exactly(automation) end end @@ -113,11 +113,11 @@ RSpec.describe WorkPackage, "custom_actions" do let(:other_role) { create(:project_role) } let(:conditions) do - [CustomActions::Conditions::Role.new(other_role.id)] + [Automations::Conditions::Role.new(other_role.id)] end it "does not return the action" do - expect(work_package.custom_actions(user)) + expect(work_package.automations(user)) .to be_empty end end diff --git a/spec/requests/api/v3/custom_actions/custom_actions_api_spec.rb b/spec/requests/api/v3/custom_actions/custom_actions_api_spec.rb index 985a434d113..f42ac68ad2d 100644 --- a/spec/requests/api/v3/custom_actions/custom_actions_api_spec.rb +++ b/spec/requests/api/v3/custom_actions/custom_actions_api_spec.rb @@ -48,7 +48,7 @@ RSpec.describe "API::V3::CustomActions::CustomActionsAPI" do create(:user, member_with_roles: { project => role }) end let(:action) do - create(:custom_action, actions: [CustomActions::Actions::AssignedTo.new(nil)]) + create(:automation_with_manual_trigger, actions: [Automations::Actions::AssignedTo.new(nil)]) end let(:parameters) do { @@ -92,6 +92,22 @@ RSpec.describe "API::V3::CustomActions::CustomActionsAPI" do end end + context "for an automation without manual trigger" do + let(:action) do + create(:automation_with_manual_trigger, actions: [Automations::Actions::AssignedTo.new(nil)]).tap do |automation| + automation.triggers.first.update_column(:type, "Automations::Triggers::Base") + end + end + + before do + get api_v3_paths.custom_action(action.id) + end + + it "is a 404 NOT FOUND" do + expect(last_response.status).to be(404) + end + end + context "when lacking permissions" do let(:user) { create(:user) } @@ -212,9 +228,9 @@ RSpec.describe "API::V3::CustomActions::CustomActionsAPI" do context "when conditions are not fulfilled for the user" do let(:admin_role) { create(:project_role) } let(:action) do - create(:custom_action, - actions: [CustomActions::Actions::AssignedTo.new(nil)], - conditions: [CustomActions::Conditions::Role.new(admin_role.id)]) + create(:automation, + actions: [Automations::Actions::AssignedTo.new(nil)], + conditions: [Automations::Conditions::Role.new(admin_role.id)]) end include_context "post request" diff --git a/spec/routing/custom_actions_spec.rb b/spec/routing/automations_spec.rb similarity index 61% rename from spec/routing/custom_actions_spec.rb rename to spec/routing/automations_spec.rb index 96a99d11c48..04b4bc96958 100644 --- a/spec/routing/custom_actions_spec.rb +++ b/spec/routing/automations_spec.rb @@ -30,46 +30,46 @@ require "spec_helper" -RSpec.describe "custom_actions routes" do +RSpec.describe "automations routes" do describe "index" do - it "links GET /admin/custom_actions" do - expect(get("/admin/custom_actions")) - .to route_to("custom_actions#index") + it "links GET /admin/automations" do + expect(get("/admin/automations")) + .to route_to("automations#index") end end describe "new" do - it "links GET /admin/custom_actions/new" do - expect(get("/admin/custom_actions/new")) - .to route_to("custom_actions#new") + it "links GET /admin/automations/new" do + expect(get("/admin/automations/new")) + .to route_to("automations#new") end end describe "create" do - it "links POST /admin/custom_actions" do - expect(post("/admin/custom_actions")) - .to route_to("custom_actions#create") + it "links POST /admin/automations" do + expect(post("/admin/automations")) + .to route_to("automations#create") end end describe "edit" do - it "links GET /admin/custom_actions/:id/edit" do - expect(get("/admin/custom_actions/42/edit")) - .to route_to("custom_actions#edit", id: "42") + it "links GET /admin/automations/:id/edit" do + expect(get("/admin/automations/42/edit")) + .to route_to("automations#edit", id: "42") end end describe "update" do - it "links PATCH /admin/custom_actions/:id" do - expect(patch("/admin/custom_actions/42")) - .to route_to("custom_actions#update", id: "42") + it "links PATCH /admin/automations/:id" do + expect(patch("/admin/automations/42")) + .to route_to("automations#update", id: "42") end end describe "delete" do - it "links DELETE /admin/custom_actions/:id" do - expect(delete("/admin/custom_actions/42")) - .to route_to("custom_actions#destroy", id: "42") + it "links DELETE /admin/automations/:id" do + expect(delete("/admin/automations/42")) + .to route_to("automations#destroy", id: "42") end end end diff --git a/spec/services/custom_actions/update_service_spec.rb b/spec/services/automations/update_service_spec.rb similarity index 93% rename from spec/services/custom_actions/update_service_spec.rb rename to spec/services/automations/update_service_spec.rb index d9d6e02de57..7f3073c1729 100644 --- a/spec/services/custom_actions/update_service_spec.rb +++ b/spec/services/automations/update_service_spec.rb @@ -30,9 +30,9 @@ require "spec_helper" -RSpec.describe CustomActions::UpdateService do +RSpec.describe Automations::UpdateService do let(:action) do - action = build_stubbed(:custom_action) + action = build_stubbed(:automation) allow(action) .to receive(:save) @@ -51,7 +51,7 @@ RSpec.describe CustomActions::UpdateService do let(:contract) do contract_instance = double("contract instance") - allow(CustomActions::CuContract) + allow(Automations::CuContract) .to receive(:new) .with(action) .and_return(contract_instance) @@ -130,8 +130,8 @@ RSpec.describe CustomActions::UpdateService do end it "updates the actions" do - action.actions = [CustomActions::Actions::AssignedTo.new("1"), - CustomActions::Actions::Status.new("3")] + action.actions = [Automations::Actions::AssignedTo.new("1"), + Automations::Actions::Status.new("3")] new_actions = instance .call(attributes: { actions: { assigned_to: ["2"], priority: ["3"] } }) @@ -158,7 +158,7 @@ RSpec.describe CustomActions::UpdateService do old_status = create(:status) new_status = create(:status) - action.conditions = [CustomActions::Conditions::Status.new(old_status.id)] + action.conditions = [Automations::Conditions::Status.new(old_status.id)] new_conditions = instance .call(attributes: { conditions: { status: new_status.id } }) diff --git a/spec/services/custom_actions/update_work_package_service_spec.rb b/spec/services/automations/update_work_package_service_spec.rb similarity index 96% rename from spec/services/custom_actions/update_work_package_service_spec.rb rename to spec/services/automations/update_work_package_service_spec.rb index b62d97d16f8..2b243fe6dfa 100644 --- a/spec/services/custom_actions/update_work_package_service_spec.rb +++ b/spec/services/automations/update_work_package_service_spec.rb @@ -30,9 +30,9 @@ require "spec_helper" -RSpec.describe CustomActions::UpdateWorkPackageService do - let(:custom_action) do - action = build_stubbed(:custom_action) +RSpec.describe Automations::UpdateWorkPackageService do + let(:automation) do + action = build_stubbed(:automation) allow(action) .to receive(:actions) @@ -63,7 +63,7 @@ RSpec.describe CustomActions::UpdateWorkPackageService do action end let(:user) { build_stubbed(:user) } - let(:instance) { described_class.new(action: custom_action, user:) } + let(:instance) { described_class.new(action: automation, user:) } let(:update_service_call_implementation) do -> do result diff --git a/spec/support/pages/admin/custom_actions/edit.rb b/spec/support/pages/admin/automations/edit.rb similarity index 91% rename from spec/support/pages/admin/custom_actions/edit.rb rename to spec/support/pages/admin/automations/edit.rb index ae2eb585433..e2388c7f387 100644 --- a/spec/support/pages/admin/custom_actions/edit.rb +++ b/spec/support/pages/admin/automations/edit.rb @@ -34,16 +34,16 @@ require_relative "form" module Pages module Admin - module CustomActions + module Automations class Edit < Form - attr_reader :custom_action + attr_reader :automation def initialize(action) - @custom_action = action + @automation = action end def path - edit_custom_action_path(id: custom_action.id) + edit_automation_path(id: automation.id) end def save diff --git a/spec/support/pages/admin/custom_actions/form.rb b/spec/support/pages/admin/automations/form.rb similarity index 88% rename from spec/support/pages/admin/custom_actions/form.rb rename to spec/support/pages/admin/automations/form.rb index f2fef873c84..4365d823966 100644 --- a/spec/support/pages/admin/custom_actions/form.rb +++ b/spec/support/pages/admin/automations/form.rb @@ -33,7 +33,7 @@ require "support/components/autocompleter/ng_select_autocomplete_helpers" module Pages module Admin - module CustomActions + module Automations class Form < ::Pages::Page include ::Components::Autocompleter::NgSelectAutocompleteHelpers @@ -48,7 +48,7 @@ module Pages def add_action(name, value) retry_block do select name, from: "Add action" - within "#custom-actions-form--active-actions" do + within "#automations-form--active-actions" do expect(page).to have_css(".form--label", text: name) end end @@ -57,7 +57,7 @@ module Pages end def remove_action(name) - within "#custom-actions-form--active-actions" do + within "#automations-form--active-actions" do find(".form--field", text: name) .click_on accessible_name: "Close" end @@ -70,11 +70,11 @@ module Pages def expect_action(name, value) value = "" if value.nil? - within "#custom-actions-form--actions" do + within "#automations-form--actions" do if value.is_a?(Array) value.each { |name| expect_selected_option(name.to_s) } else - element = find("input[name='custom_action[actions][#{name}]']", visible: :all) + element = find("input[name='automation[actions][#{name}]']", visible: :all) expect(element.value).to eq value.to_s end end @@ -91,7 +91,7 @@ module Pages retry_block do set_condition_value(name, val) - within "#custom-actions-form--conditions" do + within "#automations-form--conditions" do expect_selected_option val end end @@ -101,13 +101,13 @@ module Pages private def set_action_value(name, value) - field = find("#custom-actions-form--active-actions .form--field", text: name, wait: 5) + field = find("#automations-form--active-actions .form--field", text: name, wait: 5) set_field_value(field, name, value) end def set_condition_value(name, value) - field = find("#custom-actions-form--conditions .form--field", text: name, wait: 5) + field = find("#automations-form--conditions .form--field", text: name, wait: 5) set_field_value(field, name, value) end diff --git a/spec/support/pages/admin/custom_actions/index.rb b/spec/support/pages/admin/automations/index.rb similarity index 90% rename from spec/support/pages/admin/custom_actions/index.rb rename to spec/support/pages/admin/automations/index.rb index 5507daf97f5..aac9afe73ad 100644 --- a/spec/support/pages/admin/custom_actions/index.rb +++ b/spec/support/pages/admin/automations/index.rb @@ -32,14 +32,14 @@ require "support/pages/page" module Pages module Admin - module CustomActions + module Automations class Index < ::Pages::Page def new - page.find_test_selector("op-admin-custom-actions--button-new", text: "Custom action").click + page.find_test_selector("op-admin-automations--button-new").click wait_for_reload - Pages::Admin::CustomActions::New.new + Pages::Admin::Automations::New.new end def edit(name) @@ -47,8 +47,8 @@ module Pages find(".icon-edit").click end - custom_action = CustomAction.find_by!(name:) - Pages::Admin::CustomActions::Edit.new(custom_action) + automation = Automation.find_by!(name:) + Pages::Admin::Automations::Edit.new(automation) end def delete(name) @@ -97,7 +97,7 @@ module Pages end def path - custom_actions_path + automations_path end private diff --git a/spec/support/pages/admin/custom_actions/new.rb b/spec/support/pages/admin/automations/new.rb similarity index 96% rename from spec/support/pages/admin/custom_actions/new.rb rename to spec/support/pages/admin/automations/new.rb index 58be21a7c04..cc4f713427b 100644 --- a/spec/support/pages/admin/custom_actions/new.rb +++ b/spec/support/pages/admin/automations/new.rb @@ -34,14 +34,14 @@ require_relative "form" module Pages module Admin - module CustomActions + module Automations class New < Form def create click_button "Create" end def path - new_custom_action_path + new_automation_path end end end diff --git a/spec/support/pages/work_packages/abstract_work_package.rb b/spec/support/pages/work_packages/abstract_work_package.rb index 2c2f61ed076..9da3b345049 100644 --- a/spec/support/pages/work_packages/abstract_work_package.rb +++ b/spec/support/pages/work_packages/abstract_work_package.rb @@ -190,22 +190,22 @@ module Pages expect(page).to have_css(".op-app-header") end - def expect_custom_action(name) + def expect_automation(name) expect(page) .to have_css(".custom-action", text: name) end - def expect_custom_action_disabled(name) + def expect_automation_disabled(name) expect(page) .to have_css(".custom-action [disabled]", text: name) end - def expect_no_custom_action(name) + def expect_no_automation(name) expect(page) .to have_no_css(".custom-action", text: name) end - def expect_custom_action_order(*names) + def expect_automation_order(*names) within(".custom-actions") do names.each_cons(2) do |earlier, later| body.index(earlier) < body.index(later) @@ -286,7 +286,7 @@ module Pages page end - def click_custom_action(name, expect_success: true) + def click_automation(name, expect_success: true) page.within(".custom-actions") do click_button(name) end