Merge pull request #4225 from oliverguenther/feature/expand-error-columns
[22556] Error handling for other required/invalid fields of a WP rowpull/4233/head
commit
c6844255f9
@ -0,0 +1,63 @@ |
|||||||
|
//-- copyright |
||||||
|
// OpenProject is a project management system. |
||||||
|
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF) |
||||||
|
// |
||||||
|
// 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 doc/COPYRIGHT.rdoc for more details. |
||||||
|
//++ |
||||||
|
|
||||||
|
.-editable |
||||||
|
.wp-edit-field |
||||||
|
&.-active |
||||||
|
padding: 0 |
||||||
|
|
||||||
|
&.-error |
||||||
|
background: $nm-color-error-background |
||||||
|
border-color: $nm-color-error-border |
||||||
|
|
||||||
|
&:hover |
||||||
|
border-color: lighten($nm-color-error-border, 10%) |
||||||
|
|
||||||
|
form |
||||||
|
width: 100% |
||||||
|
|
||||||
|
&:hover .wp-edit-field.-error:hover |
||||||
|
border-color: $nm-color-error-border |
||||||
|
|
||||||
|
.wp-table--cell |
||||||
|
cursor: not-allowed |
||||||
|
|
||||||
|
&.-editable |
||||||
|
cursor: pointer |
||||||
|
|
||||||
|
.work-package-table--container .generic-table tbody |
||||||
|
td |
||||||
|
.wp-edit-field .-hidden-overflow |
||||||
|
overflow: hidden |
||||||
|
text-overflow: ellipsis |
||||||
|
&.-editable |
||||||
|
padding-top: 0 |
||||||
|
padding-bottom: 0 |
||||||
|
display: table-cell |
||||||
|
width: auto |
||||||
|
|
@ -0,0 +1,68 @@ |
|||||||
|
//-- copyright
|
||||||
|
// OpenProject is a project management system.
|
||||||
|
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
|
||||||
|
//
|
||||||
|
// 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 doc/COPYRIGHT.rdoc for more details.
|
||||||
|
//++
|
||||||
|
|
||||||
|
function errorResource(HalResource:typeof op.HalResource, NotificationsService:any) { |
||||||
|
class ErrorResource extends HalResource { |
||||||
|
public errors:any[]; |
||||||
|
public message:string; |
||||||
|
public details:any; |
||||||
|
public errorIdentifier:string; |
||||||
|
|
||||||
|
public get errorMessages():string[] { |
||||||
|
if (this.isMultiErrorMessage()) { |
||||||
|
return this.errors.map(error => error.message); |
||||||
|
} else { |
||||||
|
return [this.message]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public isMultiErrorMessage() { |
||||||
|
return this.errorIdentifier === 'urn:openproject-org:api:v3:errors:MultipleErrors'; |
||||||
|
} |
||||||
|
|
||||||
|
public showErrorNotification() { |
||||||
|
var messages = this.errorMessages; |
||||||
|
if (messages.length > 1) { |
||||||
|
NotificationsService.addError('', messages); |
||||||
|
} else { |
||||||
|
NotificationsService.addError(messages[0]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public getInvolvedColumns():string[] { |
||||||
|
var columns = this.details ? [{ details: this.details }] : this.errors; |
||||||
|
return columns.map(field => field.details.attribute); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return ErrorResource; |
||||||
|
} |
||||||
|
|
||||||
|
angular |
||||||
|
.module('openproject.api') |
||||||
|
.factory('ErrorResource', errorResource); |
@ -0,0 +1,117 @@ |
|||||||
|
require 'spec_helper' |
||||||
|
|
||||||
|
describe 'Inline editing work packages', js: true do |
||||||
|
let(:dev_role) do |
||||||
|
FactoryGirl.create :role, |
||||||
|
permissions: [:view_work_packages, |
||||||
|
:add_work_packages] |
||||||
|
end |
||||||
|
let(:dev) do |
||||||
|
FactoryGirl.create :user, |
||||||
|
firstname: 'Dev', |
||||||
|
lastname: 'Guy', |
||||||
|
member_in_project: project, |
||||||
|
member_through_role: dev_role |
||||||
|
end |
||||||
|
let(:manager_role) do |
||||||
|
FactoryGirl.create :role, |
||||||
|
permissions: [:view_work_packages, |
||||||
|
:edit_work_packages] |
||||||
|
end |
||||||
|
let(:manager) do |
||||||
|
FactoryGirl.create :user, |
||||||
|
firstname: 'Manager', |
||||||
|
lastname: 'Guy', |
||||||
|
member_in_project: project, |
||||||
|
member_through_role: manager_role |
||||||
|
end |
||||||
|
let(:type) { FactoryGirl.create :type } |
||||||
|
let(:type2) { FactoryGirl.create :type } |
||||||
|
let(:project) { FactoryGirl.create(:project, types: [type, type2]) } |
||||||
|
let(:work_package) { |
||||||
|
FactoryGirl.create(:work_package, |
||||||
|
author: dev, |
||||||
|
project: project, |
||||||
|
type: type, |
||||||
|
subject: 'Foobar') |
||||||
|
} |
||||||
|
|
||||||
|
let(:wp_table) { Pages::WorkPackagesTable.new(project) } |
||||||
|
let(:fields) { InlineEditField.new(wp_table, work_package) } |
||||||
|
|
||||||
|
let(:priority2) { FactoryGirl.create :priority } |
||||||
|
let(:status2) { FactoryGirl.create :status } |
||||||
|
let(:workflow) do |
||||||
|
FactoryGirl.create :workflow, |
||||||
|
type_id: type2.id, |
||||||
|
old_status: work_package.status, |
||||||
|
new_status: status2, |
||||||
|
role: manager_role |
||||||
|
end |
||||||
|
let(:version) { FactoryGirl.create :version, project: project } |
||||||
|
let(:category) { FactoryGirl.create :category, project: project } |
||||||
|
|
||||||
|
before do |
||||||
|
login_as(manager) |
||||||
|
|
||||||
|
manager |
||||||
|
dev |
||||||
|
priority2 |
||||||
|
workflow |
||||||
|
|
||||||
|
wp_table.visit! |
||||||
|
wp_table.expect_work_package_listed(work_package) |
||||||
|
end |
||||||
|
|
||||||
|
it 'allows updating and seeing the results' do |
||||||
|
subject_field = wp_table.edit_field(work_package, :subject) |
||||||
|
subject_field.expect_text('Foobar') |
||||||
|
|
||||||
|
subject_field.activate! |
||||||
|
|
||||||
|
subject_field.set_value('New subject!') |
||||||
|
|
||||||
|
expect(UpdateWorkPackageService).to receive(:new).and_call_original |
||||||
|
subject_field.save! |
||||||
|
subject_field.expect_text('New subject!') |
||||||
|
|
||||||
|
work_package.reload |
||||||
|
expect(work_package.subject).to eq('New subject!') |
||||||
|
end |
||||||
|
|
||||||
|
it 'allows to subsequently edit multiple fields' do |
||||||
|
subject_field = wp_table.edit_field(work_package, :subject) |
||||||
|
priority_field = wp_table.edit_field(work_package, :priority) |
||||||
|
|
||||||
|
expect(UpdateWorkPackageService).to receive(:new).and_call_original |
||||||
|
subject_field.activate! |
||||||
|
subject_field.set_value('Other subject!') |
||||||
|
|
||||||
|
priority_field.activate! |
||||||
|
priority_field.set_value(priority2.name) |
||||||
|
priority_field.expect_inactive! |
||||||
|
|
||||||
|
subject_field.expect_text('Other subject!') |
||||||
|
priority_field.expect_text(priority2.name) |
||||||
|
|
||||||
|
work_package.reload |
||||||
|
expect(work_package.subject).to eq('Other subject!') |
||||||
|
expect(work_package.priority.id).to eq(priority2.id) |
||||||
|
end |
||||||
|
|
||||||
|
it 'provides error handling' do |
||||||
|
subject_field = wp_table.edit_field(work_package, :subject) |
||||||
|
subject_field.expect_text('Foobar') |
||||||
|
|
||||||
|
subject_field.activate! |
||||||
|
|
||||||
|
subject_field.set_value('') |
||||||
|
|
||||||
|
expect(UpdateWorkPackageService).to receive(:new).and_call_original |
||||||
|
subject_field.save! |
||||||
|
subject_field.expect_error |
||||||
|
|
||||||
|
work_package.reload |
||||||
|
expect(work_package.subject).to eq('Foobar') |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,85 @@ |
|||||||
|
class InlineEditField |
||||||
|
include Capybara::DSL |
||||||
|
include RSpec::Matchers |
||||||
|
|
||||||
|
attr_reader :work_package, :attribute, :element, :selector |
||||||
|
|
||||||
|
def initialize(work_package, attribute, field_type: nil) |
||||||
|
@work_package = work_package |
||||||
|
@attribute = attribute |
||||||
|
@field_type = field_type |
||||||
|
|
||||||
|
@selector = "#work-package-#{work_package.id} .#{attribute}" |
||||||
|
@element = page.find(selector) |
||||||
|
end |
||||||
|
|
||||||
|
def expect_text(text) |
||||||
|
expect(page).to have_selector(selector, text: text) |
||||||
|
end |
||||||
|
|
||||||
|
## |
||||||
|
# Activate the field and check it opened correctly |
||||||
|
def activate! |
||||||
|
edit_field.click |
||||||
|
expect_active! |
||||||
|
end |
||||||
|
|
||||||
|
## |
||||||
|
# Set or select the given value. |
||||||
|
# For fields of type select, will check for an option with that value. |
||||||
|
def set_value(content) |
||||||
|
if field_type == 'select' |
||||||
|
input_field.find(:option, content).select_option |
||||||
|
else |
||||||
|
input_field.set(content) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
def expect_error |
||||||
|
expect(page).to have_selector("#{field_selector}.-error") |
||||||
|
end |
||||||
|
|
||||||
|
def expect_active! |
||||||
|
expect(edit_field).to have_selector(field_type) |
||||||
|
end |
||||||
|
|
||||||
|
def expect_inactive! |
||||||
|
expect(edit_field).to have_no_selector(field_type) |
||||||
|
end |
||||||
|
|
||||||
|
def save! |
||||||
|
input_field.native.send_keys(:return) |
||||||
|
reset_field |
||||||
|
end |
||||||
|
|
||||||
|
def edit_field |
||||||
|
@edit_field ||= @element.find('.wp-edit-field') |
||||||
|
end |
||||||
|
|
||||||
|
def input_field |
||||||
|
@input_field ||= edit_field.find(field_type) |
||||||
|
end |
||||||
|
|
||||||
|
private |
||||||
|
|
||||||
|
def field_selector |
||||||
|
"#{selector} .wp-edit-field" |
||||||
|
end |
||||||
|
|
||||||
|
## |
||||||
|
# Reset the input field e.g., after saving |
||||||
|
def reset_field |
||||||
|
@input_field = nil |
||||||
|
end |
||||||
|
|
||||||
|
def field_type |
||||||
|
@field_type ||= begin |
||||||
|
case attribute |
||||||
|
when :assignee, :priority, :status |
||||||
|
:select |
||||||
|
else |
||||||
|
:input |
||||||
|
end.to_s |
||||||
|
end |
||||||
|
end |
||||||
|
end |
Loading…
Reference in new issue