Merge pull request #7362 from opf/feature/csv_export_on_time_entry_index_only

Feature/csv export on time entry index only

[ci skip]
pull/7377/head
Oliver Günther 5 years ago committed by GitHub
commit 5a7ac87ac0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      Gemfile
  2. 3
      Gemfile.lock
  3. 7
      app/controllers/projects_controller.rb
  4. 76
      app/controllers/timelog_controller.rb
  5. 16
      app/helpers/projects_helper.rb
  6. 49
      app/views/projects/level_list.api.rabl
  7. 90
      app/views/timelog/_list.html.erb
  8. 28
      app/views/timelog/index.html.erb
  9. 30
      app/views/timelog/index.rabl
  10. 57
      app/views/timelog/show.rabl
  11. 34
      app/views/users/show.rabl
  12. 58
      config/initializers/rabl_hack.rb
  13. 53
      config/initializers/rabl_init.rb
  14. 52
      config/initializers/respond_to_api.rb
  15. 9
      config/locales/en.yml
  16. 2
      config/routes.rb
  17. 84
      spec/features/time_entry/root_time_entries_spec.rb
  18. 41
      spec/features/time_entry/time_entry_report_spec.rb
  19. 43
      spec/helpers/projects_helper_spec.rb
  20. 83
      spec/views/projects/level_list_api_json_spec.rb
  21. 88
      spec_legacy/functional/timelog_controller_spec.rb

@ -103,11 +103,6 @@ gem 'bcrypt', '~> 3.1.6'
gem 'multi_json', '~> 1.13.1'
gem 'oj', '~> 3.7.0'
# We rely on this specific version, which is the latest as of now (start of 2019),
# because we have to apply to it a bugfix which could break things in other versions.
# This can be removed as soon as said bugfix is integrated into rabl itself.
# See: config/initializers/rabl_hack.rb
gem 'rabl', '~> 0.14.0'
gem 'daemons'
gem 'delayed_job_active_record', '~> 4.1.1'

@ -650,8 +650,6 @@ GEM
pry (>= 0.9.11)
public_suffix (3.0.3)
puma (3.12.0)
rabl (0.14.0)
activesupport (>= 2.3.14)
rack (2.0.6)
rack-accept (0.4.5)
rack (>= 0.4)
@ -998,7 +996,6 @@ DEPENDENCIES
pry-rescue (~> 1.5.0)
pry-stack_explorer (~> 0.4.9.2)
puma (~> 3.12.0)
rabl (~> 0.14.0)
rack-attack (~> 5.4.2)
rack-mini-profiler
rack-protection (~> 2.0.0)

@ -199,7 +199,7 @@ class ProjectsController < ApplicationController
flash[:error] = I18n.t(:error_can_not_archive_project)
redirect_back fallback_location: projects_url
end
update_demo_project_settings @project, false
end
@ -231,10 +231,10 @@ class ProjectsController < ApplicationController
end
def level_list
@projects = Project.project_level_list(Project.visible)
projects = Project.project_level_list(Project.visible)
respond_to do |format|
format.api
format.json { render json: projects_level_list_json(projects) }
end
end
@ -242,6 +242,7 @@ class ProjectsController < ApplicationController
def find_optional_project
return true unless params[:id]
@project = Project.find(params[:id])
authorize
rescue ActiveRecord::RecordNotFound

