Merge branch 'dev' into timeline

pull/5181/head
Roman Roelofsen 8 years ago
commit 5a5170455d
  1. 18
      app/controllers/api/experimental/concerns/v3_naming.rb
  2. 44
      app/helpers/queries_helper.rb
  3. 4
      app/models/enterprise_token.rb
  4. 28
      app/models/queries/available_filters.rb
  5. 11
      app/models/queries/base_filter.rb
  6. 3
      app/models/queries/filter_serializer.rb
  7. 20
      app/models/queries/work_packages/filter/category_filter.rb
  8. 15
      app/models/queries/work_packages/filter/custom_field_filter.rb
  9. 20
      app/models/queries/work_packages/filter/group_filter.rb
  10. 10
      app/models/queries/work_packages/filter/principal_base_filter.rb
  11. 19
      app/models/queries/work_packages/filter/priority_filter.rb
  12. 10
      app/models/queries/work_packages/filter/project_filter.rb
  13. 10
      app/models/queries/work_packages/filter/role_filter.rb
  14. 20
      app/models/queries/work_packages/filter/status_filter.rb
  15. 20
      app/models/queries/work_packages/filter/subproject_filter.rb
  16. 10
      app/models/queries/work_packages/filter/type_filter.rb
  17. 10
      app/models/queries/work_packages/filter/version_filter.rb
  18. 23
      app/models/query.rb
  19. 167
      app/services/api/v3/parse_query_params_service.rb
  20. 55
      app/services/api/v3/update_query_from_v3_params_service.rb
  21. 49
      app/services/api/v3/work_package_collection_from_query_params_service.rb
  22. 155
      app/services/api/v3/work_package_collection_from_query_service.rb
  23. 84
      app/services/update_query_from_params_service.rb
  24. 2
      config/initializers/homescreen.rb
  25. 3
      config/locales/en.yml
  26. 1088
      doc/apiv3/endpoints/queries.apib
  27. 4
      doc/apiv3/endpoints/work-packages.apib
  28. 2
      doc/operation_guides/system_requirements.md
  29. 2
      frontend/app/components/routing/wp-view-base/wp-view-base.controller.ts
  30. 4
      frontend/app/components/work-packages/work-package-cache.service.test.ts
  31. 2
      frontend/app/components/work-packages/wp-display-attr/wp-display-attr.directive.ts
  32. 2
      frontend/app/components/work-packages/wp-single-view/wp-single-view.directive.ts
  33. 2
      frontend/app/components/work-packages/wp-subject/wp-subject.directive.ts
  34. 2
      frontend/app/components/work-packages/wp-watcher-button/wp-watcher-button.directive.ts
  35. 2
      frontend/app/components/wp-copy/wp-copy.controller.ts
  36. 2
      frontend/app/components/wp-create/wp-create.controller.ts
  37. 2
      frontend/app/components/wp-edit/wp-edit-field/wp-edit-field.directive.ts
  38. 2
      frontend/app/components/wp-edit/wp-edit-form.directive.ts
  39. 2
      frontend/app/components/wp-panels/activity-panel/activity-panel.directive.ts
  40. 2
      frontend/app/components/wp-panels/watchers-panel/watchers-panel.controller.ts
  41. 4
      frontend/app/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.directive.ts
  42. 4
      frontend/app/components/wp-relations/wp-relations.directive.ts
  43. 6
      frontend/app/helpers/reactive-fassade.ts
  44. 19
      lib/api/decorators/single.rb
  45. 14
      lib/api/root.rb
  46. 1
      lib/api/utilities/grape_helper.rb
  47. 14
      lib/api/utilities/property_name_converter.rb
  48. 38
      lib/api/utilities/property_name_converter_work_package_dummy.rb
  49. 47
      lib/api/utilities/query_filters_name_converter.rb
  50. 9
      lib/api/utilities/query_filters_name_converter_context.rb
  51. 47
      lib/api/utilities/wp_property_name_converter.rb
  52. 67
      lib/api/v3/queries/columns/query_column_representer.rb
  53. 66
      lib/api/v3/queries/columns/query_columns_api.rb
  54. 46
      lib/api/v3/queries/filters/query_filter_decorator.rb
  55. 96
      lib/api/v3/queries/filters/query_filter_instance_representer.rb
  56. 65
      lib/api/v3/queries/filters/query_filter_representer.rb
  57. 71
      lib/api/v3/queries/filters/query_filters_api.rb
  58. 67
      lib/api/v3/queries/group_bys/query_group_by_representer.rb
  59. 66
      lib/api/v3/queries/group_bys/query_group_bys_api.rb
  60. 63
      lib/api/v3/queries/operators/query_operator_representer.rb
  61. 57
      lib/api/v3/queries/operators/query_operators_api.rb
  62. 16
      lib/api/v3/queries/queries_api.rb
  63. 90
      lib/api/v3/queries/query_params_representer.rb
  64. 131
      lib/api/v3/queries/query_representer.rb
  65. 73
      lib/api/v3/queries/sort_bys/query_sort_by_representer.rb
  66. 68
      lib/api/v3/queries/sort_bys/query_sort_bys_api.rb
  67. 99
      lib/api/v3/queries/sort_bys/sort_by_decorator.rb
  68. 20
      lib/api/v3/utilities/path_helper.rb
  69. 210
      lib/api/v3/work_packages/work_package_list_helpers.rb
  70. 15
      lib/api/v3/work_packages/work_packages_api.rb
  71. 11
      lib/api/v3/work_packages/work_packages_by_project_api.rb
  72. 2
      lib/redmine/menu_manager/top_menu/help_menu.rb
  73. 4
      package.json
  74. 9
      spec/lib/api/utilities/property_name_converter_spec.rb
  75. 90
      spec/lib/api/v3/queries/columns/query_column_representer_spec.rb
  76. 111
      spec/lib/api/v3/queries/filters/query_filter_instance_representer_spec.rb
  77. 106
      spec/lib/api/v3/queries/filters/query_filter_representer_spec.rb
  78. 90
      spec/lib/api/v3/queries/group_bys/query_group_by_representer_spec.rb
  79. 66
      spec/lib/api/v3/queries/operators/query_operator_representer_spec.rb
  80. 373
      spec/lib/api/v3/queries/query_representer_spec.rb
  81. 130
      spec/lib/api/v3/queries/sort_bys/query_sort_by_representer_spec.rb
  82. 30
      spec/lib/api/v3/utilities/path_helper_spec.rb
  83. 18
      spec/models/enterprise_token_spec.rb
  84. 6
      spec/models/queries/available_filters_spec.rb
  85. 25
      spec/models/queries/work_packages/filter/category_filter_spec.rb
  86. 2
      spec/models/queries/work_packages/filter/created_at_filter_spec.rb
  87. 104
      spec/models/queries/work_packages/filter/custom_field_filter_spec.rb
  88. 2
      spec/models/queries/work_packages/filter/due_date_filter_spec.rb
  89. 2
      spec/models/queries/work_packages/filter/estimated_hours_filter_spec.rb
  90. 24
      spec/models/queries/work_packages/filter/group_filter_spec.rb
  91. 24
      spec/models/queries/work_packages/filter/priority_filter_spec.rb
  92. 25
      spec/models/queries/work_packages/filter/project_filter_spec.rb
  93. 26
      spec/models/queries/work_packages/filter/responsible_filter_spec.rb
  94. 24
      spec/models/queries/work_packages/filter/role_filter_spec.rb
  95. 2
      spec/models/queries/work_packages/filter/start_date_filter_spec.rb
  96. 23
      spec/models/queries/work_packages/filter/status_filter_spec.rb
  97. 3
      spec/models/queries/work_packages/filter/subject_filter_spec.rb
  98. 27
      spec/models/queries/work_packages/filter/subproject_filter_spec.rb
  99. 25
      spec/models/queries/work_packages/filter/type_filter_spec.rb
  100. 2
      spec/models/queries/work_packages/filter/updated_at_filter_spec.rb
  101. Some files were not shown because too many files have changed in this diff Show More

@ -28,9 +28,13 @@
module Api::Experimental::Concerns::V3Naming
def v3_to_internal_name(string, append_id: true)
API::Utilities::PropertyNameConverter.to_ar_name(string,
context: context_dummy,
refer_to_ids: append_id)
if append_id
API::Utilities::QueryFiltersNameConverter.to_ar_name(string,
refer_to_ids: append_id)
else
API::Utilities::WpPropertyNameConverter.to_ar_name(string,
refer_to_ids: append_id)
end
end
def internal_to_v3_name(string)
@ -86,12 +90,4 @@ module Api::Experimental::Concerns::V3Naming
json_query
end
private
def context_dummy
# memorize the work package because
# initializing a work package queries the db
@context_dummy ||= API::Utilities::PropertyNameConverterQueryContext.new
end
end

