Merge branch 'dev' into feature/foundation-apps-framework

pull/2419/head
Alex Coles 10 years ago
commit ae7facfb4e
  1. 2
      app/controllers/api/experimental/work_packages_controller.rb
  2. 5
      app/controllers/work_packages_controller.rb
  3. 105
      app/models/work_package/csv_exporter.rb
  4. 4
      frontend/tests/integration/protractor.conf.js
  5. 6
      frontend/tests/integration/work-package-details-spec.js
  6. 14
      lib/redmine/access_control.rb
  7. 7
      spec/controllers/work_packages_controller_spec.rb
  8. 37
      spec/features/support/select_2.rb
  9. 87
      spec/features/support/work_package_table.rb
  10. 10
      spec/features/work_packages/select_work_package_row_spec.rb
  11. 54
      spec/features/work_packages/table_sorting_spec.rb

@ -115,7 +115,9 @@ module Api
def all_query_columns(query)
columns = query.columns.map(&:name) + [:id]
columns << query.group_by.to_sym if query.group_by
columns += query.sort_criteria.map { |x| x.first.to_sym }
columns
end

@ -221,11 +221,12 @@ class WorkPackagesController < ApplicationController
layout: 'angular' # !request.xhr?
end
format.csv do
serialized_work_packages = WorkPackage::Exporter.csv(@work_packages, @project)
serialized_work_packages = WorkPackage::Exporter.csv(@work_packages, @query)
charset = "charset=#{l(:general_csv_encoding).downcase}"
title = @query.new_record? ? l(:label_work_package_plural) : @query.name
send_data(serialized_work_packages, type: "text/csv; #{charset}; header=present",
filename: 'export.csv')
filename: "#{title}.csv")
end
format.pdf do
serialized_work_packages = WorkPackage::Exporter.pdf(@work_packages,

@ -30,60 +30,17 @@
module WorkPackage::CsvExporter
include Redmine::I18n
include CustomFieldsHelper
include ActionView::Helpers::TextHelper
include ActionView::Helpers::NumberHelper
def csv(work_packages, project = nil)
decimal_separator = l(:general_csv_decimal_separator)
def csv(work_packages, query)
export = CSV.generate(col_sep: l(:general_csv_separator)) do |csv|
# csv header fields
headers = ['#',
WorkPackage.human_attribute_name(:status),
WorkPackage.human_attribute_name(:project),
WorkPackage.human_attribute_name(:type),
WorkPackage.human_attribute_name(:priority),
WorkPackage.human_attribute_name(:subject),
WorkPackage.human_attribute_name(:assigned_to),
WorkPackage.human_attribute_name(:category),
WorkPackage.human_attribute_name(:fixed_version),
WorkPackage.human_attribute_name(:author),
WorkPackage.human_attribute_name(:start_date),
WorkPackage.human_attribute_name(:due_date),
WorkPackage.human_attribute_name(:done_ratio),
WorkPackage.human_attribute_name(:estimated_hours),
WorkPackage.human_attribute_name(:parent_work_package),
WorkPackage.human_attribute_name(:created_at),
WorkPackage.human_attribute_name(:updated_at)
]
# Export project custom fields if project is given
# otherwise export custom fields marked as "For all projects"
custom_fields = project.nil? ? WorkPackageCustomField.for_all : project.all_work_package_custom_fields
custom_fields.each { |f| headers << f.name }
# Description in the last column
headers << CustomField.human_attribute_name(:description)
headers = csv_headers(query)
csv << encode_csv_columns(headers)
# csv lines
work_packages.each do |work_package|
fields = [work_package.id,
work_package.status.name,
work_package.project.name,
work_package.type.name,
work_package.priority.name,
work_package.subject,
work_package.assigned_to,
work_package.category,
work_package.fixed_version,
work_package.author.name,
format_date(work_package.start_date),
format_date(work_package.due_date),
(Setting.work_package_done_ratio != 'disabled' ? work_package.done_ratio : ''),
work_package.estimated_hours.to_s.gsub('.', decimal_separator),
work_package.parent_id,
format_time(work_package.created_at),
format_time(work_package.updated_at)
]
custom_fields.each { |f| fields << show_value(work_package.custom_value_for(f)) }
fields << work_package.description
csv << encode_csv_columns(fields)
row = csv_row(work_package, query)
csv << encode_csv_columns(row)
end
end
@ -95,4 +52,52 @@ module WorkPackage::CsvExporter
Redmine::CodesetUtil.from_utf8(cell.to_s, encoding)
end
end
private
# fetch all headers
def csv_headers(query)
headers = []
headers << '#'
query.columns.each_with_index do |column, _|
headers << column.caption
end
headers << CustomField.human_attribute_name(:description)
headers
end
# fetch all row values
def csv_row(work_package, query)
row = query.columns.collect do |column|
csv_format_value(work_package, column)
end
if row.size > 0
row.unshift(work_package.id.to_s)
row << work_package.description.gsub(/\r/, '').gsub(/\n/, ' ')
end
row
end
def csv_format_value(work_package, column)
if column.is_a?(QueryCustomFieldColumn)
cv = work_package.custom_values.detect { |v| v.custom_field_id == column.custom_field.id }
show_value(cv)
else
value = work_package.send(column.name)
case value
when Date
format_date(value)
when Time
format_time(value)
else
value
end
end.to_s
end
end

@ -39,10 +39,10 @@ exports.config = {
specs: ['work-packages-spec.js', 'work-package-details-spec.js'],
allScriptsTimeout: 40000,
allScriptsTimeout: 500000,
mochaOpts: {
timeout: 40000,
timeout: 500000,
reporter: 'mocha-jenkins-reporter'
},

@ -41,9 +41,10 @@ describe('OpenProject', function() {
function loadPane(workPackageId) {
var page = new WorkPackageDetailsPane(workPackageId, 'overview');
page.get();
browser.waitForAngular();
}
context('pane itself', function() {
describe('pane itself', function() {
beforeEach(function() {
loadPane(819);
});
@ -245,8 +246,7 @@ describe('OpenProject', function() {
describe('activities', function() {
describe('overview tab', function() {
before(function() {
var page = new WorkPackageDetailsPane(819, 'overview');
page.get();
loadPane(819);
});
it('should render the last 3 activites', function() {

@ -79,15 +79,9 @@ module Redmine
end
class Mapper
def initialize
@project_module = nil
@project_modules_without_permissions = []
end
def permission(name, hash, options = {})
@permissions ||= []
options.merge!(project_module: @project_module)
@permissions << Permission.new(name, hash, options)
mapped_permissions << Permission.new(name, hash, options)
end
def project_module(name, _options = {})
@ -96,16 +90,16 @@ module Redmine
yield self
@project_module = nil
else
@project_modules_without_permissions << name
project_modules_without_permissions << name
end
end
def mapped_permissions
@permissions
@permissions ||= []
end
def project_modules_without_permissions
@project_modules_without_permissions
@project_modules_without_permissions ||= []
end
end

@ -193,12 +193,13 @@ describe WorkPackagesController, type: :controller do
before do
mock_csv = double('csv export')
expect(WorkPackage::Exporter).to receive(:csv).with(work_packages, project)
.and_return(mock_csv)
expect(WorkPackage::Exporter).to receive(:csv).with(work_packages, query)
.and_return(mock_csv)
expect(controller).to receive(:send_data).with(mock_csv,
type: 'text/csv; charset=utf-8; header=present',
filename: 'export.csv') do |*_args|
filename: "#{query.name}.csv") do |_|
# We need to render something because otherwise
# the controller will and he will not find a suitable template
controller.render text: 'success'

@ -0,0 +1,37 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2014 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.
#++
shared_context 'select2 helpers' do
def select2_select_option(select2_element, option_name)
select2_element.find('.select2-choice').click
within('.select2-drop') do
find('.select2-result-selectable', text: option_name).click
end
end
end

@ -0,0 +1,87 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2014 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.
#++
shared_context 'work package table helpers' do
def remove_wp_table_column(column_name)
click_button('Settings')
click_link('Columns ...')
# This is faster than has_selector but does not wait for anything.
# So if problems occur, switch to has_selector?
if find('.select2-choices').text.include?(column_name)
find('.select2-search-choice', text: column_name)
.click_link('select2-search-choice-close')
end
click_button('Apply')
end
def sort_wp_table_by(column_name, order: :desc)
click_button('Settings')
click_link('Sort by ...')
# If the modal was accessible, this would be elegant.
first_sort_criteria = all('.select2-container')[0]
select2_select_option(first_sort_criteria, column_name)
# If the modal was accessible, this would be elegant.
order_name = order == :desc ? 'Descending' : 'Ascending'
first_sort_order = all('.select2-container')[1]
select2_select_option(first_sort_order, order_name)
click_button('Apply')
end
def expect_work_packages_to_be_in_order(order)
within_wp_table do
preceeding_elements = order[0..-2]
following_elements = order[1..-1]
preceeding_elements.each_with_index do |wp_1, i|
wp_2 = following_elements[i]
expect(self).to have_selector("#work-package-#{wp_1.id} + \
#work-package-#{wp_2.id}")
end
end
end
def within_wp_table(&block)
within('.work-packages-table--results-container', &block)
end
def ensure_wp_page_is_loaded
# This is here to ensure the page is loaded completely before the next spec
# is run. As the filters are loaded late in the page, all Ajax requests
# have been answered by then. Without this, requests still running from
# the last spec, might expect data that has already been removed as
# preparation for the current spec.
find('#work-packages-filter-toggle-button').click
expect(page).to have_selector('.filter label', text: 'Status')
end
end

@ -46,6 +46,8 @@ describe 'Select work package row', type: :feature do
}
let(:work_packages_page) { WorkPackagesPage.new(project) }
include_context 'work package table helpers'
before do
allow(User).to receive(:current).and_return(user)
@ -57,13 +59,7 @@ describe 'Select work package row', type: :feature do
end
after do
# This is here to ensure the page is loaded completely before the next spec
# is run. As the filters are loaded late in the page, all Ajax requests
# have been answered by then. Without this, requests still running from
# the last spec, might expect data that has already been removed as
# preparation for the current spec.
find('#work-packages-filter-toggle-button').click
expect(page).to have_selector('.filter label', text: 'Status')
ensure_wp_page_is_loaded
end
describe 'Work package row selection', js: true do

@ -0,0 +1,54 @@
require 'spec_helper'
require 'features/work_packages/work_packages_page'
describe 'Select work package row', type: :feature do
let(:user) { FactoryGirl.create(:admin) }
let(:project) { FactoryGirl.create(:project) }
let(:work_package_1) do
FactoryGirl.create(:work_package, project: project)
end
let(:work_package_2) do
FactoryGirl.create(:work_package, project: project)
end
let(:work_packages_page) { WorkPackagesPage.new(project) }
let(:version_1) do
FactoryGirl.create(:version, project: project,
name: 'aaa_version')
end
let(:version_2) do
FactoryGirl.create(:version, project: project,
name: 'zzz_version')
end
before do
allow(User).to receive(:current).and_return(user)
work_package_1
work_package_2
work_packages_page.visit_index
end
include_context 'select2 helpers'
include_context 'work package table helpers'
after do
ensure_wp_page_is_loaded
end
context 'sorting by version', js: true do
before do
work_package_1.update_attribute(:fixed_version_id, version_2.id)
work_package_2.update_attribute(:fixed_version_id, version_1.id)
end
it 'sorts by version although version is not selected as a column' do
remove_wp_table_column('Version')
sort_wp_table_by('Version')
expect_work_packages_to_be_in_order([work_package_1, work_package_2])
end
end
end
Loading…
Cancel
Save