OpenProject is the leading open source project management software.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openproject/spec/features/work_packages/custom_actions_spec.rb

481 lines
16 KiB

#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2020 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-2017 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See docs/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
describe 'Custom actions', type: :feature, js: true do
using_shared_fixtures :admin
let(:permissions) { %i(view_work_packages edit_work_packages move_work_packages) }
let(:role) { FactoryBot.create(:role, permissions: permissions) }
let!(:other_role) { FactoryBot.create(:role, permissions: permissions) }
let(:user) do
user = FactoryBot.create(:user,
firstname: 'A',
lastname: 'User')
FactoryBot.create(:member,
project: project,
roles: [role],
user: user)
FactoryBot.create(:member,
project: other_project,
roles: [role],
user: user)
user
end
let!(:other_member_user) do
FactoryBot.create(:user,
firstname: 'Other member',
lastname: 'User',
member_in_project: project,
member_through_role: role)
end
let(:project) { FactoryBot.create(:project, name: 'This project') }
let(:other_project) { FactoryBot.create(:project, name: 'Other project') }
let!(:work_package) do
FactoryBot.create(:work_package,
project: project,
assigned_to: user,
priority: default_priority,
status: default_status)
end
let(:wp_page) { Pages::FullWorkPackage.new(work_package) }
let(:default_priority) do
FactoryBot.create(:default_priority, name: 'Normal')
end
let!(:immediate_priority) do
FactoryBot.create(:issue_priority,
name: 'At once',
position: IssuePriority.maximum(:position) + 1)
end
let(:default_status) do
FactoryBot.create(:default_status, name: 'Default status')
end
let(:closed_status) do
FactoryBot.create(:closed_status, name: 'Closed')
end
let(:rejected_status) do
FactoryBot.create(:closed_status, name: 'Rejected')
end
let(:other_type) do
type = FactoryBot.create(:type)
other_project.types << type
type
end
let!(:workflows) do
FactoryBot.create(:workflow,
old_status: default_status,
new_status: closed_status,
role: role,
type: work_package.type)
FactoryBot.create(:workflow,
new_status: default_status,
old_status: closed_status,
role: role,
type: work_package.type)
FactoryBot.create(:workflow,
old_status: default_status,
new_status: rejected_status,
role: role,
type: work_package.type)
FactoryBot.create(:workflow,
old_status: rejected_status,
new_status: default_status,
role: role,
type: other_type)
end
let!(:list_custom_field) do
cf = FactoryBot.create(:list_wp_custom_field, multi_value: true)
project.work_package_custom_fields = [cf]
work_package.type.custom_fields = [cf]
cf
end
let!(:int_custom_field) do
FactoryBot.create(:int_wp_custom_field)
end
let(:selected_list_custom_field_options) do
[list_custom_field.custom_options.first, list_custom_field.custom_options.last]
end
let!(:date_custom_field) do
cf = FactoryBot.create(:date_wp_custom_field)
other_project.work_package_custom_fields = [cf]
other_type.custom_fields = [cf]
cf
end
let(:index_ca_page) { Pages::Admin::CustomActions::Index.new }
before do
with_enterprise_token(:custom_actions)
login_as(admin)
end
scenario 'viewing workflow buttons' do
# create custom action 'Unassign'
index_ca_page.visit!
new_ca_page = index_ca_page.new
retry_block do
new_ca_page.visit!
new_ca_page.set_name('Unassign')
new_ca_page.set_description('Removes the assignee')
new_ca_page.add_action('Assignee', '-')
5 years ago
new_ca_page.expect_action('assigned_to', nil)
end
new_ca_page.create
index_ca_page.expect_current_path
index_ca_page.expect_listed('Unassign')
unassign = CustomAction.last
expect(unassign.actions.length).to eq(1)
expect(unassign.conditions.length).to eq(0)
# create custom action 'Close'
new_ca_page = index_ca_page.new
retry_block do
new_ca_page.visit!
new_ca_page.set_name('Close')
5 years ago
new_ca_page.add_action('Status', 'Close')
5 years ago
new_ca_page.expect_action('status', closed_status.id)
new_ca_page.set_condition('Role', role.name)
5 years ago
new_ca_page.expect_selected_option role.name
new_ca_page.set_condition('Status', [default_status.name, rejected_status.name])
5 years ago
new_ca_page.expect_selected_option default_status.name
new_ca_page.expect_selected_option rejected_status.name
end
new_ca_page.create
index_ca_page.expect_current_path
index_ca_page.expect_listed('Unassign', 'Close')
close = CustomAction.last
expect(close.actions.length).to eq(1)
expect(close.conditions.length).to eq(2)
# create custom action 'Escalate'
new_ca_page = index_ca_page.new
retry_block do
new_ca_page.visit!
new_ca_page.set_name('Escalate')
new_ca_page.add_action('Priority', immediate_priority.name)
5 years ago
new_ca_page.expect_action('priority', immediate_priority.id)
new_ca_page.add_action('Notify', other_member_user.name)
5 years ago
new_ca_page.expect_selected_option other_member_user.name
new_ca_page.add_action(list_custom_field.name, selected_list_custom_field_options.map(&:name))
5 years ago
new_ca_page.expect_selected_option 'A'
new_ca_page.expect_selected_option 'G'
end
new_ca_page.create
index_ca_page.expect_current_path
index_ca_page.expect_listed('Unassign', 'Close', 'Escalate')
escalate = CustomAction.last
expect(escalate.actions.length).to eq(3)
expect(escalate.conditions.length).to eq(0)
# create custom action 'Reset'
new_ca_page = index_ca_page.new
retry_block do
new_ca_page.visit!
new_ca_page.set_name('Reset')
5 years ago
new_ca_page.add_action('Priority', default_priority.name)
5 years ago
new_ca_page.expect_action('priority', default_priority.id)
new_ca_page.add_action('Status', default_status.name)
5 years ago
new_ca_page.expect_action('status', default_status.id)
new_ca_page.add_action('Assignee', user.name)
5 years ago
new_ca_page.expect_action('assigned_to', user.id)
# This custom field is not applicable
new_ca_page.add_action(int_custom_field.name, '1')
5 years ago
new_ca_page.expect_action("custom_field_#{int_custom_field.id}", '1')
new_ca_page.set_condition('Status', closed_status.name)
5 years ago
new_ca_page.expect_selected_option closed_status.name
end
new_ca_page.create
index_ca_page.expect_current_path
index_ca_page.expect_listed('Unassign', 'Close', 'Escalate', 'Reset')
reset = CustomAction.last
expect(reset.actions.length).to eq(4)
expect(reset.conditions.length).to eq(1)
# create custom action 'Other roles action'
new_ca_page = index_ca_page.new
retry_block do
new_ca_page.visit!
new_ca_page.set_name('Other roles action')
5 years ago
new_ca_page.add_action('Status', default_status.name)
5 years ago
new_ca_page.expect_action('status', default_status.id)
new_ca_page.set_condition('Role', other_role.name)
5 years ago
new_ca_page.expect_selected_option other_role.name
end
new_ca_page.create
index_ca_page.expect_current_path
index_ca_page.expect_listed('Unassign', 'Close', 'Escalate', 'Reset', 'Other roles action')
other_roles_action = CustomAction.last
expect(other_roles_action.actions.length).to eq(1)
expect(other_roles_action.conditions.length).to eq(1)
# create custom action 'Move project'
new_ca_page = index_ca_page.new
retry_block do
new_ca_page.visit!
new_ca_page.set_name('Move project')
# Add date custom action which has a different admin layout
select date_custom_field.name, from: 'Add action'
select 'on', from: date_custom_field.name
5 years ago
[32880] Display scheduling mode in work package view (full, split & table) (#8410) * Show icon next to start date depending on the scheduling mode * Show modal when opening date edit fields which shall be able to change the dates and the scheduling mode * Introduce flatpickr as a datepicker for the modal of the dateEditField * Use new datepicker within opDatePickerComponent * Reuse existing Datepicker class for better code abstraction * Set dates only when Instance is ready * Let augmentedDatePicker use generic DatePicker class, too. * Remove jQuery UI datepicker specific styles as they do not apply any more. * Use localized strings in new datepicker * Close datepicker only on click outside * Allow single and multi selection of dates and save changes * Update schedulingMode button correctly && remove unused method * Show combined field for start and end date within the WP single view * Show normal datepicker for most date fields and the modal with the extended datepicker only for startDate * Add datepicker edit field and component helper * Correctly save start and end date * Show correct default value for dateEditFields && Adapt some tests to new datepicker and combined date field * Fix date editor spec * Fix expecting datepicker for start/due date as milestone * Prevent overflow on body by using modal-portal class && make edit input for combinedDates visible again to show something in a create form * Only output one debounced change event for op datepicker * Show combined datepickerModal for dueDate in table, too && save correct scheduling value * Hide modal if the showing component gets destroyed * Correctly check for closed datepicker * Add spec for toggling scheduling mode * Fix setting dates to modal * Avoid too many (unnecessary) close events and use keydown instead * Fix date setting and add custom field date spec * Change layout of combined date picker modal * Switch from a range date picker to a multiple selection in order to be able to set only start or due date && Highlight date field that is changed next * Highlight range manually * Highlight dueDate initially when the user clicked on the due date field && handle special case that one of the values is not set yet. In this case we don't want to keep both values but only the newly clicked. * Move helper functions to own class && Improve the range selection to be able highlight ranges over multiple months && Prevent that a start date > due date can be chosen by setting minDate and maxDate * Adapt test to new way of changing the scheduling mode * Handle case that the user clicks on a already selected date and thus deselects it Co-authored-by: Oliver Günther <mail@oliverguenther.de>
4 years ago
date = (Date.today + 5.days)
find("#custom_action_actions_custom_field_#{date_custom_field.id}_visible").click
datepicker = Components::Datepicker.new 'body'
datepicker.set_date date
new_ca_page.add_action('Type', other_type.name)
5 years ago
new_ca_page.expect_action('type', other_type.id)
new_ca_page.add_action('Project', other_project.name)
5 years ago
new_ca_page.expect_action('project', other_project.id)
new_ca_page.set_condition('Project', project.name)
5 years ago
new_ca_page.expect_selected_option project.name
end
new_ca_page.create
index_ca_page.expect_current_path
index_ca_page.expect_listed('Unassign', 'Close', 'Escalate', 'Reset', 'Other roles action', 'Move project')
move_project = CustomAction.last
expect(move_project.actions.length).to eq(3)
expect(move_project.conditions.length).to eq(1)
# use custom actions
login_as(user)
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')
within('.custom-actions') do
# When hovering over the button, the description is displayed
button = find('.custom-action--button', text: 'Unassign')
expect(button['title'])
.to eql 'Removes the assignee'
end
wp_page.click_custom_action('Unassign')
wp_page.expect_attributes assignee: '-'
wp_page.click_custom_action('Escalate')
wp_page.expect_attributes priority: immediate_priority.name,
status: default_status.name,
assignee: '-',
"customField#{list_custom_field.id}" => selected_list_custom_field_options.map(&:name).join("\n")
expect(page)
.to have_selector('.work-package-details-activities-activity-contents a.user-mention', text: other_member_user.name)
wp_page.click_custom_action('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.click_custom_action('Reset')
wp_page.expect_attributes priority: default_priority.name,
status: default_status.name,
assignee: user.name
wp_page.expect_no_attribute "customField#{int_custom_field.id}"
# edit 'Reset' to now be named 'Reject' which sets the status to 'Rejected'
login_as(admin)
index_ca_page.visit!
edit_ca_page = index_ca_page.edit('Reset')
retry_block do
edit_ca_page.visit!
edit_ca_page.set_name 'Reject'
edit_ca_page.remove_action 'Priority'
edit_ca_page.set_action 'Assignee', '-'
5 years ago
edit_ca_page.expect_action 'assigned_to', nil
edit_ca_page.set_action 'Status', rejected_status.name
5 years ago
edit_ca_page.expect_action 'status', rejected_status.id
edit_ca_page.set_condition 'Status', default_status.name
5 years ago
edit_ca_page.expect_selected_option default_status.name
end
edit_ca_page.save
index_ca_page.expect_current_path
index_ca_page.expect_listed('Unassign', 'Close', 'Escalate', 'Reject')
reset = CustomAction.find_by(name: 'Reject')
expect(reset.actions.length).to eq(3)
expect(reset.conditions.length).to eq(1)
index_ca_page.move_top 'Move project'
index_ca_page.move_bottom 'Escalate'
index_ca_page.move_up 'Close'
index_ca_page.move_down 'Unassign'
# Check the altered button
login_as(user)
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.click_custom_action('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')
# Delete 'Reject' from list of actions
login_as(admin)
index_ca_page.visit!
index_ca_page.delete('Unassign')
index_ca_page.expect_current_path
index_ca_page.expect_listed('Close', 'Escalate', 'Reject')
login_as(user)
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')
# Move project
wp_page.click_custom_action('Move project')
wp_page.expect_attributes assignee: '-',
status: rejected_status.name,
type: other_type.name.upcase,
"customField#{date_custom_field.id}" => (Date.today + 5.days).strftime('%m/%d/%Y')
expect(page)
.to have_content(I18n.t('js.project.work_package_belongs_to', projectname: other_project.name))
## Bump the lockVersion and by that force a conflict.
work_package.reload.touch
wp_page.click_custom_action('Escalate', expect_success: false)
wp_page.expect_notification type: :error, message: I18n.t('api_v3.errors.code_409')
end
scenario 'editing a current date custom action (Regression #30949)' do
# create custom action 'Unassign'
index_ca_page.visit!
new_ca_page = index_ca_page.new
retry_block do
new_ca_page.visit!
new_ca_page.set_name('Current date')
new_ca_page.set_description('Sets the current date')
new_ca_page.add_action('Date', 'Current date')
end
new_ca_page.create
index_ca_page.expect_current_path
index_ca_page.expect_listed('Current date')
date_action = CustomAction.last
expect(date_action.actions.length).to eq(1)
expect(date_action.conditions.length).to eq(0)
edit_page = index_ca_page.edit('Current date')
expect(page).to have_select('custom_action_actions_date', selected: 'Current date')
end
end