@ -47,9 +47,9 @@ module QueriesHelper
def add_filter_from_params(query, filters: params)
query.filters = []
query.add_filters(
fields_from_params(query, filters),
operators_from_params(query, filters),
values_from_params(query, filters)
fields_from_params(filters),
operators_from_params(filters),
values_from_params(filters)
)
end
@ -113,11 +113,7 @@ module QueriesHelper
def column_names_from_params(params)
names = params[:c] || (params[:query] && params[:query][:column_names])
if names
context = WorkPackage.new
names.map { |name| converter.to_ar_name name, context: context }
end
names.map { |name| attribute_converter.to_ar_name name } if names
end
def visible_queries
@ -138,24 +134,24 @@ module QueriesHelper
params[:group_by] || params[:groupBy] || params[:g]
end
def fields_from_params(query, params)
fix_field_array(query, params[:fields] || params[:f]).compact
def fields_from_params(params)
fix_field_array(params[:fields] || params[:f]).compact
end
def operators_from_params(query, params)
fix_field_hash(query, params[:operators] || params[:op])
def operators_from_params(params)
fix_field_hash(params[:operators] || params[:op])
end
def values_from_params(query, params)
fix_field_hash(query, params[:values] || params[:v])
def values_from_params(params)
fix_field_hash(params[:values] || params[:v])
end
def fix_field_hash(query, field_hash)
def fix_field_hash(field_hash)
return nil if field_hash.nil?
names = field_hash.keys
entries = names
.zip(fix_field_array(query, names))
.zip(fix_field_array(names))
.select { |_name, field| field.present? }
.map { |name, field| [field, field_hash[name]] }
@ -178,22 +174,18 @@ module QueriesHelper
# @param field_names [Array] Field names as read from the params.
# @return [Array] Returns a list of fixed field names. The list may contain nil values
# for fields which could not be found.
def fix_field_array(query, field_names)
def fix_field_array(field_names)
return [] if field_names.nil?
available_keys = query.available_filters.map(&:name)
field_names
.map { |name| converter.to_ar_name name, context: converter_context, refer_to_ids: true }
.map { |name| available_keys.find { |k| name =~ /#{k}(s|_id)?$/ } }
.map { |name| filter_converter.to_ar_name name, refer_to_ids: true }
end
def converter
API::Utilities::PropertyNameConverter
def filter_converter
API::Utilities::QueryFiltersNameConverter
end
def converter_context
# memoize to reduce overhead of WorkPackage.new
@fix_field_array_wp ||= API::Utilities::PropertyNameConverterQueryContext.new
def attribute_converter
API::Utilities::WpPropertyNameConverter
end
end

@ -37,8 +37,8 @@ class EnterpriseToken < ActiveRecord::Base
Authorization::EnterpriseService.new(current).call(action).result
end
def show_banners
!current || current.expired?
def show_banners?
OpenProject::Configuration.ee_manager_visible? && (!current || current.expired?)
end
def set_current_token

@ -28,6 +28,22 @@
#++
module Queries::AvailableFilters
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def registered_filters
Queries::Register.filters[self]
end
def find_registered_filter(key)
registered_filters.detect do |f|
f.key === key.to_sym
end
end
end
def available_filters
uninitialized = registered_filters - already_initialized_filters
@ -71,9 +87,7 @@ module Queries::AvailableFilters
end
def find_registered_filter(key)
registered_filters.detect do |f|
f.key === key.to_sym
end
self.class.find_registered_filter(key)
end
def find_initialized_filter(key)
@ -86,15 +100,11 @@ module Queries::AvailableFilters
@already_initialized_filters ||= []
end
def registered_filters
@registered_filters ||= filter_register
end
def initialized_filters
@initialized_filters ||= []
end
def filter_register
Queries::Register.filters[self.class]
def registered_filters
self.class.registered_filters
end
end

@ -157,6 +157,17 @@ class Queries::BaseFilter
@values = Array(values).reject(&:blank?).map(&:to_s)
end
# Does the filter filter on other models, e.g. User, Status
def ar_object_filter?
false
end
# List of objects the value represents
# is empty if the filter does not filter on other AR objects
def value_objects
[]
end
protected
def validate_inclusion_of_operator

@ -29,6 +29,7 @@
module Queries::FilterSerializer
extend Queries::AvailableFilters
extend Queries::AvailableFilters::ClassMethods
def self.load(serialized_filter_hash)
return [] if serialized_filter_hash.nil?
@ -46,7 +47,7 @@ module Queries::FilterSerializer
YAML.dump ((filters || []).map(&:to_hash).reduce(:merge) || {}).stringify_keys
end
def self.filter_register
def self.registered_filters
Queries::Register.filters[Query]
end
end

@ -31,9 +31,7 @@ class Queries::WorkPackages::Filter::CategoryFilter <
Queries::WorkPackages::Filter::WorkPackageFilter
def allowed_values
@allowed_values ||= begin
project.categories.map { |s| [s.name, s.id.to_s] }
end
all_project_categories.map { |s| [s.name, s.id.to_s] }
end
def available?
@ -52,4 +50,20 @@ class Queries::WorkPackages::Filter::CategoryFilter <
def self.key
:category_id
end
def value_objects
int_values = values.map(&:to_i)
all_project_categories.select { |c| int_values.include?(c.id) }
end
def ar_object_filter?
true
end
private
def all_project_categories
@all_project_categories ||= project.categories
end
end

@ -110,6 +110,21 @@ class Queries::WorkPackages::Filter::CustomFieldFilter <
end
end
def ar_object_filter?
%w{user version}.include? custom_field.field_format
end
def value_objects
case custom_field.field_format
when 'user'
User.find(values)
when 'version'
Version.find(values)
else
super
end
end
private
def custom_field_valid

@ -29,9 +29,7 @@
class Queries::WorkPackages::Filter::GroupFilter < Queries::WorkPackages::Filter::WorkPackageFilter
def allowed_values
@allowed_values ||= begin
::Group.all.map { |g| [g.name, g.id.to_s] }
end
all_groups.map { |g| [g.name, g.id.to_s] }
end
def available?
@ -53,4 +51,20 @@ class Queries::WorkPackages::Filter::GroupFilter < Queries::WorkPackages::Filter
def self.key
:member_of_group
end
def ar_object_filter?
true
end
def value_objects
value_ints = values.map(&:to_i)
all_groups.select { |g| value_ints.include?(g.id) }
end
private
def all_groups
@all_groups ||= ::Group.all
end
end

@ -33,6 +33,16 @@ class Queries::WorkPackages::Filter::PrincipalBaseFilter <
User.current.logged? || allowed_values.any?
end
def value_objects
prepared_values = values.map { |value| value == 'me' ? User.current : value }
Principal.find(prepared_values)
end
def ar_object_filter?
true
end
private
def me_value

@ -27,11 +27,10 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
class Queries::WorkPackages::Filter::PriorityFilter < Queries::WorkPackages::Filter::WorkPackageFilter
class Queries::WorkPackages::Filter::PriorityFilter <
Queries::WorkPackages::Filter::WorkPackageFilter
def allowed_values
@allowed_values ||= begin
priorities.map { |s| [s.name, s.id.to_s] }
end
priorities.map { |s| [s.name, s.id.to_s] }
end
def available?
@ -50,9 +49,19 @@ class Queries::WorkPackages::Filter::PriorityFilter < Queries::WorkPackages::Fil
:priority_id
end
def ar_object_filter?
true
end
def value_objects
value_ints = values.map(&:to_i)
priorities.select { |p| value_ints.include? p.id }
end
private
def priorities
IssuePriority.active
@priorities ||= IssuePriority.active
end
end

@ -56,6 +56,16 @@ class Queries::WorkPackages::Filter::ProjectFilter < Queries::WorkPackages::Filt
:project_id
end
def ar_object_filter?
true
end
def value_objects
value_ints = values.map(&:to_i)
visible_projects.select { |p| value_ints.include?(p.id) }
end
private
def visible_projects

@ -54,6 +54,16 @@ class Queries::WorkPackages::Filter::RoleFilter < Queries::WorkPackages::Filter:
:assigned_to_role
end
def ar_object_filter?
true
end
def value_objects
value_ints = values.map(&:to_i)
roles.select { |r| value_ints.include?(r.id) }
end
private
def roles

@ -29,9 +29,7 @@
class Queries::WorkPackages::Filter::StatusFilter < Queries::WorkPackages::Filter::WorkPackageFilter
def allowed_values
@allowed_values ||= begin
Status.all.map { |s| [s.name, s.id.to_s] }
end
all_statuses.map { |s| [s.name, s.id.to_s] }
end
def available?
@ -49,4 +47,20 @@ class Queries::WorkPackages::Filter::StatusFilter < Queries::WorkPackages::Filte
def self.key
:status_id
end
def value_objects
values_ids = values.map(&:to_i)
all_statuses.select { |status| values_ids.include?(status.id) }
end
def ar_object_filter?
true
end
private
def all_statuses
@all_statuses ||= Status.all
end
end

@ -31,14 +31,14 @@ class Queries::WorkPackages::Filter::SubprojectFilter <
Queries::WorkPackages::Filter::WorkPackageFilter
def allowed_values
@allowed_values ||= begin
project.descendants.visible.map { |s| [s.name, s.id.to_s] }
visible_subprojects.map { |s| [s.name, s.id.to_s] }
end
end
def available?
project &&
!project.leaf? &&
project.descendants.visible.exists?
visible_subprojects.exists?
end
def type
@ -56,4 +56,20 @@ class Queries::WorkPackages::Filter::SubprojectFilter <
def self.key
:subproject_id
end
def ar_object_filter?
true
end
def value_objects
value_ints = values.map(&:to_i)
visible_subprojects.select { |p| value_ints.include?(p.id) }
end
private
def visible_subprojects
@visible_subprojects ||= project.descendants.visible
end
end

@ -51,6 +51,16 @@ class Queries::WorkPackages::Filter::TypeFilter <
:type_id
end
def ar_object_filter?
true
end
def value_objects
value_ints = values.map(&:to_i)
types.select { |t| value_ints.include?(t.id) }
end
private
def types

@ -52,6 +52,16 @@ class Queries::WorkPackages::Filter::VersionFilter <
:fixed_version_id
end
def ar_object_filter?
true
end
def value_objects
value_ints = values.map(&:to_i)
versions.select { |v| value_ints.include?(v.id) }
end
private
def versions

@ -246,6 +246,10 @@ class Query < ActiveRecord::Base
filter
end
def filtered?
filters.any?
end
def normalized_name
name.parameterize.underscore
end
@ -268,6 +272,21 @@ class Query < ActiveRecord::Base
self.available_columns = v
end
def self.all_columns
WorkPackageCustomField
.includes(:translations)
.map { |cf| ::QueryCustomFieldColumn.new(cf) }
.concat(available_columns)
end
def self.groupable_columns
all_columns.select(&:groupable)
end
def self.sortable_columns
all_columns.select(&:sortable)
end
def self.add_available_column(column)
available_columns << column if column.is_a?(QueryColumn)
end
@ -360,6 +379,10 @@ class Query < ActiveRecord::Base
sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
end
def sorted?
sort_criteria.any?
end
# Returns the SQL sort order that should be prepended for grouping
def group_by_sort_order
if grouped? && (column = group_by_column)

@ -0,0 +1,167 @@
#-- 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 API
module V3
class ParseQueryParamsService
def call(params)
parsed_params = {}
parsed_params[:group_by] = group_by_from_params(params)
error_result = with_service_error_on_json_parse_error do
parsed_params[:filters] = filters_from_params(params)
parsed_params[:sort_by] = sort_by_from_params(params)
end
return error_result if error_result
parsed_params[:columns] = columns_from_params(params)
parsed_params[:display_sums] = sums_from_params(params)
ServiceResult.new(success: true,
result: without_empty(parsed_params))
end
def group_by_from_params(params)
convert_attribute(params[:group_by] || params[:groupBy] || params[:g])
end
def sort_by_from_params(params)
return unless params[:sortBy]
parse_sorting_from_json(params[:sortBy])
end
# Expected format looks like:
# [
# {
# "filtered_field_name": {
# "operator": "a name for a filter operation",
# "values": ["values", "for the", "operation"]
# }
# },
# { /* more filters if needed */}
# ]
def filters_from_params(params)
return unless params[:filters]
filters = JSON.parse(params[:filters])
filters.each_with_object([]) do |filter, array|
attribute = filter.keys.first # there should only be one attribute per filter
operator = filter[attribute]['operator']
values = filter[attribute]['values']
ar_attribute = convert_filter_attribute attribute, append_id: true
internal_representation = { field: ar_attribute,
operator: operator,
values: values }
array << internal_representation
end
end
def columns_from_params(params)
columns = params[:columns] || params[:c] || params[:column_names]
return unless columns
columns.map do |column|
convert_attribute(column)
end
end
def sums_from_params(params)
if params[:showSums] == 'true'
true
elsif params[:showSums] == 'false'
false
end
end
##
# Maps given field names coming from the frontend to the actual names
# as expected by the query. This works slightly different to what happens
# in #column_names_from_params. For instance while they column name is
# :type the expected field name is :type_id.
#
# Examples:
# * status => status_id
# * progresssDone => done_ratio
# * assigned => assigned_to
# * customField1 => cf_1
#
# @param query [Query] Query for which to get the correct field names.
# @param field_names [Array] Field names as read from the params.
# @return [Array] Returns a list of fixed field names. The list may contain nil values
# for fields which could not be found.
def fix_field_array(field_names)
return [] if field_names.nil?
field_names
.map { |name| convert_attribute name, append_id: true }
end
def parse_sorting_from_json(json)
JSON.parse(json).map do |order|
attribute, direction = if order.is_a?(Array)
[order.first, order.last]
elsif order.is_a?(String)
order.split(':')
end
[convert_attribute(attribute), direction]
end
end
def convert_attribute(attribute, append_id: false)
::API::Utilities::WpPropertyNameConverter.to_ar_name(attribute,
refer_to_ids: append_id)
end
def convert_filter_attribute(attribute, append_id: false)
::API::Utilities::QueryFiltersNameConverter.to_ar_name(attribute,
refer_to_ids: append_id)
end
def with_service_error_on_json_parse_error
yield
nil
rescue ::JSON::ParserError => error
result = ServiceResult.new
result.errors.add(:base, error.message)
return result
end
def without_empty(hash)
hash.select { |_, v| v.present? || v == false }
end
end
end
end

@ -0,0 +1,55 @@
#-- 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 API
module V3
class UpdateQueryFromV3ParamsService
def initialize(query, user)
self.query = query
self.current_user = user
end
def call(params)
parsed = ::API::V3::ParseQueryParamsService
.new
.call(params)
if parsed.success?
::UpdateQueryFromParamsService
.new(query, current_user)
.call(parsed.result)
else
parsed
end
end
attr_accessor :query,
:current_user
end
end
end

@ -0,0 +1,49 @@
#-- 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 API
module V3
class WorkPackageCollectionFromQueryParamsService
def initialize(user)
self.current_user = user
end
def call(params = {})
query = Query.new(name: '_', project: params[:project], sort_criteria: [['parent', 'desc']])
WorkPackageCollectionFromQueryService
.new(query, current_user)
.call(params)
end
private
attr_accessor :current_user
end
end
end

@ -0,0 +1,155 @@
#-- 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 API
module V3
class WorkPackageCollectionFromQueryService
include Utilities::PathHelper
def initialize(query, user)
self.query = query
self.current_user = user
end
def call(params = {})
update = UpdateQueryFromV3ParamsService
.new(query, current_user)
.call(params)
if update.success?
representer = results_to_representer(params)
ServiceResult.new(success: true, result: representer)
else
update
end
end
private
def results_to_representer(params)
collection_representer(query.results.sorted_work_packages,
params: params,
project: query.project,
groups: generate_groups,
sums: generate_total_sums)
end
attr_accessor :query,
:current_user
def representer
::API::V3::WorkPackages::WorkPackageCollectionRepresenter
end
def calculate_resulting_params(provided_params)
calculate_default_params
.merge(provided_params.slice('offset', 'pageSize').symbolize_keys)
end
def calculate_default_params
::API::V3::Queries::QueryParamsRepresenter
.new(query)
.to_h
end
def generate_groups
return unless query.grouped?
results = query.results
results.work_package_count_by_group.map do |group, count|
sums = if query.display_sums?
format_query_sums results.all_sums_for_group(group)
end
::API::Decorators::AggregationGroup.new(group, count, sums: sums)
end
end
def generate_total_sums
return unless query.display_sums?
format_query_sums query.results.all_total_sums
end
def format_query_sums(sums)
OpenStruct.new(format_column_keys(sums))
end
def format_column_keys(hash_by_column)
::Hash[
hash_by_column.map do |column, value|
match = /cf_(\d+)/.match(column.name.to_s)
column_name = if match
"custom_field_#{match[1]}"
else
column.name.to_s
end
[column_name, value]
end
]
end
def collection_representer(work_packages, params:, project:, groups:, sums:)
resulting_params = calculate_resulting_params(params)
::API::V3::WorkPackages::WorkPackageCollectionRepresenter.new(
work_packages,
self_link(project),
project: project,
query: resulting_params,
page: to_i_or_nil(resulting_params[:offset]),
per_page: to_i_or_nil(resulting_params[:pageSize]),
groups: groups,
total_sums: sums,
embed_schemas: true,
current_user: current_user
)
end
def to_i_or_nil(value)
value ? value.to_i : nil
end
def self_link(project)
if project
api_v3_paths.work_packages_by_project(project.id)
else
api_v3_paths.work_packages
end
end
def convert_to_v3(attribute)
::API::Utilities::PropertyNameConverter.from_ar_name(attribute).to_sym
end
end
end
end

@ -0,0 +1,84 @@
#-- 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.
#++
class UpdateQueryFromParamsService
def initialize(query, user)
self.query = query
self.current_user = user
end
def call(params)
apply_group_by(params)
apply_sort_by(params)
apply_filters(params)
apply_columns(params)
apply_sums(params)
if query.valid?
ServiceResult.new(success: true,
result: query)
else
ServiceResult.new(errors: query.errors)
end
end
private
def apply_group_by(params)
query.group_by = params[:group_by] if params[:group_by]
end
def apply_sort_by(params)
query.sort_criteria = params[:sort_by] if params[:sort_by]
end
def apply_filters(params)
return unless params[:filters]
query.filters = []
params[:filters].each do |filter|
query.add_filter(filter[:field], filter[:operator], filter[:values])
end
end
def apply_columns(params)
query.column_names = params[:columns] if params[:columns]
end
def apply_sums(params)
query.display_sums = params[:display_sums] if params[:display_sums]
end
attr_accessor :query,
:current_user,
:params
end

@ -45,7 +45,7 @@ OpenProject::Static::Homescreen.manage :blocks do |blocks|
{ partial: 'administration',
if: Proc.new { User.current.admin? } },
{ partial: 'upsale',
if: Proc.new { EnterpriseToken.show_banners } }
if: Proc.new { EnterpriseToken.show_banners? } }
)
end