@ -29,8 +29,6 @@
#++
class TimelogController < ApplicationController
menu_item :issues
before_action :disable_api, except: %i[index destroy]
before_action :find_work_package, only: %i[new create]
before_action :find_project, only: %i[new create]
@ -72,49 +70,8 @@ class TimelogController < ApplicationController
respond_to do |format|
format.html do
# Paginate results
@entry_count = TimeEntry
.visible
.includes(:project, :work_package)
.references(:projects)
.where(cond.conditions)
.count
@total_hours = TimeEntry
.visible
.includes(:project, :work_package)
.references(:projects)
.where(cond.conditions)
.distinct(false)
.sum(:hours).to_f
set_entries(cond)
gon.rabl template: 'app/views/timelog/index.rabl'
gon.project_id = @project.id if @project
gon.work_package_id = @issue.id if @issue
gon.sort_column = 'spent_on'
gon.sort_direction = 'desc'
gon.total_count = total_entry_count(cond)
gon.settings = client_preferences
render layout: layout_non_or_no_menu
end
format.json do
set_entries(cond)
gon.rabl template: 'app/views/timelog/index.rabl'
end
format.atom do
entries = TimeEntry
.visible
.includes(:project, :activity, :user, work_package: :type)
.references(:projects)
.where(cond.conditions)
.order("#{TimeEntry.table_name}.created_on DESC")
.limit(Setting.feeds_limit.to_i)
render_feed(entries, title: l(:label_spent_time))
end
format.csv do
# Export all entries
@entries = TimeEntry
@ -133,15 +90,6 @@ class TimelogController < ApplicationController
end
end
def show
respond_to do |format|
# TODO: Implement html response
format.html do
head 406
end
end
end
def new
@time_entry = new_time_entry(@project, @issue, permitted_params.time_entry.to_h)
@ -206,30 +154,6 @@ class TimelogController < ApplicationController
private
def total_entry_count(cond)
TimeEntry
.visible
.includes(:project, :activity, :user, work_package: :type)
.references(:projects)
.where(cond.conditions)
.count
end
def set_entries(cond)
# .visible introduces a distinct which we don't need here and which interferes
# with the order on postgresql.
# The distinct is therefore explicitly removed
@entries = TimeEntry
.visible
.includes(:project, :activity, :user, work_package: :type)
.references(:projects)
.where(cond.conditions)
.distinct(false)
.order(sort_clause)
.page(page_param)
.per_page(per_page_param)
end
def find_time_entry
@time_entry = TimeEntry.find(params[:id])
unless @time_entry.editable_by?(User.current)

@ -235,6 +235,22 @@ module ProjectsHelper
s
end
def projects_level_list_json(projects)
projects_list = projects.map do |item|
project = item[:project]
{
"id": project.id,
"name": project.name,
"identifier": project.identifier,
"has_children": !project.leaf?,
"level": item[:level]
}
end
{ projects: projects_list }
end
def projects_with_levels_order_sensitive(projects, &block)
if sorted_by_lft?
project_tree(projects, &block)

@ -1,49 +0,0 @@
#-- 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.
#++
collection @projects => "projects"
# This is a bit verbose as Project.level_list produces an array of hashes with the form:
# [
# { :project => <Project object>,
# :level => <hierarchy level> }
# ]
node(:id) { |p| p[:project].id }
node(:name) { |p| p[:project].name }
node(:identifier) { |p| p[:project].identifier }
node(:has_children) { |p| !p[:project].leaf? }
node(:level) { |p| p[:level] }
node(:created_on, if: lambda { |p| p[:project].created_on }) do |p|
p[:project].created_on.utc
end
node(:updated_on, if: lambda { |p| p[:project].updated_on }) do |p|
p[:project].updated_on.utc
end

@ -1,90 +0,0 @@
<%#-- copyright
OpenProject is a project management system.
Copyright (C) 2012-2018 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 docs/COPYRIGHT.rdoc for more details.
++#%>
<div class="generic-table--container">
<div class="generic-table--results-container">
<table class="generic-table">
<colgroup>
<col highlight-col>
<col highlight-col>
<col highlight-col>
<col highlight-col>
<col highlight-col>
<col highlight-col>
<col highlight-col>
<col>
</colgroup>
<thead>
<tr>
<%= sort_header_tag("spent_on", caption: TimeEntry.human_attribute_name(:spent_on)) %>
<%= sort_header_tag("user", caption: TimeEntry.human_attribute_name(:user)) %>
<%= sort_header_tag("activity", caption: TimeEntry.human_attribute_name(:activity)) %>
<%= sort_header_tag("project", caption: TimeEntry.human_attribute_name(:project)) %>
<%= sort_header_tag("work_package", caption: TimeEntry.human_attribute_name(:issue)) %>
<%= sort_header_tag("comments", caption: TimeEntry.human_attribute_name(:comments)) %>
<%= sort_header_tag("hours", caption: TimeEntry.human_attribute_name(:hours)) %>
<th>
<div class="generic-table--empty-header"></div>
</th>
</tr>
</thead>
<tbody>
<% entries.each do |entry| -%>
<tr class="time-entry">
<td class="spent_on"><%= format_date(entry.spent_on) %></td>
<td class="user"><%= link_to_user(entry.user) %></td>
<td class="activity"><%= h entry.activity %></td>
<td class="project"><%= link_to_project(entry.project) %></td>
<td class="subject">
<% if entry.work_package -%>
<%= entry.work_package.visible? ? link_to_work_package(entry.work_package, truncate: 50) : "##{entry.work_package.id}" -%>
<% end -%>
</td>
<td class="comments"><%= h entry.comments %></td>
<td class="hours"><%= html_hours("%.2f" % entry.hours) %></td>
<td align="center">
<% if entry.editable_by?(current_user) -%>
<%= link_to icon_wrapper('icon-context icon-edit', t(:button_edit)),
edit_time_entry_path(entry),
class: 'no-decoration-on-hover',
title: t(:button_edit) %>
<%= link_to icon_wrapper('icon-context icon-delete', t(:button_delete)),
time_entry_path(entry),
confirm: t(:text_are_you_sure),
class: 'no-decoration-on-hover',
method: :delete,
title: t(:button_delete) %>
<% end -%>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>

