mirror of
https://github.com/opf/openproject.git
synced 2026-06-13 19:20:00 +00:00
Move all custom actions into an automations namespace
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
+2
-2
@@ -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
|
||||
@@ -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,
|
||||
+12
-12
@@ -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
|
||||
@@ -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
|
||||
+2
-2
@@ -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
|
||||
+2
-2
@@ -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
|
||||
+2
-2
@@ -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
|
||||
+1
-1
@@ -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
|
||||
+2
-2
@@ -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|
|
||||
+2
-2
@@ -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
|
||||
+2
-2
@@ -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
|
||||
|
||||
+2
-2
@@ -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
|
||||
+3
-3
@@ -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
|
||||
+2
-2
@@ -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
|
||||
+2
-2
@@ -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
|
||||
+3
-3
@@ -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
|
||||
+3
-3
@@ -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
|
||||
+2
-2
@@ -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
|
||||
[
|
||||
+1
-1
@@ -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)
|
||||
+1
-1
@@ -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
|
||||
+2
-2
@@ -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
|
||||
+2
-2
@@ -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)
|
||||
+3
-3
@@ -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
|
||||
+2
-2
@@ -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
|
||||
+3
-3
@@ -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?
|
||||
+2
-2
@@ -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
|
||||
+2
-2
@@ -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
|
||||
+3
-3
@@ -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
|
||||
+1
-1
@@ -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
|
||||
+1
-1
@@ -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
|
||||
@@ -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
|
||||
|
||||
+18
-18
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
+1
-1
@@ -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
|
||||
+5
-5
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
+1
-1
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
+1
-1
@@ -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)
|
||||
+1
-1
@@ -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
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
+14
-42
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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") %>
|
||||
|
||||
<div class="form--field -required">
|
||||
<%= f.text_field :name, required: true, container_class: "-middle" %>
|
||||
</div>
|
||||
<div class="form--field">
|
||||
<%= f.text_area :description, container_class: "-middle" %>
|
||||
</div>
|
||||
|
||||
<fieldset class="form--fieldset" id="automations-form--trigger">
|
||||
<legend class="form--fieldset-legend"><%= t("automations.trigger") %></legend>
|
||||
<div class="form--field">
|
||||
<%= styled_label_tag "automation_trigger_type", t("automations.triggers.name"), class: "-top" %>
|
||||
<div class="form--field-container">
|
||||
<%= styled_text_field_tag "automation_trigger_type",
|
||||
t("automations.triggers.manual.label"),
|
||||
disabled: true,
|
||||
container_class: "-middle" %>
|
||||
</div>
|
||||
</div>
|
||||
<%= render partial: "trigger_fields_manual", locals: { trigger: } %>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="form--fieldset" id="automations-form--conditions">
|
||||
<legend class="form--fieldset-legend"><%= t("automations.conditions") %></legend>
|
||||
|
||||
<% @automation.all_conditions.each do |condition| %>
|
||||
<div class="form--field">
|
||||
<%= styled_label_tag("automation_conditions_#{condition.key}", condition.human_name, class: "-top") %>
|
||||
<% input_name = "automation[conditions][#{condition.key}]" %>
|
||||
<div class="form--field-container">
|
||||
<div class="form--select-container -middle">
|
||||
<% 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 %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="form--fieldset" id="automations-form--actions">
|
||||
<legend class="form--fieldset-legend"><%= t("automations.actions.name") %></legend>
|
||||
|
||||
<div id="automations-form--active-actions">
|
||||
<% @automation.all_actions.each do |action| %>
|
||||
<section class="hide-section"
|
||||
data-hide-sections-target="section"
|
||||
data-name="<%= action.key %>"
|
||||
<%= tag.attributes(data: { section_name: action.key }, hidden: active_section_keys.exclude?(action.key)) %>>
|
||||
<div class="form--field">
|
||||
<%= 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) %>
|
||||
<div class="form--field-container">
|
||||
<div class="form--select-container -middle">
|
||||
<% 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}"
|
||||
} %>
|
||||
</div>
|
||||
</div>
|
||||
<% elsif %i(string_property text_property link_property float_property integer_property).include?(action.type) %>
|
||||
<div class="form--field-container">
|
||||
<%= styled_text_field_tag input_name, action.values, container_class: "-slim" %>
|
||||
</div>
|
||||
<% 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" })) %>
|
||||
</div>
|
||||
</section>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="form--field">
|
||||
<label class="form--label" for="add-automation-action-select">
|
||||
<%= render(Primer::Beta::Octicon.new(icon: :plus, size: :small)) %>
|
||||
<%= I18n.t(:"automations.actions.add") %>
|
||||
</label>
|
||||
<span class="form--field-container">
|
||||
<span class="form--select-container -middle">
|
||||
<%= 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" %>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</fieldset>
|
||||
@@ -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? %>
|
||||
|
||||
<div class="form--field -required">
|
||||
<%= styled_label_tag "automation_triggers_attributes_0_options_button_label", t("automations.triggers.manual.button_label"), class: "-top" %>
|
||||
<div class="form--field-container">
|
||||
<%= 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 %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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 %>
|
||||
@@ -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 %>
|
||||
@@ -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 %>
|
||||
@@ -1,200 +0,0 @@
|
||||
<% active_section_keys = @custom_action.actions.map(&:key) %>
|
||||
|
||||
<div class="form--field -required">
|
||||
<%= f.text_field :name, required: true, container_class: "-middle" %>
|
||||
</div>
|
||||
<div class="form--field">
|
||||
<%= f.text_area :description, container_class: "-middle" %>
|
||||
</div>
|
||||
|
||||
<fieldset class="form--fieldset" id="custom-actions-form--conditions">
|
||||
<legend class="form--fieldset-legend">
|
||||
<%= t("custom_actions.conditions") %>
|
||||
</legend>
|
||||
|
||||
<% @custom_action.all_conditions.each do |condition| %>
|
||||
<div class="form--field">
|
||||
<%= styled_label_tag("custom_action_conditions_#{condition.key}", condition.human_name, class: "-top") %>
|
||||
<% input_name = "custom_action[conditions][#{condition.key}]" %>
|
||||
|
||||
<div class="form--field-container">
|
||||
<div class="form--select-container -middle">
|
||||
<% 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 %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form--space"></div>
|
||||
<% end %>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="form--fieldset" id="custom-actions-form--actions">
|
||||
<legend class="form--fieldset-legend">
|
||||
<%= t("custom_actions.actions.name") %>
|
||||
</legend>
|
||||
|
||||
<div id="custom-actions-form--active-actions">
|
||||
<% @custom_action.all_actions.each do |action| %>
|
||||
<section class="hide-section"
|
||||
data-hide-sections-target="section"
|
||||
data-name="<%= action.key %>"
|
||||
<%= tag.attributes(data: { section_name: action.key }, hidden: active_section_keys.exclude?(action.key)) %>>
|
||||
<div class="form--field">
|
||||
<%= 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) %>
|
||||
<div class="form--field-container">
|
||||
<div class="form--select-container -middle">
|
||||
<% 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 %>
|
||||
</div>
|
||||
</div>
|
||||
<% elsif %i(date_property).include?(action.type) %>
|
||||
<div class="form--field-container">
|
||||
<% date = action.values.first %>
|
||||
<%= angular_component_tag "opce-custom-date-action-admin",
|
||||
data: { "field-value": date.try(:iso8601) || date, "field-name": input_name } %>
|
||||
</div>
|
||||
<% elsif %i(string_property text_property).include?(action.type) %>
|
||||
<div class="form--field-container">
|
||||
<%= styled_text_field_tag input_name,
|
||||
action.values,
|
||||
container_class: "-slim",
|
||||
step: "any" %>
|
||||
</div>
|
||||
<% elsif %i(link_property).include?(action.type) %>
|
||||
<div class="form--field-container">
|
||||
<%= styled_url_field_tag input_name,
|
||||
action.values,
|
||||
container_class: "-slim",
|
||||
step: "any" %>
|
||||
</div>
|
||||
<% elsif action.type == :float_property %>
|
||||
<div class="form--field-container">
|
||||
<%= styled_number_field_tag input_name,
|
||||
action.values,
|
||||
container_class: "-slim",
|
||||
min: action.minimum,
|
||||
max: action.maximum,
|
||||
step: "any" %>
|
||||
</div>
|
||||
<% elsif action.type == :integer_property %>
|
||||
<div class="form--field-container">
|
||||
<%= styled_number_field_tag input_name,
|
||||
action.values,
|
||||
container_class: "-slim",
|
||||
min: action.minimum,
|
||||
max: action.maximum,
|
||||
step: 1 %>
|
||||
</div>
|
||||
<% 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" }
|
||||
)
|
||||
)
|
||||
%>
|
||||
</div>
|
||||
</section>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="form--space"></div>
|
||||
|
||||
<div class="form--field">
|
||||
<label class="form--label" for="add-custom-action-select">
|
||||
<%= render(Primer::Beta::Octicon.new(icon: :plus, size: :small)) %>
|
||||
<%= I18n.t(:"custom_actions.actions.add") %>
|
||||
</label>
|
||||
<span class="form--field-container">
|
||||
<span class="form--select-container -middle">
|
||||
<%= 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" %>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</fieldset>
|
||||
@@ -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 %>
|
||||
@@ -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 %>
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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|
|
||||
|
||||
+19
-16
@@ -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
|
||||
@@ -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).
|
||||
|
||||
+9
-9
@@ -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
|
||||
+27
-27
@@ -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)
|
||||
@@ -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
|
||||
+5
-5
@@ -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
|
||||
+6
-6
@@ -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
|
||||
+50
-50
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+14
-14
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
+4
-4
@@ -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
|
||||
+5
-5
@@ -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
|
||||
+1
-1
@@ -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 }
|
||||
+2
-2
@@ -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
|
||||
+1
-1
@@ -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 }
|
||||
+2
-2
@@ -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
|
||||
+1
-1
@@ -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
|
||||
+1
-1
@@ -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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user