@ -1639,6 +1639,9 @@ en:
project_module_timelines: "Timelines"
project_module_wiki: "Wiki"
query:
attribute_and_direction: "%{attribute} (%{direction})"
# possible query parameters (e.g. issue queries),
# which are not attributes of an AR-Model.
query_fields:

File diff suppressed because it is too large Load Diff

@ -609,7 +609,7 @@ For more details and all possible responses see the general specification of [Fo
+ groupBy (optional, string, `status`) ... The column to group by.
+ showSums = `false` (optional), boolean, `true` ... Indicates whether properties should be summed up if they support it.
+ showSums = `false` (optional, boolean, `true`) ... Indicates whether properties should be summed up if they support it.
+ Response 200 (application/hal+json)
@ -795,7 +795,7 @@ A project link must be set when creating work packages through this route.
+ groupBy (optional, string, `status`) ... The column to group by.
+ showSums = `false` (optional), boolean, `true` ... Indicates whether properties should be summed up if they support it.
+ showSums = `false` (optional, boolean, `true`) ... Indicates whether properties should be summed up if they support it.
+ Response 200 (application/hal+json)

@ -26,7 +26,7 @@ provide any official support for them.
### Dependencies
* __Runtime:__ [Ruby](https://www.ruby-lang.org/en/) Version 2.1.6
* __Runtime:__ [Ruby](https://www.ruby-lang.org/en/) Version >= 2.2.5, < 2.4
* __Webserver:__ [Apache](http://httpd.apache.org/)
or [nginx](http://nginx.org/en/docs/)
* __Application server:__ [Phusion Passenger](https://www.phusionpassenger.com/)

@ -72,7 +72,7 @@ export class WorkPackageViewController {
* Needs to be run explicitly by descendants.
*/
protected observeWorkPackage() {
this.wpCacheService.loadWorkPackage(this.workPackageId).observe(this.$scope)
this.wpCacheService.loadWorkPackage(this.workPackageId).observeOnScope(this.$scope)
.subscribe((wp:WorkPackageResourceInterface) => {
this.workPackage = wp;
this.init();

@ -64,7 +64,7 @@ describe('WorkPackageCacheService', () => {
wpCacheService.updateWorkPackageList(dummyWorkPackages);
let workPackage: WorkPackageResource;
wpCacheService.loadWorkPackage(1).observe(null).subscribe(wp => {
wpCacheService.loadWorkPackage(1).observeOnScope(null).subscribe(wp => {
workPackage = wp;
expect(workPackage.id).to.eq(1);
done();
@ -100,7 +100,7 @@ describe('WorkPackageCacheService', () => {
wpCacheService.updateWorkPackageList([workPackage]);
$rootScope.$apply();
wpCacheService.loadWorkPackage(1).observe(null).subscribe((wp: any) => {
wpCacheService.loadWorkPackage(1).observeOnScope(null).subscribe((wp: any) => {
expect(wp.id).to.eq(1);
expect(wp.dummy).to.eq(expected);

@ -119,7 +119,7 @@ function wpDisplayAttrDirective(wpCacheService:WorkPackageCacheService) {
controllers) {
if (!scope.$ctrl.customSchema) {
wpCacheService.loadWorkPackage(scope.$ctrl.workPackage.id).observe(scope)
wpCacheService.loadWorkPackage(scope.$ctrl.workPackage.id).observeOnScope(scope)
.subscribe((wp: WorkPackageResource) => {
scope.$ctrl.updateAttribute(wp);
});

@ -78,7 +78,7 @@ export class WorkPackageSingleViewController {
this.init(this.workPackage);
}
wpCacheService.loadWorkPackage(wpId).observe($scope).subscribe(wp => this.init(wp));
wpCacheService.loadWorkPackage(wpId).observeOnScope($scope).subscribe(wp => this.init(wp));
$scope.$on('workPackageUpdatedInEditor', () => {
this.wpNotificationsService.showSave(this.workPackage);
});

@ -37,7 +37,7 @@ export class WorkPackageSubjectController {
protected $stateParams,
protected wpCacheService) {
if (!this.workPackage) {
wpCacheService.loadWorkPackage($stateParams.workPackageId).observe($scope)
wpCacheService.loadWorkPackage($stateParams.workPackageId).observeOnScope($scope)
.subscribe((wp: WorkPackageResource) => {
this.workPackage = wp;
});

@ -45,7 +45,7 @@ export class WorkPackageWatcherButtonController {
public I18n,
public wpCacheService:WorkPackageCacheService) {
wpCacheService.loadWorkPackage(<number> this.workPackage.id).observe($scope)
wpCacheService.loadWorkPackage(<number> this.workPackage.id).observeOnScope($scope)
.subscribe((wp: WorkPackageResourceInterface) => {
this.workPackage = wp;
this.setWatchStatus();

@ -37,7 +37,7 @@ export class WorkPackageCopyController extends WorkPackageCreateController {
protected newWorkPackageFromParams(stateParams) {
var deferred = this.$q.defer();
this.wpCacheService.loadWorkPackage(stateParams.copiedFromWorkPackageId).observe(this.$scope)
this.wpCacheService.loadWorkPackage(stateParams.copiedFromWorkPackageId).observeOnScope(this.$scope)
.subscribe((wp:WorkPackageResourceInterface) => {
this.createCopyFrom(wp).then(newWorkPackage => {
deferred.resolve(newWorkPackage);

@ -82,7 +82,7 @@ export class WorkPackageCreateController {
wpCacheService.updateWorkPackage(wp);
if ($state.params.parent_id) {
wpCacheService.loadWorkPackage($state.params.parent_id).observe($scope)
wpCacheService.loadWorkPackage($state.params.parent_id).observeOnScope($scope)
.subscribe(parent => {
this.parentWorkPackage = parent;
this.newWorkPackage.parent = parent;

@ -350,7 +350,7 @@ function wpEditField(wpCacheService: WorkPackageCacheService) {
controllers[1].formCtrl = formCtrl;
formCtrl.registerField(scope.vm);
wpCacheService.loadWorkPackage(formCtrl.workPackage.id).observe(scope)
wpCacheService.loadWorkPackage(formCtrl.workPackage.id).observeOnScope(scope)
.subscribe((wp: WorkPackageResource) => {
scope.vm.workPackage = wp;
scope.vm.initializeField();

@ -57,7 +57,7 @@ export class WorkPackageEditFormController {
wpEditModeState.register(this);
}
states.workPackages.get(this.workPackage.id.toString()).observe($scope)
states.workPackages.get(this.workPackage.id.toString()).observeOnScope($scope)
.subscribe((wp: WorkPackageResource) => {
this.workPackage = wp;
});

@ -42,7 +42,7 @@ export class ActivityPanelController {
this.reverse = wpActivity.order === 'asc';
wpCacheService.loadWorkPackage(<number> this.workPackage.id).observe($scope)
wpCacheService.loadWorkPackage(<number> this.workPackage.id).observeOnScope($scope)
.subscribe((wp:WorkPackageResourceInterface) => {
this.workPackage = wp;
this.wpActivity.aggregateActivities(this.workPackage).then(activities => {

@ -65,7 +65,7 @@ export class WatchersPanelController {
return;
}
wpCacheService.loadWorkPackage(<number> this.workPackage.id).observe($scope)
wpCacheService.loadWorkPackage(<number> this.workPackage.id).observeOnScope($scope)
.subscribe((wp: WorkPackageResourceInterface) => {
this.workPackage = wp;
this.loadCurrentWatchers();

@ -47,7 +47,7 @@ export class WorkPackageRelationsHierarchyController {
this.wpCacheService
.loadWorkPackage(<number> this.workPackage.id)
.observe(this.$scope)
.observeOnScope(this.$scope)
.subscribe((wp:WorkPackageResourceInterface) => {
this.workPackage = wp;
this.loadParent();
@ -72,7 +72,7 @@ export class WorkPackageRelationsHierarchyController {
this.wpCacheService
.loadWorkPackage(this.workPackage.parentId)
.observe(this.$scope)
.observeOnScope(this.$scope)
.take(1)
.subscribe((parent:WorkPackageResourceInterface) => {
this.workPackage.parent = parent;

@ -59,7 +59,7 @@ export class WorkPackageRelationsController {
// Listen for changes to this WP.
this.wpCacheService
.loadWorkPackage(<number> this.workPackage.id)
.observe(this.$scope)
.observeOnScope(this.$scope)
.subscribe((wp:WorkPackageResourceInterface) => {
this.workPackage = wp;
this.workPackage.relations.$load().then(this.loadRelations.bind(this));
@ -67,7 +67,7 @@ export class WorkPackageRelationsController {
}
protected getRelatedWorkPackages(workPackageIds:number[]) {
let observablesToGetZipped = workPackageIds.map(wpId => this.wpCacheService.loadWorkPackage(wpId).observe(this.$scope));
let observablesToGetZipped = workPackageIds.map(wpId => this.wpCacheService.loadWorkPackage(wpId).observeOnScope(this.$scope));
if (observablesToGetZipped.length > 1) {
return Observable

@ -108,10 +108,14 @@ export class State<T> extends StoreElement {
return this.observable.take(1).toPromise();
}
public observe(scope: IScope): Observable<T> {
public observeOnScope(scope: IScope): Observable<T> {
return this.scopedObservable(scope);
}
public observeUntil(unsubscribeNotifier: Observable<any>): Observable<T> {
return this.observable.takeUntil(unsubscribeNotifier);
}
public observeCleared(scope: IScope): Observable<any> {
return scope ? scopedObservable(scope, this.cleared.asObservable()) : this.cleared.asObservable();
}

@ -65,8 +65,9 @@ module API
def self.self_link(path: nil, id_attribute: :id, title_getter: -> (*) { represented.name })
link :self do
path = _type.underscore unless path
link_object = { href: api_v3_paths.send(path, represented.send(id_attribute)) }
self_path = self_v3_path(path, id_attribute)
link_object = { href: self_path }
title = instance_eval(&title_getter)
link_object[:title] = title if title
@ -151,6 +152,20 @@ module API
def model_required?
true
end
def self_v3_path(path, id_attribute)
path = _type.underscore unless path
id = if id_attribute.respond_to?(:call)
instance_eval(&id_attribute)
else
represented.send(id_attribute)
end
id = [nil] if id.nil?
api_v3_paths.send(path, *Array(id))
end
end
end
end

@ -148,6 +148,20 @@ module API
raise API::Errors::Unauthorized unless authorized
authorized
end
def raise_invalid_query_on_service_failure
service = yield
if service.success?
service
else
api_errors = service.errors.full_messages.map do |message|
::API::Errors::InvalidQuery.new(message)
end
raise ::API::Errors::MultipleErrors.create_if_many api_errors
end
end
end
def self.auth_headers

@ -54,6 +54,7 @@ module API
resp_headers = instance_exec &headers
env['api.format'] = 'hal+json'
Rails.logger.error "Grape rescuing from error: #{e}"
error_response status: e.code, message: representer.to_json, headers: resp_headers
}

@ -84,12 +84,18 @@ module API
attribute = collapse_custom_field_name(attribute)
special_conversion = special_api_to_ar_conversions[attribute]
if special_conversion && context.respond_to?(special_conversion)
attribute = special_conversion
if refer_to_ids
special_conversion = denormalize_foreign_key_name(special_conversion, context)
end
attribute = denormalize_foreign_key_name(attribute, context) if refer_to_ids
attribute
if special_conversion && context.respond_to?(special_conversion)
special_conversion
elsif refer_to_ids
denormalize_foreign_key_name(attribute, context)
else
attribute
end
end
private

@ -0,0 +1,38 @@
#-- 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.
#++
# The sole purpose of this is to have a work package
# that is inexpensive to initialize by overriding the after_initialize hook
module API
module Utilities
class PropertyNameConverterWorkPackageDummy < ::WorkPackage
def set_default_values; end
end
end
end

@ -0,0 +1,47 @@
#-- encoding: UTF-8
#-- 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 'api/utilities/property_name_converter'
require 'api/utilities/query_filters_name_converter_context'
module API
module Utilities
class QueryFiltersNameConverter
class << self
def to_ar_name(attribute, refer_to_ids: false)
conversion_wp = ::API::Utilities::QueryFiltersNameConverterContext.new
::API::Utilities::PropertyNameConverter.to_ar_name(attribute,
context: conversion_wp,
refer_to_ids: refer_to_ids)
end
end
end
end
end

@ -0,0 +1,9 @@
module API
module Utilities
class QueryFiltersNameConverterContext
def respond_to?(method_name, include_private = false)
Query.registered_filters.map(&:key).include?(method_name.to_sym) || super
end
end
end
end

@ -0,0 +1,47 @@
#-- encoding: UTF-8
#-- 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 'api/utilities/property_name_converter'
require 'api/utilities/property_name_converter_work_package_dummy'
module API
module Utilities
class WpPropertyNameConverter
class << self
def to_ar_name(attribute, refer_to_ids: false)
conversion_wp = ::API::Utilities::PropertyNameConverterWorkPackageDummy.new
::API::Utilities::PropertyNameConverter.to_ar_name(attribute,
context: conversion_wp,
refer_to_ids: refer_to_ids)
end
end
end
end
end

@ -0,0 +1,67 @@
#-- encoding: UTF-8
#-- 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 API
module V3
module Queries
module Columns
class QueryColumnRepresenter < ::API::Decorators::Single
self_link id_attribute: ->(*) { converted_name },
title_getter: ->(*) { represented.caption }
def initialize(model)
super(model, current_user: nil, embed_links: true)
end
property :id,
exec_context: :decorator
property :caption,
as: :name
private
def converted_name
convert_attribute(represented.name)
end
alias :id :converted_name
def _type
'QueryColumn'
end
def convert_attribute(attribute)
::API::Utilities::PropertyNameConverter.from_ar_name(attribute)
end
end
end
end
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 API
module V3
module Queries
module Columns
class QueryColumnsAPI < ::API::OpenProjectAPI
resource :columns do
helpers do
def convert_to_ar(attribute)
::API::Utilities::WpPropertyNameConverter.to_ar_name(attribute)
end
end
params do
requires :id, desc: 'Column id'
end
before do
authorize(:view_work_packages, global: true, user: current_user)
end
route_param :id do
get do
ar_id = convert_to_ar(params[:id]).to_sym
column = Query.all_columns.detect { |candidate| candidate.name == ar_id }
if column
::API::V3::Queries::Columns::QueryColumnRepresenter.new(column)
else
raise API::Errors::NotFound
end
end
end
end
end
end
end
end
end

@ -0,0 +1,46 @@
#-- encoding: UTF-8
#-- 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 API
module V3
module Queries
module Filters
class QueryFilterDecorator
def initialize(filter)
self.filter = filter
end
private
attr_accessor :filter
end
end
end
end
end

@ -0,0 +1,96 @@
#-- encoding: UTF-8
#-- 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 API
module V3
module Queries
module Filters
class QueryFilterInstanceRepresenter < ::API::Decorators::Single
def initialize(model)
super(model, current_user: nil, embed_links: true)
end
link :filter do
{
href: api_v3_paths.query_filter(converted_name),
title: name
}
end
link :operator do
{
href: api_v3_paths.query_operator(represented.operator),
title: operator_name
}
end
links :values do
next unless represented.ar_object_filter?
represented.value_objects.map do |value_object|
{
href: api_v3_paths.send(value_object.class.name.downcase, value_object.id),
title: value_object.name
}
end
end
property :name,
exec_context: :decorator
property :values,
if: ->(*) { !ar_object_filter? },
show_nil: true
private
def name
represented.human_name
end
def _type
"#{converted_name.camelize}QueryFilter"
end
def converted_name
convert_attribute(represented.name)
end
def operator_name
I18n.t(represented.class.operators[represented.operator.to_sym])
end
def convert_attribute(attribute)
::API::Utilities::PropertyNameConverter.from_ar_name(attribute)
end
end
end
end
end
end

@ -0,0 +1,65 @@
#-- encoding: UTF-8
#-- 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 API
module V3
module Queries
module Filters
class QueryFilterRepresenter < ::API::Decorators::Single
self_link id_attribute: ->(*) { converted_key },
title_getter: ->(*) { represented.human_name },
path: :query_filter
def initialize(model)
super(model, current_user: nil, embed_links: true)
end
property :id,
exec_context: :decorator
private
def converted_key
convert_attribute(represented.name)
end
alias :id :converted_key
def _type
'QueryFilter'
end
def convert_attribute(attribute)
::API::Utilities::PropertyNameConverter.from_ar_name(attribute)
end
end
end
end
end
end

@ -0,0 +1,71 @@
#-- 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 API
module V3
module Queries
module Filters
class QueryFiltersAPI < ::API::OpenProjectAPI
resource :filters do
helpers do
def convert_to_ar(attribute)
::API::Utilities::QueryFiltersNameConverter.to_ar_name(attribute,
refer_to_ids: true)
end
end
params do
requires :id, desc: 'Filter id'
end
before do
authorize(:view_work_packages, global: true, user: current_user)
end
route_param :id do
get do
ar_id = convert_to_ar(params[:id])
filter_class = Query.find_registered_filter(ar_id)
if filter_class
filter = filter_class.new
filter.name = ar_id
::API::V3::Queries::Filters::QueryFilterRepresenter.new(filter)
else
raise API::Errors::NotFound
end
end
end
end
end
end
end
end
end

@ -0,0 +1,67 @@
#-- encoding: UTF-8
#-- 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 API
module V3
module Queries
module GroupBys
class QueryGroupByRepresenter < ::API::Decorators::Single
self_link id_attribute: ->(*) { converted_name },
title_getter: ->(*) { represented.caption }
def initialize(model)
super(model, current_user: nil, embed_links: true)
end
property :id,
exec_context: :decorator
property :caption,
as: :name
private
def converted_name
convert_attribute(represented.name)
end
alias :id :converted_name
def _type
'QueryGroupBy'
end
def convert_attribute(attribute)
::API::Utilities::PropertyNameConverter.from_ar_name(attribute)
end
end
end
end
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 API
module V3
module Queries
module GroupBys
class QueryGroupBysAPI < ::API::OpenProjectAPI
resource :group_bys do
helpers do
def convert_to_ar(attribute)
::API::Utilities::WpPropertyNameConverter.to_ar_name(attribute)
end
end
params do
requires :id, desc: 'Group by id'
end
before do
authorize(:view_work_packages, global: true, user: current_user)
end
route_param :id do
get do
ar_id = convert_to_ar(params[:id]).to_sym
column = Query.groupable_columns.detect { |candidate| candidate.name == ar_id }
if column
::API::V3::Queries::GroupBys::QueryGroupByRepresenter.new(column)
else
raise API::Errors::NotFound
end
end
end
end
end
end
end
end
end

@ -0,0 +1,63 @@
#-- encoding: UTF-8
#-- 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 API
module V3
module Queries
module Operators
class QueryOperatorRepresenter < ::API::Decorators::Single
self_link id_attribute: ->(*) { represented },
title_getter: ->(*) { name }
def initialize(model)
super(model.to_sym, current_user: nil, embed_links: true)
end
property :id,
exec_context: :decorator
property :name,
exec_context: :decorator
private
def name
I18n.t(::Queries::BaseFilter.operators[represented])
end
alias :id :represented
def _type
'QueryOperator'
end
end
end
end
end
end

@ -0,0 +1,57 @@
#-- 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 API
module V3
module Queries
module Operators
class QueryOperatorsAPI < ::API::OpenProjectAPI
resource :operators do
params do
requires :id, desc: 'Operator id'
end
before do
authorize(:view_work_packages, global: true, user: current_user)
end
route_param :id do
get do
if ::Queries::BaseFilter.operators[params[:id].to_sym]
::API::V3::Queries::Operators::QueryOperatorRepresenter.new(params[:id])
else
raise API::Errors::NotFound
end
end
end
end
end
end
end
end
end

@ -34,6 +34,12 @@ module API
module Queries
class QueriesAPI < ::API::OpenProjectAPI
resources :queries do
mount API::V3::Queries::Columns::QueryColumnsAPI
mount API::V3::Queries::GroupBys::QueryGroupBysAPI
mount API::V3::Queries::SortBys::QuerySortBysAPI
mount API::V3::Queries::Filters::QueryFiltersAPI
mount API::V3::Queries::Operators::QueryOperatorsAPI
get do
authorize_any [:view_work_packages, :manage_public_queries], global: true
@ -58,7 +64,15 @@ module API
route_param :id do
before do
@query = Query.find(params[:id])
@representer = QueryRepresenter.new(@query, current_user: current_user)
results_representer = ::API::V3::WorkPackageCollectionFromQueryService
.new(@query, current_user)
.call(params)
@representer = QueryRepresenter.new(@query,
current_user: current_user,
results: results_representer.result,
params: params)
authorize_by_policy(:show) do
raise API::Errors::NotFound
end

@ -0,0 +1,90 @@
#-- encoding: UTF-8
#-- 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.
#++
# Other than the Roar based representers of the api v3, this
# representer is only responsible for transforming a query's
# attributes into a hash which in turn can be used e.g. to be displayed
# in a url
module API
module V3
module Queries
class QueryParamsRepresenter
def initialize(query)
self.query = query
end
def to_h
p = default_hash
p[:showSums] = 'true' if query.display_sums?
p[:groupBy] = query.group_by if query.group_by?
p[:sortBy] = sort_criteria_to_v3 if query.sorted?
p[:filters] = filters_to_v3 if query.filtered?
p
end
def self_link
if query.project
api_v3_paths.work_packages_by_project(query.project.id)
else
api_v3_paths.work_packages
end
end
private
def sort_criteria_to_v3
converted = query.sort_criteria.map { |first, last| [convert_to_v3(first), last] }
JSON::dump(converted)
end
def filters_to_v3
converted = query.filters.map do |filter|
{ convert_to_v3(filter.name) => { operator: filter.operator, values: filter.values } }
end
JSON::dump(converted)
end
def convert_to_v3(attribute)
::API::Utilities::PropertyNameConverter.from_ar_name(attribute).to_sym
end
def default_hash
{ offset: 1, pageSize: Setting.per_page_options_array.first }
end
attr_accessor :query
end
end
end
end

@ -36,6 +36,71 @@ module API
class QueryRepresenter < ::API::Decorators::Single
self_link
attr_accessor :results,
:params
def initialize(model,
current_user:,
results: nil,
embed_links: false,
params: {})
self.results = results
self.params = params
super(model, current_user: current_user, embed_links: embed_links)
end
link :results do
path = if represented.project
api_v3_paths.work_packages_by_project(represented.project.id)
else
api_v3_paths.work_packages
end
url_query = ::API::V3::Queries::QueryParamsRepresenter
.new(represented)
.to_h
.merge(params.slice(:offset, :pageSize))
{
href: [path, url_query.to_query].join('?')
}
end
links :columns do
represented.columns.map do |column|
{
href: api_v3_paths.query_column(convert_attribute(column.name).underscore),
title: column.caption
}
end
end
link :groupBy do
column = represented.group_by_column
if column
{
href: api_v3_paths.query_group_by(convert_attribute(column.name).underscore),
title: column.caption
}
else
{
href: nil,
title: nil
}
end
end
links :sortBy do
map_with_sort_by_as_decorated do |sort_by|
{
href: api_v3_paths.query_sort_by(sort_by.converted_name, sort_by.direction_name),
title: sort_by.name
}
end
end
linked_property :user
linked_property :project
@ -45,35 +110,62 @@ module API
exec_context: :decorator,
getter: ->(*) {
represented.filters.map do |filter|
attribute = convert_attribute filter.field
{
attribute => { operator: filter.operator, values: filter.values }
}
::API::V3::Queries::Filters::QueryFilterInstanceRepresenter.new(filter)
end
}
property :is_public, getter: -> (*) { is_public }
property :column_names,
property :public, getter: -> (*) { is_public }
property :sort_by,
exec_context: :decorator,
getter: ->(*) {
return nil unless represented.column_names
represented.column_names.map { |name| convert_attribute name }
return unless represented.sort_criteria
map_with_sort_by_as_decorated do |sort_by|
::API::V3::Queries::SortBys::QuerySortByRepresenter.new(sort_by)
end
},
embedded: true,
if: ->(*) {
embed_links
}
property :sort_criteria,
property :sums, getter: -> (*) { display_sums }
property :starred, getter: -> (*) { starred }
property :columns,
exec_context: :decorator,
getter: ->(*) {
return nil unless represented.sort_criteria
represented.sort_criteria.map do |attribute, order|
[convert_attribute(attribute), order]
represented.columns.map do |column|
::API::V3::Queries::Columns::QueryColumnRepresenter.new(column)
end
},
embedded: true,
if: ->(*) {
embed_links
}
property :group_by,
exec_context: :decorator,
getter: ->(*) {
represented.grouped? ? convert_attribute(represented.group_by) : nil
return unless represented.grouped?
column = represented.group_by_column
::API::V3::Queries::GroupBys::QueryGroupByRepresenter.new(column)
},
embedded: true,
if: ->(*) {
embed_links
},
render_nil: true
property :display_sums, getter: -> (*) { display_sums }
property :is_starred, getter: -> (*) { starred }
property :results,
exec_context: :decorator,
render_nil: true,
embedded: true,
if: ->(*) {
results
}
self.to_eager_load = [:query_menu_item,
project: { work_package_custom_fields: :translations }]
@ -84,6 +176,15 @@ module API
::API::Utilities::PropertyNameConverter.from_ar_name(attribute)
end
def map_with_sort_by_as_decorated
represented.sort_criteria.map do |attribute, order|
decorated = ::API::V3::Queries::SortBys::SortByDecorator.new(attribute,
order)
yield decorated
end
end
def _type
'Query'
end

@ -0,0 +1,73 @@
#-- encoding: UTF-8
#-- 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 API
module V3
module Queries
module SortBys
class QuerySortByRepresenter < ::API::Decorators::Single
self_link id_attribute: ->(*) { self_link_params },
title_getter: ->(*) { represented.name }
def initialize(model)
super(model, current_user: nil, embed_links: true)
end
link :column do
{
href: api_v3_paths.query_column(represented.converted_name),
title: represented.column_caption
}
end
link :direction do
{
href: represented.direction_uri,
title: represented.direction_l10n
}
end
property :id
property :name
private
def self_link_params
[represented.converted_name, represented.direction_name]
end
def _type
'QuerySortBy'
end
end
end
end
end
end

@ -0,0 +1,68 @@
#-- 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 API
module V3
module Queries
module SortBys
class QuerySortBysAPI < ::API::OpenProjectAPI
resource :sort_bys do
helpers do
def convert_to_ar(attribute)
::API::Utilities::WpPropertyNameConverter.to_ar_name(attribute)
end
end
params do
requires :id, desc: 'Group by id'
requires :direction, desc: 'Direction of sorting'
end
before do
authorize(:view_work_packages, global: true, user: current_user)
end
namespace ':id-:direction' do
get do
ar_id = convert_to_ar(params[:id])
begin
decorator = ::API::V3::Queries::SortBys::SortByDecorator.new(ar_id,
params[:direction])
::API::V3::Queries::SortBys::QuerySortByRepresenter.new(decorator)
rescue ArgumentError
raise API::Errors::NotFound
end
end
end
end
end
end
end
end
end

@ -0,0 +1,99 @@
#-- encoding: UTF-8
#-- 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 API
module V3
module Queries
module SortBys
class SortByDecorator
def initialize(column_name, direction)
if !['asc', 'desc'].include?(direction)
raise ArgumentError, "Invalid direction. Only 'asc' and 'desc' are supported."
end
self.direction = direction
column_sym = column_name.to_sym
column = Query
.sortable_columns
.detect { |candidate| candidate.name == column_sym }
if column.nil?
raise ArgumentError, "Invalid column name."
end
self.column = column
end
def id
"#{converted_name}-#{direction_name}"
end
def name
I18n.t('query.attribute_and_direction',
attribute: column_caption,
direction: direction_l10n)
end
def converted_name
convert_attribute(column_name)
end
def direction_name
direction
end
def direction_uri
"urn:openproject-org:api:v3:queries:directions:#{direction}"
end
def direction_l10n
I18n.t(direction == 'desc' ? :label_descending : :label_ascending)
end
def column_name
column.name
end
def column_caption
column.caption
end
private
def convert_attribute(attribute)
::API::Utilities::PropertyNameConverter.from_ar_name(attribute)
end
attr_accessor :direction, :column
end
end
end
end
end

@ -143,6 +143,26 @@ module API
"#{query(id)}/unstar"
end
def self.query_column(name)
"#{queries}/columns/#{name}"
end
def self.query_group_by(name)
"#{queries}/group_bys/#{name}"
end
def self.query_sort_by(name, direction)
"#{queries}/sort_bys/#{name}-#{direction}"
end
def self.query_filter(name)
"#{queries}/filters/#{name}"
end
def self.query_operator(name)
"#{queries}/operators/#{name}"
end
def self.relation(id)
"#{root}/relations/#{id}"
end

@ -1,210 +0,0 @@
#-- 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 API
module V3
module WorkPackages
module WorkPackageListHelpers
extend Grape::API::Helpers
include QueriesHelper
def work_packages_by_params(project: nil)
query = Query.new(name: '_', project: project)
query_params = {}
begin
apply_filters query, query_params
apply_sorting query, query_params
groups = apply_and_generate_groups query, query_params
total_sums = generate_total_sums query.results, query_params
rescue ::JSON::ParserError => error
raise ::API::Errors::InvalidQuery.new(error.message)
end
work_packages = query
.results
.sorted_work_packages
collection_representer(work_packages,
project: project,
query_params: query_params,
groups: groups,
sums: total_sums)
end
def apply_filters(query, query_params)
if params[:filters]
filters = parse_filters_from_json(params[:filters])
set_filters(query, filters)
query_params[:filters] = params[:filters]
end
end
# Expected format looks like:
# [
# {
# "filtered_field_name": {
# "operator": "a name for a filter operation",
# "values": ["values", "for the", "operation"]
# }
# },
# { /* more filters if needed */}
# ]
def parse_filters_from_json(json)
filters = JSON.parse(json)
operators = {}
values = {}
filters.each do |filter|
attribute = filter.keys.first # there should only be one attribute per filter
operators[attribute] = filter[attribute]['operator']
values[attribute] = filter[attribute]['values']
end
{
fields: values.keys,
operators: operators,
values: values
}
end
def set_filters(query, filters)
add_filter_from_params(query, filters: filters)
bad_filter = query.filters.detect(&:invalid?)
if bad_filter
raise_invalid_query(bad_filter.errors)
end
end
def apply_sorting(query, query_params)
if params[:sortBy]
query.sort_criteria = parse_sorting_from_json(params[:sortBy])
query_params[:sortBy] = params[:sortBy]
else
query.sort_criteria = [['parent', 'desc']]
query_params[:sortBy] = 'parent:desc'
end
end
def parse_sorting_from_json(json)
JSON.parse(json).map do |(attribute, order)|
[convert_attribute(attribute), order]
end
end
def apply_and_generate_groups(query, query_params)
if params[:groupBy]
query.group_by = convert_attribute params[:groupBy]
query_params[:groupBy] = params[:groupBy]
generate_groups query.results
end
end
def generate_groups(results)
results.work_package_count_by_group.map do |group, count|
sums = nil
if params[:showSums] == 'true'
sums = format_query_sums results.all_sums_for_group(group)
end
::API::Decorators::AggregationGroup.new(group, count, sums: sums)
end
end
def generate_total_sums(results, query_params)
if params[:showSums] == 'true'
query_params[:showSums] = 'true'
format_query_sums results.all_total_sums
end
end
def format_query_sums(sums)
OpenStruct.new(format_column_keys(sums))
end
def format_column_keys(hash_by_column)
::Hash[
hash_by_column.map do |column, value|
match = /cf_(\d+)/.match(column.name.to_s)
column_name = if match
"custom_field_#{match[1]}"
else
column.name.to_s
end
[column_name, value]
end
]
end
def collection_representer(work_packages, project:, query_params:, groups:, sums:)
self_link = if project
api_v3_paths.work_packages_by_project(project.id)
else
api_v3_paths.work_packages
end
::API::V3::WorkPackages::WorkPackageCollectionRepresenter.new(
work_packages,
self_link,
project: project,
query: query_params,
page: to_i_or_nil(params[:offset]),
per_page: to_i_or_nil(params[:pageSize]),
groups: groups,
total_sums: sums,
embed_schemas: true,
current_user: current_user
)
end
def convert_attribute(attribute, append_id: false)
@@conversion_wp ||= ::API::Utilities::PropertyNameConverterQueryContext.new
::API::Utilities::PropertyNameConverter.to_ar_name(attribute,
context: @@conversion_wp,
refer_to_ids: append_id)
end
def raise_invalid_query(errors)
api_errors = errors.full_messages.map do |message|
::API::Errors::InvalidQuery.new(message)
end
raise ::API::Errors::MultipleErrors.create_if_many api_errors
end
def to_i_or_nil(value)
value ? value.to_i : nil
end
end
end
end
end

@ -34,7 +34,6 @@ module API
module WorkPackages
class WorkPackagesAPI < ::API::OpenProjectAPI
resources :work_packages do
helpers ::API::V3::WorkPackages::WorkPackageListHelpers
helpers ::API::V3::WorkPackages::CreateWorkPackages
# The enpoint needs to be mounted before the GET :work_packages/:id.
@ -45,7 +44,19 @@ module API
get do
authorize(:view_work_packages, global: true)
work_packages_by_params
service = WorkPackageCollectionFromQueryParamsService
.new(current_user)
.call(params)
if service.success?
service.result
else
api_errors = service.errors.full_messages.map do |message|
::API::Errors::InvalidQuery.new(message)
end
raise ::API::Errors::MultipleErrors.create_if_many api_errors
end
end
post do

@ -26,7 +26,6 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'api/v3/work_packages/work_package_list_helpers'
require 'api/v3/work_packages/create_work_packages'
module API
@ -35,11 +34,17 @@ module API
class WorkPackagesByProjectAPI < ::API::OpenProjectAPI
resources :work_packages do
helpers ::API::V3::WorkPackages::CreateWorkPackages
helpers ::API::V3::WorkPackages::WorkPackageListHelpers
get do
authorize(:view_work_packages, context: @project)
work_packages_by_params(project: @project)
service = raise_invalid_query_on_service_failure do
WorkPackageCollectionFromQueryParamsService
.new(current_user)
.call(params.merge(project: @project))
end
service.result
end
post do

@ -83,7 +83,7 @@ module Redmine::MenuManager::TopMenu::HelpMenu
class: 'drop-down--help-headline',
title: l('top_menu.help_and_support')
}
if EnterpriseToken.show_banners
if EnterpriseToken.show_banners?
result << static_link_item(:upsale, href_suffix: "?utm_source=ce-helpmenu")
end
result << static_link_item(:user_guides)

@ -10,7 +10,7 @@
},
"private": true,
"engines": {
"node": "6.9.1",
"npm": "4.0.0"
"node": ">= 6.9.1",
"npm": ">= 4.0.0"
}
}

@ -171,6 +171,15 @@ describe ::API::Utilities::PropertyNameConverter do
end
end
end
context 'inappropriate replacement as context does not respond to it with foreign key' do
let(:attribute_name) { 'type' }
subject { described_class.to_ar_name(attribute_name, context: context, refer_to_ids: true) }
it 'does not take the special replacement but appends the id suffix' do
is_expected.to eql('type_id')
end
end
end
end
end

@ -0,0 +1,90 @@
#-- 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'
describe ::API::V3::Queries::Columns::QueryColumnRepresenter do
include ::API::V3::Utilities::PathHelper
let(:column) { Query.available_columns.detect { |column| column.name == :status } }
let(:representer) { described_class.new(column) }
subject { representer.to_json }
describe 'generation' do
describe '_links' do
it_behaves_like 'has a titled link' do
let(:link) { 'self' }
let(:href) { api_v3_paths.query_column 'status' }
let(:title) { 'Status' }
end
end
it 'has _type QueryColumn' do
is_expected
.to be_json_eql('QueryColumn'.to_json)
.at_path('_type')
end
it 'has id attribute' do
is_expected
.to be_json_eql('status'.to_json)
.at_path('id')
end
it 'has name attribute' do
is_expected
.to be_json_eql('Status'.to_json)
.at_path('name')
end
context 'for a translated column' do
let(:column) { Query.available_columns.detect { |column| column.name == :assigned_to } }
describe '_links' do
it_behaves_like 'has a titled link' do
let(:link) { 'self' }
let(:href) { api_v3_paths.query_column 'assignee' }
let(:title) { 'Assignee' }
end
end
it 'has id attribute' do
is_expected
.to be_json_eql('assignee'.to_json)
.at_path('id')
end
it 'has name attribute' do
is_expected
.to be_json_eql('Assignee'.to_json)
.at_path('name')
end
end
end
end

@ -0,0 +1,111 @@
#-- 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'
describe ::API::V3::Queries::Filters::QueryFilterInstanceRepresenter do
include ::API::V3::Utilities::PathHelper
let(:operator) { '=' }
let(:values) { [status.id.to_s] }
let(:status) { FactoryGirl.build_stubbed(:status) }
let(:filter) do
Queries::WorkPackages::Filter::StatusFilter.new(operator: operator, values: values)
end
let(:representer) { described_class.new(filter) }
before do
allow(filter)
.to receive(:value_objects)
.and_return([status])
end
describe 'generation' do
subject { representer.to_json }
describe '_links' do
it_behaves_like 'has a titled link' do
let(:link) { 'filter' }
let(:href) { api_v3_paths.query_filter 'status' }
let(:title) { 'Status' }
end
it_behaves_like 'has a titled link' do
let(:link) { 'operator' }
let(:href) { api_v3_paths.query_operator '=' }
let(:title) { 'is' }
end
it "has a 'values' collection" do
expected = {
href: api_v3_paths.status(status.id.to_s),
title: status.name
}
is_expected
.to be_json_eql([expected].to_json)
.at_path('_links/values')
end
end
it 'has _type StatusQueryFilter' do
is_expected
.to be_json_eql('StatusQueryFilter'.to_json)
.at_path('_type')
end
it 'has name Status' do
is_expected
.to be_json_eql('Status'.to_json)
.at_path('name')
end
context 'with a non ar object filter' do
let(:values) { ['lorem ipsum'] }
let(:filter) do
Queries::WorkPackages::Filter::SubjectFilter.new(operator: operator, values: values)
end
describe '_links' do
it 'has no values link' do
is_expected
.not_to have_json_path('_links/values')
end
end
it "has a 'values' array property" do
is_expected
.to be_json_eql(values.to_json)
.at_path('values')
end
end
end
end

@ -0,0 +1,106 @@
#-- 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'
describe ::API::V3::Queries::Filters::QueryFilterRepresenter do
include ::API::V3::Utilities::PathHelper
let(:filter) { Queries::WorkPackages::Filter::SubjectFilter.new }
let(:representer) { described_class.new(filter) }
subject { representer.to_json }
describe 'generation' do
describe '_links' do
it_behaves_like 'has a titled link' do
let(:link) { 'self' }
let(:href) { api_v3_paths.query_filter 'subject' }
let(:title) { 'Subject' }
end
end
it 'has _type QueryFilter' do
is_expected
.to be_json_eql('QueryFilter'.to_json)
.at_path('_type')
end
it 'has id attribute' do
is_expected
.to be_json_eql('subject'.to_json)
.at_path('id')
end
context 'for a translated filter' do
let(:filter) { Queries::WorkPackages::Filter::AssignedToFilter.new }
describe '_links' do
it_behaves_like 'has a titled link' do
let(:link) { 'self' }
let(:href) { api_v3_paths.query_filter 'assignee' }
let(:title) { 'Assignee' }
end
end
it 'has id attribute' do
is_expected
.to be_json_eql('assignee'.to_json)
.at_path('id')
end
end
context 'for a custom field filter' do
let(:custom_field) { FactoryGirl.build_stubbed(:list_wp_custom_field) }
let(:filter) { Queries::WorkPackages::Filter::CustomFieldFilter.new }
before do
allow(WorkPackageCustomField)
.to receive(:find_by_id)
.with(custom_field.id)
.and_return custom_field
filter.name = "cf_#{custom_field.id}"
end
describe '_links' do
it_behaves_like 'has a titled link' do
let(:link) { 'self' }
let(:href) { api_v3_paths.query_filter "customField#{custom_field.id}" }
let(:title) { custom_field.name }
end
end
it 'has id attribute' do
is_expected
.to be_json_eql("customField#{custom_field.id}".to_json)
.at_path('id')
end
end
end
end

@ -0,0 +1,90 @@
#-- 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'
describe ::API::V3::Queries::GroupBys::QueryGroupByRepresenter do
include ::API::V3::Utilities::PathHelper
let(:column) { Query.available_columns.detect { |column| column.name == :status } }
let(:representer) { described_class.new(column) }
subject { representer.to_json }
describe 'generation' do
describe '_links' do
it_behaves_like 'has a titled link' do
let(:link) { 'self' }
let(:href) { api_v3_paths.query_group_by 'status' }
let(:title) { 'Status' }
end
end
it 'has _type QueryGroupBy' do
is_expected
.to be_json_eql('QueryGroupBy'.to_json)
.at_path('_type')
end
it 'has id attribute' do
is_expected
.to be_json_eql('status'.to_json)
.at_path('id')
end
it 'has name attribute' do
is_expected
.to be_json_eql('Status'.to_json)
.at_path('name')
end
context 'for a translated column' do
let(:column) { Query.available_columns.detect { |column| column.name == :assigned_to } }
describe '_links' do
it_behaves_like 'has a titled link' do
let(:link) { 'self' }
let(:href) { api_v3_paths.query_group_by 'assignee' }
let(:title) { 'Assignee' }
end
end
it 'has id attribute' do
is_expected
.to be_json_eql('assignee'.to_json)
.at_path('id')
end
it 'has name attribute' do
is_expected
.to be_json_eql('Assignee'.to_json)
.at_path('name')
end
end
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.
#++
require 'spec_helper'
describe ::API::V3::Queries::Operators::QueryOperatorRepresenter do
include ::API::V3::Utilities::PathHelper
let(:operator) { '!~' }
let(:representer) { described_class.new(operator) }
subject { representer.to_json }
describe 'generation' do
describe '_links' do
it_behaves_like 'has a titled link' do
let(:link) { 'self' }
let(:href) { api_v3_paths.query_operator operator }
let(:title) { I18n.t(:label_not_contains) }
end
end
it 'has _type QueryOperator' do
is_expected
.to be_json_eql('QueryOperator'.to_json)
.at_path('_type')
end
it 'has id attribute' do
is_expected
.to be_json_eql(operator.to_json)
.at_path('id')
end
it 'has name attribute' do
is_expected
.to be_json_eql(I18n.t(:label_not_contains).to_json)
.at_path('name')
end
end
end

@ -31,10 +31,11 @@ require 'spec_helper'
describe ::API::V3::Queries::QueryRepresenter do
include ::API::V3::Utilities::PathHelper
let(:query) {
FactoryGirl.build_stubbed(:query)
}
let(:representer) { described_class.new(query, current_user: double('current_user')) }
let(:query) { FactoryGirl.build_stubbed(:query, project: project) }
let(:project) { FactoryGirl.build_stubbed(:project) }
let(:representer) do
described_class.new(query, current_user: double('current_user'), embed_links: true)
end
subject { representer.to_json }
@ -58,12 +59,194 @@ describe ::API::V3::Queries::QueryRepresenter do
let(:title) { query.project.name }
end
it_behaves_like 'has an untitled link' do
let(:link) { 'results' }
let(:href) do
params = {
offset: 1,
pageSize: Setting.per_page_options_array.first
}
"#{api_v3_paths.work_packages_by_project(project.id)}?#{params.to_query}"
end
end
context 'has no project' do
let(:query) { FactoryGirl.build_stubbed(:query, project: nil) }
it_behaves_like 'has an empty link' do
let(:link) { 'project' }
end
it_behaves_like 'has an untitled link' do
let(:link) { 'results' }
let(:href) do
params = {
offset: 1,
pageSize: Setting.per_page_options_array.first
}
"#{api_v3_paths.work_packages}?#{params.to_query}"
end
end
end
context 'with filter, sort, group by and pageSize' do
let(:representer) do
described_class.new(query,
current_user: double('current_user'))
end
let(:query) do
query = FactoryGirl.build_stubbed(:query, project: project)
query.add_filter('subject', '~', ['bogus'])
query.group_by = 'author'
query.sort_criteria = [['assigned_to', 'asc'], ['type', 'desc']]
query
end
let(:expected_href) do
params = {
offset: 1,
pageSize: Setting.per_page_options_array.first,
filters: JSON::dump([{ subject: { operator: '~', values: ['bogus'] } }]),
groupBy: 'author',
sortBy: JSON::dump([['assignee', 'asc'], ['type', 'desc']])
}
api_v3_paths.work_packages_by_project(project.id) + "?#{params.to_query}"
end
it_behaves_like 'has an untitled link' do
let(:link) { 'results' }
let(:href) { expected_href }
end
end
context 'with offset and page size' do
let(:representer) do
described_class.new(query,
current_user: double('current_user'),
params: { offset: 2, pageSize: 25 })
end
let(:expected_href) do
params = {
offset: 2,
pageSize: 25
}
api_v3_paths.work_packages_by_project(project.id) + "?#{params.to_query}"
end
it_behaves_like 'has an untitled link' do
let(:link) { 'results' }
let(:href) { expected_href }
end
end
context 'without columns' do
let(:query) do
query = FactoryGirl.build_stubbed(:query, project: project)
# need to write bogus here because the query
# will otherwise sport the default columns
query.column_names = ['blubs']
query
end
it 'has an empty columns array' do
is_expected
.to be_json_eql([].to_json)
.at_path('_links/columns')
end
end
context 'with columns' do
let(:query) do
query = FactoryGirl.build_stubbed(:query, project: project)
query.column_names = ['status', 'assigned_to', 'updated_at']
query
end
it 'has an array of columns' do
status = {
href: '/api/v3/queries/columns/status',
title: 'Status'
}
assignee = {
href: '/api/v3/queries/columns/assignee',
title: 'Assignee'
}
subproject = {
href: '/api/v3/queries/columns/updated_at',
title: 'Updated on'
}
expected = [status, assignee, subproject]
is_expected
.to be_json_eql(expected.to_json)
.at_path('_links/columns')
end
end
context 'without group_by' do
it_behaves_like 'has a titled link' do
let(:href) { nil }
let(:link) { 'groupBy' }
let(:title) { nil }
end
end
context 'with group_by' do
let(:query) do
query = FactoryGirl.build_stubbed(:query, project: project)
query.group_by = 'status'
query
end
it_behaves_like 'has a titled link' do
let(:href) { '/api/v3/queries/group_bys/status' }
let(:link) { 'groupBy' }
let(:title) { 'Status' }
end
end
context 'without sort_by' do
it 'has an empty sortBy array' do
is_expected
.to be_json_eql([].to_json)
.at_path('_links/sortBy')
end
end
context 'with sort_by' do
let(:query) do
FactoryGirl.build_stubbed(:query,
sort_criteria: [['subject', 'asc'], ['assigned_to', 'desc']])
end
it 'has an array of sortBy' do
expected = [
{
href: api_v3_paths.query_sort_by('subject', 'asc'),
title: 'Subject (Ascending)'
},
{
href: api_v3_paths.query_sort_by('assignee', 'desc'),
title: 'Assignee (Descending)'
}
]
is_expected
.to be_json_eql(expected.to_json)
.at_path('_links/sortBy')
end
end
end
@ -76,68 +259,178 @@ describe ::API::V3::Queries::QueryRepresenter do
end
it 'should indicate whether sums are shown' do
is_expected.to be_json_eql(query.display_sums.to_json).at_path('displaySums')
is_expected.to be_json_eql(query.display_sums.to_json).at_path('sums')
end
it 'should indicate whether the query is publicly visible' do
is_expected.to be_json_eql(query.is_public.to_json).at_path('isPublic')
end
describe 'grouping' do
let(:query) { FactoryGirl.build_stubbed(:query, group_by: 'assigned_to') }
it 'should show the grouping column' do
is_expected.to be_json_eql('assignee'.to_json).at_path('groupBy')
end
context 'without grouping' do
let(:query) { FactoryGirl.build_stubbed(:query, group_by: nil) }
it 'should show no grouping column' do
is_expected.to be_json_eql(nil.to_json).at_path('groupBy')
end
end
is_expected.to be_json_eql(query.is_public.to_json).at_path('public')
end
describe 'with filters' do
let(:query) do
query = FactoryGirl.build_stubbed(:query)
query.add_filter('status_id', '=', ['1'])
query.add_filter('status_id', '=', [filter_status.id.to_s])
allow(query.filters.last)
.to receive(:value_objects)
.and_return([filter_status])
query.add_filter('assigned_to_id', '!', [filter_user.id.to_s])
allow(query.filters.last)
.to receive(:value_objects)
.and_return([filter_user])
query
end
let(:filter_status) { FactoryGirl.build_stubbed(:status) }
let(:filter_user) { FactoryGirl.build_stubbed(:user) }
it 'should render the filters' do
expected = [
{
status: {
operator: '=',
values: ['1']
}
expected_status = {
"_type": "StatusQueryFilter",
"name": "Status",
"_links": {
"filter": {
"href": "/api/v3/queries/filters/status",
"title": "Status"
},
"operator": {
"href": "/api/v3/queries/operators/=",
"title": "is"
},
"values": [
{
"href": api_v3_paths.status(filter_status.id),
"title": filter_status.name
}
]
}
]
}
expected_assignee = {
"_type": "AssigneeQueryFilter",
"name": "Assignee",
"_links": {
"filter": {
"href": "/api/v3/queries/filters/assignee",
"title": "Assignee"
},
"operator": {
"href": "/api/v3/queries/operators/!",
"title": "is not"
},
"values": [
{
"href": api_v3_paths.user(filter_user.id),
"title": filter_user.name
}
]
}
}
expected = [expected_status, expected_assignee]
is_expected.to be_json_eql(expected.to_json).at_path('filters')
end
end
describe 'with sort criteria' do
let(:query) {
let(:query) do
FactoryGirl.build_stubbed(:query,
sort_criteria: [['subject', 'asc'], ['assigned_to', 'desc']])
}
end
it 'should render the filters' do
is_expected.to be_json_eql([
['subject', 'asc'],
['assignee', 'desc']
].to_json).at_path('sortCriteria')
it 'has the sort criteria embedded' do
is_expected
.to be_json_eql('/api/v3/queries/sort_bys/subject-asc'.to_json)
.at_path('_embedded/sortBy/0/_links/self/href')
is_expected
.to be_json_eql('/api/v3/queries/sort_bys/assignee-desc'.to_json)
.at_path('_embedded/sortBy/1/_links/self/href')
end
end
describe 'with columns' do
let(:query) { FactoryGirl.build_stubbed(:query, column_names: ['subject', 'assigned_to']) }
let(:query) do
query = FactoryGirl.build_stubbed(:query, project: project)
it 'should render the filters' do
is_expected.to be_json_eql(['subject', 'assignee'].to_json).at_path('columnNames')
query.column_names = ['status', 'assigned_to', 'updated_at']
query
end
it 'has the columns embedded' do
is_expected
.to be_json_eql('/api/v3/queries/columns/status'.to_json)
.at_path('_embedded/columns/0/_links/self/href')
end
context 'when not embedding' do
let(:representer) do
described_class.new(query, current_user: double('current_user'), embed_links: false)
end
it 'has no columns embedded' do
is_expected
.not_to have_json_path('_embedded/columns')
end
end
end
describe 'with group by' do
let(:query) do
query = FactoryGirl.build_stubbed(:query, project: project)
query.group_by = 'status'
query
end
it 'has the group by embedded' do
is_expected
.to be_json_eql('/api/v3/queries/group_bys/status'.to_json)
.at_path('_embedded/groupBy/_links/self/href')
end
context 'when not embedding' do
let(:representer) do
described_class.new(query, current_user: double('current_user'), embed_links: false)
end
it 'has no group bys embedded' do
is_expected
.not_to have_json_path('_embedded/groupBy')
end
end
end
describe 'embedded results' do
let(:query) { FactoryGirl.build_stubbed(:query) }
let(:representer) do
described_class.new(query,
current_user: double('current_user'),
results: results_representer)
end
context 'results are provided' do
let(:results_representer) do
{
_type: 'BogusResultType'
}
end
it 'should embed the results' do
is_expected
.to be_json_eql('BogusResultType'.to_json)
.at_path('_embedded/results/_type')
end
end
context 'no results provided' do
let(:results_representer) { nil }
it 'should not embed the results' do
is_expected
.not_to have_json_path('_embedded/results')
end
end
end
end

@ -0,0 +1,130 @@
#-- 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'
describe ::API::V3::Queries::SortBys::QuerySortByRepresenter do
include ::API::V3::Utilities::PathHelper
let(:column) { 'status' }
let(:direction) { 'desc' }
let(:representer) do
described_class
.new(::API::V3::Queries::SortBys::SortByDecorator.new(column, direction))
end
subject { representer.to_json }
describe 'generation' do
describe '_links' do
it_behaves_like 'has a titled link' do
let(:link) { 'self' }
let(:href) { api_v3_paths.query_sort_by 'status', 'desc' }
let(:title) { 'Status (Descending)' }
end
end
it 'has _type QuerySortBy' do
is_expected
.to be_json_eql('QuerySortBy'.to_json)
.at_path('_type')
end
it 'has id attribute' do
is_expected
.to be_json_eql('status-desc'.to_json)
.at_path('id')
end
it 'has name attribute' do
is_expected
.to be_json_eql('Status (Descending)'.to_json)
.at_path('name')
end
it_behaves_like 'has a titled link' do
let(:link) { 'column' }
let(:href) { api_v3_paths.query_column 'status' }
let(:title) { 'Status' }
end
it_behaves_like 'has a titled link' do
let(:link) { 'direction' }
let(:href) { "urn:openproject-org:api:v3:queries:directions:#{direction}" }
let(:title) { 'Descending' }
end
context 'when providing an unsupported sort direction' do
let(:direction) { 'bogus' }
it 'raises error' do
expect { subject }.to raise_error(ArgumentError)
end
end
context 'when sorting differently' do
let(:direction) { 'asc' }
it 'has id attribute' do
is_expected
.to be_json_eql('status-asc'.to_json)
.at_path('id')
end
it 'has name attribute' do
is_expected
.to be_json_eql('Status (Ascending)'.to_json)
.at_path('name')
end
end
context 'for a translated column' do
let(:column) { 'assigned_to' }
describe '_links' do
it_behaves_like 'has a titled link' do
let(:link) { 'self' }
let(:href) { api_v3_paths.query_sort_by 'assignee', 'desc' }
let(:title) { 'Assignee (Descending)' }
end
end
it 'has id attribute' do
is_expected
.to be_json_eql('assignee-desc'.to_json)
.at_path('id')
end
it 'has name attribute' do
is_expected
.to be_json_eql('Assignee (Descending)'.to_json)
.at_path('name')
end
end
end
end

@ -242,6 +242,36 @@ describe ::API::V3::Utilities::PathHelper do
it_behaves_like 'api v3 path', '/queries/1/unstar'
end
describe '#query_column' do
subject { helper.query_column 'updated_on' }
it_behaves_like 'api v3 path', '/queries/columns/updated_on'
end
describe '#query_group_by' do
subject { helper.query_group_by 'status' }
it_behaves_like 'api v3 path', '/queries/group_bys/status'
end
describe '#query_sort_by' do
subject { helper.query_sort_by 'status', 'desc' }
it_behaves_like 'api v3 path', '/queries/sort_bys/status-desc'
end
describe '#query_filter' do
subject { helper.query_filter 'status' }
it_behaves_like 'api v3 path', '/queries/filters/status'
end
describe '#query_operator' do
subject { helper.query_operator '=' }
it_behaves_like 'api v3 path', '/queries/operators/='
end
describe 'relations paths' do
describe '#relation' do
subject { helper.relation 1 }

@ -6,6 +6,7 @@ RSpec.describe EnterpriseToken, type: :model do
before do
RequestStore.delete :current_ee_token
allow(OpenProject::Configuration).to receive(:ee_manager_visible?).and_return(true)
end
describe 'existing token' do
@ -20,7 +21,7 @@ RSpec.describe EnterpriseToken, type: :model do
expect(EnterpriseToken.count).to eq(1)
expect(EnterpriseToken.current).to eq(subject)
expect(EnterpriseToken.current.encoded_token).to eq('foo')
expect(EnterpriseToken.show_banners).to eq(false)
expect(EnterpriseToken.show_banners?).to eq(false)
# Deleting it updates the current token
EnterpriseToken.current.destroy!
@ -74,7 +75,7 @@ RSpec.describe EnterpriseToken, type: :model do
it 'has an expired token' do
expect(EnterpriseToken.current).to eq(subject)
expect(EnterpriseToken.show_banners).to eq(true)
expect(EnterpriseToken.show_banners?).to eq(true)
end
end
@ -89,14 +90,23 @@ RSpec.describe EnterpriseToken, type: :model do
describe 'no token' do
it do
expect(EnterpriseToken.current).to be_nil
expect(EnterpriseToken.show_banners).to eq(true)
expect(EnterpriseToken.show_banners?).to eq(true)
end
end
describe 'invalid token' do
it 'appears as if no token is shown' do
expect(EnterpriseToken.current).to be_nil
expect(EnterpriseToken.show_banners).to eq(true)
expect(EnterpriseToken.show_banners?).to eq(true)
end
end
describe "Configuration file has `ee_manager_visible` set to false" do
it 'does not show banners promoting EE' do
expect(OpenProject::Configuration).to receive(:ee_manager_visible?).and_return(false)
expect(EnterpriseToken.show_banners?).to be_falsey
end
end
end

@ -45,9 +45,9 @@ describe Queries::AvailableFilters, type: :model do
let(:includer) do
includer = HelperClass.new(context)
allow(includer)
.to receive(:filter_register)
.and_return(registered_filters)
allow(Queries::Register)
.to receive(:filters)
.and_return(HelperClass => registered_filters)
includer
end

@ -78,5 +78,30 @@ describe Queries::WorkPackages::Filter::CategoryFilter, type: :model do
.to match_array [[category.name, category.id.to_s]]
end
end
describe '#value_objects' do
let(:category1) { FactoryGirl.build_stubbed(:category) }
let(:category2) { FactoryGirl.build_stubbed(:category) }
before do
allow(project)
.to receive(:categories)
.and_return [category1, category2]
instance.values = [category2.id.to_s]
end
it 'returns an array of category' do
expect(instance.value_objects)
.to match_array [category2]
end
end
describe '#ar_object_filter?' do
it 'is true' do
expect(instance)
.to be_ar_object_filter
end
end
end
end

@ -45,5 +45,7 @@ describe Queries::WorkPackages::Filter::CreatedAtFilter, type: :model do
expect(instance.allowed_values).to be_nil
end
end
it_behaves_like 'non ar filter'
end
end

@ -393,4 +393,108 @@ describe Queries::WorkPackages::Filter::CustomFieldFilter, type: :model do
end
end
end
describe '#ar_object_filter? / #value_objects' do
context 'list cf' do
let(:custom_field) { list_wp_custom_field }
it_behaves_like 'non ar filter'
end
context 'bool cf' do
let(:custom_field) { bool_wp_custom_field }
it_behaves_like 'non ar filter'
end
context 'int cf' do
let(:custom_field) { int_wp_custom_field }
it_behaves_like 'non ar filter'
end
context 'float cf' do
let(:custom_field) { float_wp_custom_field }
it_behaves_like 'non ar filter'
end
context 'text cf' do
let(:custom_field) { text_wp_custom_field }
it_behaves_like 'non ar filter'
end
context 'user cf' do
let(:custom_field) { user_wp_custom_field }
describe '#ar_object_filter?' do
it 'is true' do
expect(instance)
.to be_ar_object_filter
end
end
describe '#value_objects' do
let(:user1) { FactoryGirl.build_stubbed(:user) }
let(:user2) { FactoryGirl.build_stubbed(:user) }
before do
allow(User)
.to receive(:find)
.with([user1.id.to_s, user2.id.to_s])
.and_return([user1, user2])
instance.values = [user1.id.to_s, user2.id.to_s]
end
it 'returns an array with users' do
expect(instance.value_objects)
.to match_array([user1, user2])
end
end
end
context 'version cf' do
let(:custom_field) { version_wp_custom_field }
describe '#ar_object_filter?' do
it 'is true' do
expect(instance)
.to be_ar_object_filter
end
end
describe '#value_objects' do
let(:version1) { FactoryGirl.build_stubbed(:version) }
let(:version2) { FactoryGirl.build_stubbed(:version) }
before do
allow(Version)
.to receive(:find)
.with([version1.id.to_s, version2.id.to_s])
.and_return([version1, version2])
instance.values = [version1.id.to_s, version2.id.to_s]
end
it 'returns an array with users' do
expect(instance.value_objects)
.to match_array([version1, version2])
end
end
end
context 'date cf' do
let(:custom_field) { date_wp_custom_field }
it_behaves_like 'non ar filter'
end
context 'string cf' do
let(:custom_field) { string_wp_custom_field }
it_behaves_like 'non ar filter'
end
end
end

@ -45,5 +45,7 @@ describe Queries::WorkPackages::Filter::DueDateFilter, type: :model do
expect(instance.allowed_values).to be_nil
end
end
it_behaves_like 'non ar filter'
end
end

@ -45,5 +45,7 @@ describe Queries::WorkPackages::Filter::EstimatedHoursFilter, type: :model do
expect(instance.allowed_values).to be_nil
end
end
it_behaves_like 'non ar filter'
end
end

@ -67,5 +67,29 @@ describe Queries::WorkPackages::Filter::GroupFilter, type: :model do
.to match_array [[group.name, group.id.to_s]]
end
end
describe '#ar_object_filter?' do
it 'is true' do
expect(instance)
.to be_ar_object_filter
end
end
describe '#value_objects' do
let(:group2) { FactoryGirl.build_stubbed(:group) }
before do
allow(Group)
.to receive(:all)
.and_return([group, group2])
instance.values = [group2.id.to_s]
end
it 'returns an array of groups' do
expect(instance.value_objects)
.to match_array([group2])
end
end
end
end

@ -66,5 +66,29 @@ describe Queries::WorkPackages::Filter::PriorityFilter, type: :model do
.to match_array [[priority.name, priority.id.to_s]]
end
end
describe '#ar_object_filter?' do
it 'is true' do
expect(instance)
.to be_ar_object_filter
end
end
describe '#value_objects' do
let(:priority2) { FactoryGirl.build_stubbed(:priority) }
before do
allow(IssuePriority)
.to receive(:active)
.and_return([priority, priority2])
instance.values = [priority2.id.to_s]
end
it 'returns an array of priorities' do
expect(instance.value_objects)
.to match_array([priority2])
end
end
end
end

@ -86,5 +86,30 @@ describe Queries::WorkPackages::Filter::ProjectFilter, type: :model do
["-- #{child.name}", child.id.to_s]]
end
end
describe '#ar_object_filter?' do
it 'is true' do
expect(instance)
.to be_ar_object_filter
end
end
describe '#value_objects' do
let(:project) { FactoryGirl.build_stubbed(:project) }
let(:project2) { FactoryGirl.build_stubbed(:project) }
before do
allow(Project)
.to receive(:visible)
.and_return([project, project2])
instance.values = [project.id.to_s]
end
it 'returns an array of projects' do
expect(instance.value_objects)
.to match_array([project])
end
end
end
end

@ -132,5 +132,31 @@ describe Queries::WorkPackages::Filter::ResponsibleFilter, type: :model do
end
end
end
describe '#ar_object_filter?' do
it 'is true' do
expect(instance)
.to be_ar_object_filter
end
end
describe '#value_objects' do
let(:user) { FactoryGirl.build_stubbed(:user) }
let(:user2) { FactoryGirl.build_stubbed(:user) }
before do
allow(Principal)
.to receive(:find)
.with([user.id.to_s, user2.id.to_s])
.and_return([user, user2])
instance.values = [user.id.to_s, user2.id.to_s]
end
it 'returns an array of projects' do
expect(instance.value_objects)
.to match_array([user, user2])
end
end
end
end

@ -67,5 +67,29 @@ describe Queries::WorkPackages::Filter::RoleFilter, type: :model do
.to match_array [[role.name, role.id.to_s]]
end
end
describe '#ar_object_filter?' do
it 'is true' do
expect(instance)
.to be_ar_object_filter
end
end
describe '#value_objects' do
let(:role2) { FactoryGirl.build_stubbed(:role) }
before do
allow(Role)
.to receive(:givable)
.and_return([role, role2])
instance.values = [role.id.to_s, role2.id.to_s]
end
it 'returns an array of projects' do
expect(instance.value_objects)
.to match_array([role, role2])
end
end
end
end

@ -45,5 +45,7 @@ describe Queries::WorkPackages::Filter::StartDateFilter, type: :model do
expect(instance.allowed_values).to be_nil
end
end
it_behaves_like 'non ar filter'
end
end

@ -30,6 +30,7 @@ require 'spec_helper'
describe Queries::WorkPackages::Filter::StatusFilter, type: :model do
let(:status) { FactoryGirl.build_stubbed(:status) }
let(:status2) { FactoryGirl.build_stubbed(:status) }
it_behaves_like 'basic query filter' do
let(:order) { 1 }
@ -66,5 +67,27 @@ describe Queries::WorkPackages::Filter::StatusFilter, type: :model do
.to match_array [[status.name, status.id.to_s]]
end
end
describe '#value_objects' do
before do
allow(Status)
.to receive(:all)
.and_return [status, status2]
end
it 'is an array of statuses' do
instance.values = [status.id.to_s]
expect(instance.value_objects)
.to match_array [status]
end
end
describe '#ar_object_filter?' do
it 'is true' do
expect(instance)
.to be_ar_object_filter
end
end
end
end

@ -28,7 +28,6 @@
require 'spec_helper'
describe Queries::WorkPackages::Filter::SubjectFilter, type: :model do
it_behaves_like 'basic query filter' do
let(:order) { 8 }
@ -46,5 +45,7 @@ describe Queries::WorkPackages::Filter::SubjectFilter, type: :model do
expect(instance.allowed_values).to be_nil
end
end
it_behaves_like 'non ar filter'
end
end

@ -101,7 +101,32 @@ describe Queries::WorkPackages::Filter::SubprojectFilter, type: :model do
it 'returns a list of all visible descendants' do
expect(instance.allowed_values).to match_array [[subproject1.name, subproject1.id.to_s],
[subproject2.name, subproject2.id.to_s]]
[subproject2.name, subproject2.id.to_s]]
end
end
describe '#ar_object_filter?' do
it 'is true' do
expect(instance)
.to be_ar_object_filter
end
end
describe '#value_objects' do
let(:subproject1) { FactoryGirl.build_stubbed(:project) }
let(:subproject2) { FactoryGirl.build_stubbed(:project) }
before do
allow(project)
.to receive_message_chain(:descendants, :visible)
.and_return([subproject1, subproject2])
instance.values = [subproject1.id.to_s, subproject2.id.to_s]
end
it 'returns an array of projects' do
expect(instance.value_objects)
.to match_array([subproject1, subproject2])
end
end
end

@ -108,5 +108,30 @@ describe Queries::WorkPackages::Filter::TypeFilter, type: :model do
end
end
end
describe '#ar_object_filter?' do
it 'is true' do
expect(instance)
.to be_ar_object_filter
end
end
describe '#value_objects' do
let(:type1) { FactoryGirl.build_stubbed(:type) }
let(:type2) { FactoryGirl.build_stubbed(:type) }
before do
allow(project)
.to receive(:rolled_up_types)
.and_return([type1, type2])
instance.values = [type1.id.to_s, type2.id.to_s]
end
it 'returns an array of types' do
expect(instance.value_objects)
.to match_array([type1, type2])
end
end
end
end

@ -45,5 +45,7 @@ describe Queries::WorkPackages::Filter::UpdatedAtFilter, type: :model do
expect(instance.allowed_values).to be_nil
end
end
it_behaves_like 'non ar filter'
end
end

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save