@ -27,10 +27,6 @@ See docs/COPYRIGHT.rdoc for more details.
++#%>
<%= nonced_javascript_tag do %>
<%= include_gon(need_tag: false) -%>
<% end %>
<%= toolbar title: l(:label_spent_time) do %>
<% if User.current.allowed_to?({controller: :timelog, action: :new}, @project) %>
<li class="toolbar-item">
@ -42,27 +38,21 @@ See docs/COPYRIGHT.rdoc for more details.
<% end %>
<% end %>
<%= form_tag(polymorphic_path([@issue || @project, :time_entries]), method: :get, id: 'query_form') do %>
<%= render partial: 'date_range' %>
<% end %>
<%= render 'time_entry_tabs' %>
<div class="total-hours">
<p><%= l(:label_total) %>: <%= html_hours(l_hours(@total_hours)) %></p>
<div class="notification-box -warning">
<a title="close" class="notification-box--close icon-context icon-close"></a>
<div class="notification-box--content">
<%= t('deprecations.time_entries') %>
</div>
</div>
<div>
<% if @entries.empty? %>
<%= no_results_box %>
<% else %>
<%= render partial: 'list', locals: { entries: @entries }%>
<%= other_formats_links do |f| %>
<%= f.link_to 'Atom', url: permitted_params.timelog.to_h.merge({issue_id: @issue, key: User.current.rss_key}) %>
<%= f.link_to 'CSV', url: permitted_params.timelog.to_h %>
<% end %>
<%= other_formats_links do |f| %>
<%= f.link_to 'CSV', url: permitted_params.timelog.to_h %>
<% end %>
</div>
<% html_title l(:label_spent_time) %>
<% content_for :header_tags do %>
<%= auto_discovery_link_tag(:atom, {issue_id: @issue, format: 'atom', key: User.current.rss_key}, title: l(:label_spent_time)) %>
<% end %>

@ -1,30 +0,0 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2013 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.
#++
collection @entries => "timeEntries"
extends 'timelog/show'

@ -1,57 +0,0 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2013 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.
#++
object @entry => "timeEntry"
attributes :id,
:spent_on,
:comments,
:hours
node :user do |e|
partial('users/show', object: e.user)
end
child :activity => :activity do
attributes :name
end
child :project do
attributes :id, :name
end
child :work_package do
attributes :id, :subject
node :isVisible do |w|
w.visible?
end
end
node :isEditable do |e|
e.editable_by?(User.current)
end

@ -1,34 +0,0 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2013 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.
#++
object @user
attributes :id,
:name,
:firstname,
:lastname

@ -1,58 +0,0 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 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 docs/COPYRIGHT.rdoc for more details.
#++
##
# Hack against rabl 0.13.0 which applies config.include_child_root to
# #collection as well as to #child calls as you would expect.
#
module Rabl
class Engine
def to_hash_with_hack(options = {})
if is_collection?(@_data_object)
options[:building_collection] = true
end
to_hash_without_hack(options)
end
alias_method :to_hash_without_hack, :to_hash
alias_method :to_hash, :to_hash_with_hack
end
class Builder
def to_hash_with_hack(object = nil, settings = nil, options = nil)
if @options[:building_collection] && !@options[:child_root]
@options[:root_name] = false
end
to_hash_without_hack(object, settings, options)
end
alias_method :to_hash_without_hack, :to_hash
alias_method :to_hash, :to_hash_with_hack
end
end

