Merge pull request #8345 from opf/feature/32126-use-timelogging-widget-on-cost-reports-page
[32126] Use time logging widget on cost reports page [ci skip]pull/8366/head
commit
c86e77897b
@ -0,0 +1,87 @@ |
||||
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, Injector, OnInit} from "@angular/core"; |
||||
import {InjectField} from "core-app/helpers/angular/inject-field.decorator"; |
||||
import {TimeEntryEditService} from "core-app/modules/time_entries/edit/edit.service"; |
||||
import {TimeEntryCacheService} from "core-components/time-entries/time-entry-cache.service"; |
||||
import {I18nService} from "core-app/modules/common/i18n/i18n.service"; |
||||
import {TimeEntryResource} from "core-app/modules/hal/resources/time-entry-resource"; |
||||
import {TimeEntryDmService} from "core-app/modules/hal/dm-services/time-entry-dm.service"; |
||||
import {NotificationsService} from "core-app/modules/common/notifications/notifications.service"; |
||||
|
||||
export const triggerActionsEntryComponentSelector = 'time-entry--trigger-actions-entry'; |
||||
|
||||
@Component({ |
||||
selector: triggerActionsEntryComponentSelector, |
||||
template: ` |
||||
<a *ngIf="entry" |
||||
(click)="editTimeEntry(entry)" |
||||
[title]="text.edit" |
||||
class="no-decoration-on-hover"> |
||||
<op-icon icon-classes="icon-context icon-edit"></op-icon> |
||||
</a> |
||||
<a *ngIf="entry" |
||||
(click)="deleteTimeEntry(entry)" |
||||
[title]="text.delete" |
||||
class="no-decoration-on-hover"> |
||||
<op-icon icon-classes="icon-context icon-delete"></op-icon> |
||||
</a> |
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush, |
||||
}) |
||||
export class TriggerActionsEntryComponent implements OnInit { |
||||
@InjectField() readonly timeEntryEditService:TimeEntryEditService; |
||||
@InjectField() readonly timeEntryCache:TimeEntryCacheService; |
||||
@InjectField() readonly timeEntryDmService:TimeEntryDmService; |
||||
@InjectField() readonly notificationsService:NotificationsService; |
||||
@InjectField() readonly elementRef:ElementRef; |
||||
@InjectField() readonly i18n:I18nService; |
||||
@InjectField() readonly cdRef:ChangeDetectorRef; |
||||
|
||||
public entry:TimeEntryResource; |
||||
|
||||
public text = { |
||||
edit: this.i18n.t('js.button_edit'), |
||||
delete: this.i18n.t('js.button_delete'), |
||||
error: this.i18n.t('js.error.internal'), |
||||
areYouSure: this.i18n.t('js.text_are_you_sure') |
||||
}; |
||||
|
||||
constructor(readonly injector:Injector) { |
||||
|
||||
} |
||||
|
||||
ngOnInit() { |
||||
let timeEntryId = this.elementRef.nativeElement.dataset['entry']; |
||||
this.timeEntryCache |
||||
.require(timeEntryId) |
||||
.then((loadedEntry) => { |
||||
this.entry = loadedEntry; |
||||
this.cdRef.detectChanges(); |
||||
}); |
||||
} |
||||
|
||||
editTimeEntry(entry:TimeEntryResource) { |
||||
this.timeEntryEditService |
||||
.edit(entry) |
||||
.then(() => { |
||||
window.location.reload(); |
||||
}) |
||||
.catch(() => { |
||||
// User canceled the modal
|
||||
}); |
||||
} |
||||
|
||||
deleteTimeEntry(entry:TimeEntryResource) { |
||||
if (!window.confirm(this.text.areYouSure)) { |
||||
return; |
||||
} |
||||
|
||||
this.timeEntryDmService |
||||
.delete(entry) |
||||
.then(() => { |
||||
window.location.reload(); |
||||
}) |
||||
.catch((error) => { |
||||
this.notificationsService.addError(error || this.text.error); |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,98 @@ |
||||
#-- 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. |
||||
#++ |
||||
|
||||
|
||||
module Components |
||||
class CostReportsBaseTable |
||||
include Capybara::DSL |
||||
include RSpec::Matchers |
||||
|
||||
attr_reader :time_logging_modal |
||||
|
||||
def initialize |
||||
@time_logging_modal = Components::TimeLoggingModal.new |
||||
end |
||||
|
||||
def rows_count(count) |
||||
expect(page).to have_selector('#result-table tbody tr', count: count) |
||||
end |
||||
|
||||
def expect_action_icon(icon, row, present: true) |
||||
if present |
||||
expect(page).to have_selector("#{row_selector(row)} .icon-#{icon}") |
||||
else |
||||
expect(page).to have_no_selector("#{row_selector(row)} .icon-#{icon}") |
||||
end |
||||
end |
||||
|
||||
def expect_value(value, row) |
||||
expect(page).to have_selector("#{row_selector(row)} .units", text: value) |
||||
end |
||||
|
||||
def edit_time_entry(new_value, row) |
||||
page.find("#{row_selector(row)} .icon-edit").click |
||||
|
||||
time_logging_modal.is_visible true |
||||
time_logging_modal.update_field 'hours', new_value |
||||
time_logging_modal.work_package_is_missing false |
||||
|
||||
time_logging_modal.perform_action 'Save' |
||||
|
||||
sleep(3) |
||||
|
||||
expect_action_icon 'edit', row |
||||
expect_value new_value, row |
||||
end |
||||
|
||||
def edit_cost_entry(new_value, row, cost_entry_id) |
||||
page.find("#{row_selector(row)} .icon-edit").click |
||||
|
||||
expect(page).to have_current_path('/cost_entries/' + cost_entry_id + '/edit') |
||||
|
||||
fill_in('cost_entry_units', with: new_value) |
||||
click_button 'Save' |
||||
expect(page).to have_selector('.flash.notice') |
||||
|
||||
sleep(3) |
||||
end |
||||
|
||||
def delete_entry(row) |
||||
page.find("#{row_selector(row)} .icon-delete").click |
||||
|
||||
page.driver.browser.switch_to.alert.accept |
||||
|
||||
sleep(3) |
||||
end |
||||
|
||||
private |
||||
|
||||
def row_selector(row) |
||||
"#result-table tbody tr:nth-of-type(#{row})" |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,135 @@ |
||||
#-- 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' |
||||
require_relative 'support/pages/cost_report_page' |
||||
require_relative 'support/components/cost_reports_base_table' |
||||
|
||||
describe 'Updating entries within the cost report', type: :feature, js: true do |
||||
let(:project) { FactoryBot.create :project } |
||||
let(:user) { FactoryBot.create :admin } |
||||
let(:work_package) { FactoryBot.create :work_package, project: project } |
||||
|
||||
let!(:time_entry_user) do |
||||
FactoryBot.create :time_entry, |
||||
user: user, |
||||
work_package: work_package, |
||||
project: project, |
||||
hours: 5 |
||||
end |
||||
|
||||
let(:cost_type) do |
||||
type = FactoryBot.create :cost_type, name: 'My cool type' |
||||
FactoryBot.create :cost_rate, cost_type: type, rate: 7.00 |
||||
type |
||||
end |
||||
|
||||
let!(:cost_entry_user) do |
||||
FactoryBot.create :cost_entry, |
||||
work_package: work_package, |
||||
project: project, |
||||
units: 3.00, |
||||
cost_type: cost_type, |
||||
user: user |
||||
end |
||||
|
||||
let(:report_page) { ::Pages::CostReportPage.new project } |
||||
let(:table) { ::Components::CostReportsBaseTable.new } |
||||
|
||||
before do |
||||
login_as(user) |
||||
visit cost_reports_path(project) |
||||
report_page.clear |
||||
report_page.apply |
||||
report_page.show_loading_indicator present: false |
||||
end |
||||
|
||||
it 'can edit and delete time entries' do |
||||
table.rows_count 1 |
||||
|
||||
table.expect_action_icon 'edit', 1 |
||||
table.expect_action_icon 'delete', 1 |
||||
|
||||
table.edit_time_entry 2, 1 |
||||
|
||||
table.delete_entry 1 |
||||
table.rows_count 0 |
||||
end |
||||
|
||||
it 'can edit and delete cost entries' do |
||||
table.rows_count 1 |
||||
|
||||
report_page.switch_to_type 'My cool type' |
||||
report_page.show_loading_indicator present: false |
||||
|
||||
table.rows_count 1 |
||||
|
||||
table.expect_action_icon 'edit', 1 |
||||
table.expect_action_icon 'delete', 1 |
||||
|
||||
table.edit_cost_entry 2, 1, cost_entry_user.id.to_s |
||||
visit cost_reports_path(project) |
||||
table.rows_count 1 |
||||
|
||||
table.delete_entry 1 |
||||
table.rows_count 0 |
||||
end |
||||
|
||||
it 'shows the action icons after a table refresh' do |
||||
table.rows_count 1 |
||||
|
||||
table.expect_action_icon 'edit', 1 |
||||
table.expect_action_icon 'delete', 1 |
||||
|
||||
# Force a reload of the table (although nothing has changed) |
||||
report_page.apply |
||||
sleep(1) |
||||
report_page.show_loading_indicator present: false |
||||
|
||||
table.rows_count 1 |
||||
|
||||
table.expect_action_icon 'edit', 1 |
||||
table.expect_action_icon 'delete', 1 |
||||
end |
||||
|
||||
context 'as user without permissions' do |
||||
let(:role) { FactoryBot.create :role, permissions: %i(view_time_entries) } |
||||
let!(:user) do |
||||
FactoryBot.create :user, |
||||
member_in_project: project, |
||||
member_through_role: role |
||||
end |
||||
|
||||
it 'cannot edit or delete' do |
||||
table.rows_count 1 |
||||
|
||||
table.expect_action_icon 'edit', 1, present: false |
||||
table.expect_action_icon 'delete', 1, present: false |
||||
end |
||||
end |
||||
end |
Loading…
Reference in new issue