Merge pull request #5867 from opf/feature/25999/timeline-labels
[25999] Make timeline labels configurable [ci skip]pull/5642/head
commit
695a8cc3fd
@ -0,0 +1,83 @@ |
||||
.timeline-element |
||||
|
||||
// Label style |
||||
.-label-style.not-empty |
||||
background-color: white |
||||
border: 1px solid #d4d4d4 |
||||
border-radius: 5px |
||||
height: 16px |
||||
font-size: 12px |
||||
padding: 2px 5px |
||||
|
||||
.labelLeft.not-empty, |
||||
.labelHoverLeft.not-empty |
||||
pointer-events: none |
||||
white-space: nowrap |
||||
// Position container left of bar |
||||
position: absolute |
||||
left: 0px |
||||
// Then translate by its own width + some margin |
||||
transform: translateX(calc(-100% - 10px)) |
||||
font-size: 12px |
||||
|
||||
// Add some top padding to the text (NOT the hover labels!) |
||||
.labelLeft |
||||
top: 3px |
||||
|
||||
.containerRight |
||||
pointer-events: none |
||||
display: inline-block |
||||
position: absolute |
||||
margin: 0 0 0 0 |
||||
padding: 0 0 0 0 |
||||
top: -2px |
||||
left: 100% |
||||
white-space: nowrap |
||||
line-height: 10px |
||||
font-size: 12px |
||||
height: 16px |
||||
|
||||
.labelRight.not-empty |
||||
display: inline-block |
||||
pointer-events: none |
||||
margin-left: 20px |
||||
font-size: 12px |
||||
|
||||
.labelFarRight |
||||
height: 16px |
||||
display: inline-block |
||||
white-space: nowrap |
||||
font-style: italic |
||||
margin-left: 15px |
||||
padding: 5px 5px 2px 5px |
||||
font-size: 13px |
||||
|
||||
// label hover right needs different position |
||||
// since its not part of containerRight |
||||
.labelHoverRight |
||||
pointer-events: none |
||||
display: none |
||||
top: 0 |
||||
white-space: nowrap |
||||
// Position container right of bar |
||||
position: absolute |
||||
right: 0px |
||||
// Then translate by its own width + some margin |
||||
transform: translateX(calc(100% + 10px)) |
||||
font-size: 12px |
||||
|
||||
&.-editable |
||||
cursor: ew-resize |
||||
|
||||
.show-on-hover |
||||
display: none |
||||
|
||||
// Hide or show elements on hover |
||||
.wp-timeline-cell.row-hovered |
||||
.show-on-hover |
||||
display: inline-block |
||||
|
||||
.hide-on-hover |
||||
display: none |
||||
|
||||
|
@ -0,0 +1,5 @@ |
||||
class AddTimelineLabelsToQuery < ActiveRecord::Migration[5.0] |
||||
def change |
||||
add_column :queries, :timeline_labels, :text |
||||
end |
||||
end |
@ -0,0 +1,75 @@ |
||||
//-- 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.
|
||||
//++
|
||||
|
||||
import {wpControllersModule} from '../../../angular-modules'; |
||||
import {WorkPackageTableTimelineService} from '../../wp-fast-table/state/wp-table-timeline.service'; |
||||
import {WorkPackageTableColumnsService} from '../../wp-fast-table/state/wp-table-columns.service'; |
||||
|
||||
function TimelinesModalController(this:any, |
||||
timelinesModal:any, |
||||
$scope:any, |
||||
wpTableTimeline:WorkPackageTableTimelineService, |
||||
wpTableColumns:WorkPackageTableColumnsService, |
||||
I18n:op.I18n) { |
||||
this.name = 'Timelines'; |
||||
this.closeMe = timelinesModal.deactivate; |
||||
|
||||
$scope.text = { |
||||
apply: I18n.t('js.modals.button_apply'), |
||||
cancel: I18n.t('js.modals.button_cancel'), |
||||
close: I18n.t('js.close_popup_title'), |
||||
title: I18n.t('js.timelines.gantt_chart'), |
||||
labels: { |
||||
description: I18n.t('js.timelines.labels.description'), |
||||
bar: I18n.t('js.timelines.labels.bar'), |
||||
none: I18n.t('js.timelines.filter.noneSelection'), |
||||
left: I18n.t('js.timelines.labels.left'), |
||||
right: I18n.t('js.timelines.labels.right'), |
||||
farRight: I18n.t('js.timelines.labels.farRight') |
||||
} |
||||
}; |
||||
|
||||
// Current label models
|
||||
const labels = wpTableTimeline.labels; |
||||
$scope.labels = _.clone(labels); |
||||
|
||||
// Available labels
|
||||
const availableColumns = wpTableColumns |
||||
.allPropertyColumns |
||||
.sort((a, b) => a.name.localeCompare(b.name)); |
||||
|
||||
$scope.availableAttributes = [{ id: '', name: $scope.text.labels.none }].concat(availableColumns); |
||||
|
||||
// Save
|
||||
$scope.updateLabels = () => { |
||||
wpTableTimeline.updateLabels($scope.labels); |
||||
timelinesModal.deactivate(); |
||||
}; |
||||
} |
||||
|
||||
wpControllersModule.controller('TimelinesModalController', TimelinesModalController); |
@ -0,0 +1,45 @@ |
||||
<div class="ng-modal-window"> |
||||
<div class="ng-modal-inner modal-content"> |
||||
<div class="modal-header"> |
||||
<a><op-icon icon-classes="icon-close" ng-click="$ctrl.closeMe()" title="{{ ::text.close }}"></op-icon></a></div> |
||||
|
||||
<h3>{{ ::text.title }}</h3> |
||||
|
||||
<form name="modalTimelinesForm"> |
||||
<div id="modal-timelines" |
||||
class="modal-content-container"> |
||||
<p ng-bind="::text.labels.description"></p> |
||||
<section class="form--section"> |
||||
<div class="form--row" ng-repeat="(key, value) in labels"> |
||||
<div class="form--field"> |
||||
<label |
||||
for="modal-timelines-label-{{key}}" |
||||
class="form--label"> |
||||
{{ text.labels[key] }} |
||||
</label> |
||||
<div class="form--field-container"> |
||||
<div class="form--select-container"> |
||||
<select |
||||
id="modal-timelines-label-{{key}}" |
||||
ng-model="labels[key]" |
||||
focus="$first" |
||||
class="form--select" |
||||
ng-options="c.id as c.name for c in availableAttributes"> |
||||
</select> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</section> |
||||
</div> |
||||
<button class="button -highlight" |
||||
ng-bind="::text.apply" |
||||
ng-click="updateLabels()"> |
||||
</button> |
||||
<button class="button" |
||||
ng-bind="::text.cancel" |
||||
ng-click="$ctrl.closeMe()"> |
||||
</button> |
||||
</form> |
||||
</div> |
||||
</div> |
@ -0,0 +1,40 @@ |
||||
//-- 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.
|
||||
//++
|
||||
|
||||
import {wpControllersModule} from '../../../angular-modules'; |
||||
|
||||
function timelinesModalService(btfModal:any) { |
||||
return btfModal({ |
||||
controller: 'TimelinesModalController', |
||||
controllerAs: '$ctrl', |
||||
afterFocusOn: '#work-packages-settings-button', |
||||
templateUrl: '/components/modals/timelines-modal/timelines-modal.service.html' |
||||
}); |
||||
} |
||||
|
||||
wpControllersModule.factory('timelinesModal', timelinesModalService); |
@ -0,0 +1,179 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2017 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-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 doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require 'spec_helper' |
||||
|
||||
RSpec.feature 'Work package timeline labels', |
||||
with_settings: { date_format: '%Y-%m-%d' }, |
||||
js: true, |
||||
selenium: true do |
||||
let(:user) { FactoryGirl.create(:admin) } |
||||
let(:type) { FactoryGirl.create(:type_bug) } |
||||
let(:milestone_type) { FactoryGirl.create(:type, is_milestone: true) } |
||||
|
||||
let(:project) { FactoryGirl.create(:project, types: [type, milestone_type]) } |
||||
let(:query_menu) { Components::WorkPackages::QueryMenu.new } |
||||
let(:settings_menu) { Components::WorkPackages::SettingsMenu.new } |
||||
let(:config_modal) { Components::Timelines::ConfigurationModal.new } |
||||
let(:wp_timeline) { Pages::WorkPackagesTimeline.new(project) } |
||||
|
||||
let(:custom_field) do |
||||
FactoryGirl.create( |
||||
:list_wp_custom_field, |
||||
name: "Ingredients", |
||||
multi_value: true, |
||||
types: [type], |
||||
projects: [project], |
||||
possible_values: ["ham", "onions", "pineapple", "mushrooms"] |
||||
) |
||||
end |
||||
|
||||
def custom_value_for(str) |
||||
custom_field.custom_options.find { |co| co.value == str }.try(:id) |
||||
end |
||||
|
||||
let(:work_package) do |
||||
FactoryGirl.create :work_package, |
||||
project: project, |
||||
type: type, |
||||
assigned_to: user, |
||||
start_date: '2017-08-21', |
||||
due_date: '2017-08-25', |
||||
subject: 'My subject', |
||||
custom_field_values: { custom_field.id => custom_value_for('onions') } |
||||
end |
||||
|
||||
let(:milestone_work_package) do |
||||
FactoryGirl.create :work_package, |
||||
project: project, |
||||
type: milestone_type, |
||||
start_date: '2017-08-30', |
||||
due_date: '2017-08-30', |
||||
subject: 'My milestone' |
||||
end |
||||
|
||||
before do |
||||
custom_field |
||||
milestone_work_package |
||||
work_package |
||||
login_as(user) |
||||
|
||||
wp_timeline.visit! |
||||
wp_timeline.expect_timeline! open: false |
||||
wp_timeline.toggle_timeline |
||||
end |
||||
|
||||
it 'shows and allows to configure labels' do |
||||
# Check default labels (bar type) |
||||
row = wp_timeline.timeline_row work_package.id |
||||
row.expect_labels left: nil, |
||||
right: nil, |
||||
farRight: 'My subject' |
||||
|
||||
row.expect_hovered_labels left: '2017-08-21', right: '2017-08-25' |
||||
|
||||
# Check default labels (milestone) |
||||
row = wp_timeline.timeline_row milestone_work_package.id |
||||
row.expect_labels left: nil, |
||||
right: nil, |
||||
farRight: 'My milestone' |
||||
row.expect_hovered_labels left: nil, right: '2017-08-30' |
||||
|
||||
# Modify label configuration |
||||
config_modal.open! |
||||
config_modal.expect_labels! left: '(none)', |
||||
right: '(none)', |
||||
farRight: 'Subject' |
||||
|
||||
config_modal.update_labels left: 'Assignee', |
||||
right: 'Type', |
||||
farRight: 'Status' |
||||
|
||||
# Check overriden labels |
||||
row = wp_timeline.timeline_row work_package.id |
||||
row.expect_labels left: user.name, |
||||
right: type.name, |
||||
farRight: work_package.status.name |
||||
|
||||
# Check default labels (milestone) |
||||
row = wp_timeline.timeline_row milestone_work_package.id |
||||
row.expect_labels left: '-', |
||||
right: milestone_type.name, |
||||
farRight: milestone_work_package.status.name |
||||
|
||||
# Save the query |
||||
settings_menu.open_and_save_query 'changed labels' |
||||
wp_timeline.expect_title 'changed labels' |
||||
|
||||
# Check the query |
||||
query = Query.last |
||||
expect(query.timeline_labels).to eq 'left' => 'assignee', |
||||
'right' => 'type', |
||||
'farRight' => 'status' |
||||
|
||||
# Revisit page |
||||
wp_timeline.visit_query query |
||||
wp_timeline.expect_work_package_listed(work_package, milestone_work_package) |
||||
wp_timeline.expect_timeline!(open: true) |
||||
|
||||
# Check overridden labels |
||||
row = wp_timeline.timeline_row work_package.id |
||||
row.expect_labels left: user.name, |
||||
right: type.name, |
||||
farRight: work_package.status.name |
||||
|
||||
# Check overridden labels (milestone) |
||||
row = wp_timeline.timeline_row milestone_work_package.id |
||||
row.expect_labels left: '-', |
||||
right: milestone_type.name, |
||||
farRight: milestone_work_package.status.name |
||||
|
||||
# Set labels to start|due|subject |
||||
config_modal.open! |
||||
config_modal.expect_labels! left: 'Assignee', |
||||
right: 'Type', |
||||
farRight: 'Status' |
||||
|
||||
config_modal.update_labels left: 'Start date', |
||||
right: 'Due date', |
||||
farRight: 'Subject' |
||||
|
||||
# Check overriden labels |
||||
row = wp_timeline.timeline_row work_package.id |
||||
row.expect_labels left: '2017-08-21', |
||||
right: '2017-08-25', |
||||
farRight: work_package.subject |
||||
|
||||
# Check default labels (milestone) |
||||
row = wp_timeline.timeline_row milestone_work_package.id |
||||
row.expect_labels left: nil, |
||||
right: '2017-08-30', |
||||
farRight: milestone_work_package.subject |
||||
|
||||
end |
||||
end |
@ -0,0 +1,66 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2017 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-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 doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
module Components |
||||
module Timelines |
||||
class ConfigurationModal |
||||
include Capybara::DSL |
||||
include RSpec::Matchers |
||||
|
||||
attr_reader :settings_menu |
||||
|
||||
def initialize |
||||
@settings_menu = ::Components::WorkPackages::SettingsMenu.new |
||||
end |
||||
|
||||
def open! |
||||
@settings_menu.open_and_choose 'Gantt chart ...' |
||||
end |
||||
|
||||
def get_select(position) |
||||
page.find("#modal-timelines-label-#{position}") |
||||
end |
||||
|
||||
def expect_labels!(left:, right:, farRight:) |
||||
expect(page).to have_select('modal-timelines-label-left', selected: left) |
||||
expect(page).to have_select('modal-timelines-label-right', selected: right) |
||||
expect(page).to have_select('modal-timelines-label-farRight', selected: farRight) |
||||
end |
||||
|
||||
def update_labels(left:, right:, farRight:) |
||||
get_select(:left).find('option', text: left).select_option |
||||
get_select(:right).find('option', text: right).select_option |
||||
get_select(:farRight).find('option', text: farRight).select_option |
||||
|
||||
page.within '.ng-modal-window' do |
||||
click_on 'Apply' |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,76 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2017 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-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 doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
module Components |
||||
module Timelines |
||||
class TimelineRow |
||||
include Capybara::DSL |
||||
include RSpec::Matchers |
||||
|
||||
attr_reader :container |
||||
|
||||
def initialize(container) |
||||
@container = container |
||||
end |
||||
|
||||
def hover! |
||||
@container.hover |
||||
end |
||||
|
||||
def expect_hovered_labels(left:, right:) |
||||
hover! |
||||
|
||||
unless left.nil? |
||||
expect(container).to have_selector(".labelHoverLeft.not-empty", text: left) |
||||
end |
||||
unless right.nil? |
||||
expect(container).to have_selector(".labelHoverRight.not-empty", text: right) |
||||
end |
||||
|
||||
expect(container).to have_selector(".labelLeft", visible: false) |
||||
expect(container).to have_selector(".labelRight", visible: false) |
||||
expect(container).to have_selector(".labelFarRight", visible: false) |
||||
end |
||||
|
||||
def expect_labels(left:, right:, farRight:) |
||||
{ |
||||
labelLeft: left, |
||||
labelRight: right, |
||||
labelFarRight: farRight |
||||
}.each do |className, text| |
||||
if text.nil? |
||||
expect(container).to have_selector(".#{className}", visible: :all) |
||||
expect(container).to have_no_selector(".#{className}.not-empty", wait: 0) |
||||
else |
||||
expect(container).to have_selector(".#{className}.not-empty", text: text) |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
Loading…
Reference in new issue