@ -1,53 +0,0 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 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 docs/COPYRIGHT.rdoc for more details.
#++
Rabl.configure do |config|
config.json_engine = ::Oj # Class with #dump class method (defaults JSON)
config.include_json_root = true
config.include_xml_root = false
config.include_child_root = false
config.xml_options = { dasherize: false, skip_types: false }
# Commented as these are defaults
# config.cache_all_output = false
# config.cache_sources = Rails.env != 'development' # Defaults to false
# config.cache_engine = Rabl::CacheEngine.new # Defaults to Rails cache
# config.perform_caching = false
# config.escape_all_output = false
# config.msgpack_engine = nil # Defaults to ::MessagePack
# config.bson_engine = nil # Defaults to ::BSON
# config.plist_engine = nil # Defaults to ::Plist::Emit
# config.include_msgpack_root = true
# config.include_bson_root = true
# config.include_plist_root = true
# config.enable_json_callbacks = false
# config.view_paths = []
# config.raise_on_missing_attribute = true # Defaults to false
# config.replace_nil_values_with_empty_strings = true # Defaults to false
end

@ -1,52 +0,0 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 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 docs/COPYRIGHT.rdoc for more details.
#++
module ActionView
class Resolver
def find_all(name, prefix = nil, partial = false, details = {}, key = nil, locals = [])
cached(key, [name, prefix, partial], details, locals) do
if details[:formats] & [:xml, :json]
details = details.dup
details[:formats] = details[:formats].dup + [:api]
end
find_templates(name, prefix, partial, details)
end
end
end
end
module ActionController
module MimeResponds
class Collector
def api(&block)
any(:xml, :json, &block)
end
end
end
end

@ -132,14 +132,7 @@ en:
single: 'or'
deprecations:
old_timeline:
replacement: "This timelines module is being replaced by the interactive timeline embedded into the work packages module."
removal: "This module is going to be removed with OpenProject 8.0. The configuration for this view will NOT be migrated to the work package view."
further_information_before: "Please take a look at"
link_name: "how to migrate to the new timeline."
further_information_after: ""
calendar:
removal: "Please note: This module is going to be removed with OpenProject 8.0."
time_entries: "This time entries view is superseded by the 'Cost reports' module. This view now only supports exporting time entry information to csv. For interactive filtering, please activate the 'Cost reports' module in the project settings."
global_search:
overwritten_tabs:

@ -214,7 +214,7 @@ OpenProject::Application.routes.draw do
namespace :time_entries do
resource :report, controller: 'reports', only: [:show]
end
resources :time_entries, controller: 'timelog'
resources :time_entries, controller: 'timelog', except: [:show]
# Match everything to be the ID of the wiki page except the part that
# is reserved for the format. This assumes that we have only two formats:

@ -1,84 +0,0 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 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 docs/COPYRIGHT.rdoc for more details.
#++
require "spec_helper"
describe "/time_entries", type: :feature do
let(:user) { FactoryBot.create :admin }
describe "sorting time entries", js: true do
let(:projects) { FactoryBot.create_list :project, 3 }
let(:comments) { ["TE 2", "TE 1", "TE 3"] }
let(:hours) { [2, 5, 1] }
let!(:time_entries) do
comments.zip(projects).zip(hours).map do |comment_and_project, hours|
comment, project = comment_and_project
work_package = FactoryBot.create :work_package, project: project
FactoryBot.create :time_entry,
comments: comment,
work_package: work_package,
project: project,
hours: hours
end
end
def shown_comments
page.all("td.comments").map(&:text)
end
def shown_hours
page.all("td.hours").map(&:text).map(&:to_i)
end
before do
login_as user
visit time_entries_path
end
it "should allow sorting the time entries" do
expect(page).to have_selector('td.comments', text: "TE 2")
expect(page).to have_selector('td.comments', text: "TE 1")
expect(page).to have_selector('td.comments', text: "TE 3")
click_on "Comment"
expect(page).to have_selector('td.comments', count: 3)
expect(shown_comments).to eq comments.sort
click_on "Hours"
expect(page).to have_selector('td.comments', count: 3)
expect(shown_hours).to eq hours.sort
click_on "Hours"
expect(page).to have_selector('td.comments', count: 3)
expect(shown_hours).to eq hours.sort.reverse
end
end
end

@ -34,19 +34,19 @@ describe 'time entry report', type: :feature, js: true do
let(:work_package) { FactoryBot.create(:work_package, project: project) }
let!(:project_time_entry) {
FactoryBot.create_list(:time_entry,
2,
project: project,
work_package: work_package,
hours: 2.5)
2,
project: project,
work_package: work_package,
hours: 2.5)
}
let(:project2) { FactoryBot.create(:project) }
let(:work_package2) { FactoryBot.create(:work_package, project: project2) }
let!(:project_time_entry2) {
FactoryBot.create(:time_entry,
project: project2,
spent_on: 1.year.ago,
work_package: work_package2,
hours: 5.0)
project: project2,
spent_on: 1.year.ago,
work_package: work_package2,
hours: 5.0)
}
let(:user) { FactoryBot.create(:admin) }
@ -54,31 +54,6 @@ describe 'time entry report', type: :feature, js: true do
login_as(user)
end
describe 'details' do
context 'for a single project' do
before do
visit project_time_entries_path(project.identifier)
end
it 'should list the time entries' do
expect(page).to have_selector('tr.time-entry', count: 2)
expect(page).to have_selector('.time-entry .hours', text: 2.5, count: 2)
end
end
context 'for all projects' do
before do
visit time_entries_path
end
it 'should list the time entries' do
expect(page).to have_selector('tr.time-entry', count: 3)
expect(page).to have_selector('.time-entry .hours', text: 2.5, count: 2)
expect(page).to have_selector('.time-entry .hours', text: 5.0)
end
end
end
describe 'reports' do
before do
visit time_entries_report_path

@ -41,7 +41,7 @@ describe ProjectsHelper, type: :helper do
User.current = nil
end
let(:test_project) { FactoryBot.create :valid_project }
let(:test_project) { FactoryBot.create :valid_project }
describe 'a version' do
let(:version) { FactoryBot.create :version, project: test_project }
@ -170,4 +170,45 @@ describe ProjectsHelper, type: :helper do
end
end
end
describe '#projects_level_list_json' do
subject { helper.projects_level_list_json(projects).to_json }
let(:projects) { [] }
describe 'with no project available' do
it 'renders an empty projects document' do
is_expected.to have_json_size(0).at_path('projects')
end
end
describe 'with some projects available' do
let(:projects) do
p1 = FactoryBot.build(:project, name: 'P1')
# a result from Project.project_level_list
[{ project: p1,
level: 0 },
{ project: FactoryBot.build(:project, name: 'P2', parent: p1),
level: 1 },
{ project: FactoryBot.build(:project, name: 'P3'),
level: 0 }]
end
it 'renders a projects document with the size of 3 of type array' do
is_expected.to have_json_size(3).at_path('projects')
end
it 'renders all three projects' do
is_expected.to be_json_eql('P1'.to_json).at_path('projects/0/name')
is_expected.to be_json_eql('P2'.to_json).at_path('projects/1/name')
is_expected.to be_json_eql('P3'.to_json).at_path('projects/2/name')
end
it 'renders the project levels' do
is_expected.to be_json_eql(0.to_json).at_path('projects/0/level')
is_expected.to be_json_eql(1.to_json).at_path('projects/1/level')
is_expected.to be_json_eql(0.to_json).at_path('projects/2/level')
end
end
end
end

@ -1,83 +0,0 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 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 docs/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
describe '/projects/level_list.api.rabl', type: :view do
before do
params[:format] = 'json'
end
subject { rendered }
describe 'with no project available' do
it 'renders an empty projects document' do
assign(:projects, [])
render
is_expected.to have_json_size(0).at_path('projects')
end
end
describe 'with some projects available' do
let(:projects) do
p1 = FactoryBot.build(:project, name: 'P1')
# a result from Project.project_level_list
[{ project: p1,
level: 0 },
{ project: FactoryBot.build(:project, name: 'P2', parent: p1),
level: 1 },
{ project: FactoryBot.build(:project, name: 'P3'),
level: 0 }]
end
before do
assign(:projects, projects)
render
end
subject { rendered }
it 'renders a projects document with the size of 3 of type array' do
is_expected.to have_json_size(3).at_path('projects')
end
it 'renders all three projects' do
is_expected.to be_json_eql('P1'.to_json).at_path('projects/0/name')
is_expected.to be_json_eql('P2'.to_json).at_path('projects/1/name')
is_expected.to be_json_eql('P3'.to_json).at_path('projects/2/name')
end
it 'renders the project levels' do
is_expected.to be_json_eql(0.to_json).at_path('projects/0/level')
is_expected.to be_json_eql(1.to_json).at_path('projects/1/level')
is_expected.to be_json_eql(0.to_json).at_path('projects/2/level')
end
end
end

@ -92,92 +92,4 @@ describe TimelogController, type: :controller do
assert_equal 2, entry.work_package_id
assert_equal 2, entry.user_id
end
it 'should index all projects' do
get :index
assert_response :success
assert_template 'index'
refute_nil assigns(:total_hours)
assert_equal '162.90', '%.2f' % assigns(:total_hours)
assert_select 'form',
attributes: { action: '/time_entries', id: 'query_form' }
end
it 'should index at project level' do
get :index, params: { project_id: 'ecookbook' }
assert_response :success
assert_template 'index'
refute_nil assigns(:entries)
assert_equal 4, assigns(:entries).size
# project and subproject
assert_equal [1, 3], assigns(:entries).map(&:project_id).uniq.sort
refute_nil assigns(:total_hours)
assert_equal '162.90', '%.2f' % assigns(:total_hours)
# display all time by default
assert_equal nil, assigns(:from)
assert_equal nil, assigns(:to)
assert_select 'form',
attributes: { action: '/projects/ecookbook/time_entries', id: 'query_form' }
end
it 'should index at project level with date range' do
get :index, params: { project_id: 'ecookbook', from: '2007-03-20', to: '2007-04-30' }
assert_response :success
assert_template 'index'
refute_nil assigns(:entries)
assert_equal 3, assigns(:entries).size
refute_nil assigns(:total_hours)
assert_equal '12.90', '%.2f' % assigns(:total_hours)
assert_equal '2007-03-20'.to_date, assigns(:from)
assert_equal '2007-04-30'.to_date, assigns(:to)
assert_select 'form',
attributes: { action: '/projects/ecookbook/time_entries', id: 'query_form' }
end
it 'should index at project level with period' do
get :index, params: { project_id: 'ecookbook', period: '7_days' }
assert_response :success
assert_template 'index'
refute_nil assigns(:entries)
refute_nil assigns(:total_hours)
assert_equal Date.today - 7, assigns(:from)
assert_equal Date.today, assigns(:to)
assert_select 'form',
attributes: { action: '/projects/ecookbook/time_entries', id: 'query_form' }
end
it 'should index one day' do
get :index, params: { project_id: 'ecookbook', from: '2007-03-23', to: '2007-03-23' }
assert_response :success
assert_template 'index'
refute_nil assigns(:total_hours)
assert_equal '4.25', '%.2f' % assigns(:total_hours)
assert_select 'form',
attributes: { action: '/projects/ecookbook/time_entries', id: 'query_form' }
end
it 'should index at issue level' do
get :index, params: { work_package_id: 1 }
assert_response :success
assert_template 'index'
refute_nil assigns(:entries)
assert_equal 2, assigns(:entries).size
refute_nil assigns(:total_hours)
assert_equal 154.25, assigns(:total_hours)
# display all time based on what's been logged
assert_equal nil, assigns(:from)
assert_equal nil, assigns(:to)
assert_select 'form',
attributes: { action: work_package_time_entries_path(1), id: 'query_form' }
end
it 'should index atom feed' do
TimeEntry.all.each(&:recreate_initial_journal!)
get :index, params: { project_id: 1, format: 'atom' }
assert_response :success
assert_equal 'application/atom+xml', response.content_type
refute_nil assigns(:items)
assert assigns(:items).first.is_a?(TimeEntry)
end
end

Loading…
Cancel
Save