Backporting APIv3 categories to OP 4.0

Backport was neccessary to implement missing categories filter without adding more technical debt.
These changes will be merged into 4.1 and following branches and vanish in that merge (merge-strategy "ours")

YOU SHOULD NOT SEE ANY CONTENTS FROM THIS COMMIT ANYWHERE AFTER 4.0.x!

This commit was built from the commits of the GitHub-PR mentioned below, as well as some code dependencies for that.

The GitHub-PR was https://github.com/opf/openproject/pull/2505
pull/2908/head
Jan Sandbrink 10 years ago
parent 8007ad5638
commit 900b2a4c45
  1. 3
      .gitignore
  2. 5
      Gemfile
  3. 36
      Gemfile.lock
  4. 14
      config/initializers/grape.rb
  5. 55
      config/initializers/representable_patch.rb
  6. 4
      doc/apiv3-documentation.apib
  7. 6
      lib/api/decorators/collection.rb
  8. 84
      lib/api/decorators/collection_four_dot_one.rb
  9. 126
      lib/api/decorators/single.rb
  10. 16
      lib/api/utilities/url_helper.rb
  11. 7
      lib/api/v3/activities/activity_representer.rb
  12. 6
      lib/api/v3/attachments/attachment_representer.rb
  13. 16
      lib/api/v3/categories/categories_api.rb
  14. 50
      lib/api/v3/categories/categories_by_project_api.rb
  15. 32
      lib/api/v3/categories/category_collection_representer.rb
  16. 30
      lib/api/v3/categories/category_representer.rb
  17. 4
      lib/api/v3/priorities/priority_collection_representer.rb
  18. 6
      lib/api/v3/priorities/priority_representer.rb
  19. 6
      lib/api/v3/projects/project_representer.rb
  20. 4
      lib/api/v3/projects/projects_api.rb
  21. 6
      lib/api/v3/queries/query_representer.rb
  22. 1
      lib/api/v3/root.rb
  23. 6
      lib/api/v3/root_representer.rb
  24. 4
      lib/api/v3/statuses/status_collection_representer.rb
  25. 6
      lib/api/v3/statuses/status_representer.rb
  26. 6
      lib/api/v3/users/user_representer.rb
  27. 178
      lib/api/v3/utilities/path_helper.rb
  28. 4
      lib/api/v3/versions/version_collection_representer.rb
  29. 6
      lib/api/v3/versions/version_representer.rb
  30. 6
      lib/api/v3/work_packages/relation_representer.rb
  31. 2
      lib/api/v3/work_packages/work_package_model.rb
  32. 18
      lib/api/v3/work_packages/work_package_representer.rb
  33. 32
      spec/lib/api/v3/categories/category_collection_representer_spec.rb
  34. 49
      spec/lib/api/v3/categories/category_representer_spec.rb
  35. 3
      spec/lib/api/v3/projects/project_representer_spec.rb
  36. 51
      spec/lib/api/v3/support/api_v3_collection.rb
  37. 279
      spec/lib/api/v3/utilities/path_helper_spec.rb
  38. 57
      spec/requests/api/v3/category_resource_spec.rb
  39. 49
      spec/requests/api/v3/support/api_v3_collection_response.rb

3
.gitignore vendored

@ -42,6 +42,9 @@
/log/*.log
/tmp
# Ignore RubyMine files
/.idea
/backup
/.project
/.loadpath

@ -179,9 +179,8 @@ end
# API gems
gem 'grape', '~> 0.7.0'
gem 'representable', git: 'https://github.com/finnlabs/representable'
gem 'roar', '~> 0.12.6'
gem 'reform', '~> 1.0.4', require: false
gem 'roar', '~> 1.0.0'
gem 'reform', '~> 1.2.6', require: false
# Use the commented pure ruby gems, if you have not the needed prerequisites on
# board to compile the native ones. Note, that their use is discouraged, since

@ -6,15 +6,6 @@ GIT
rack-protection (1.5.2)
rack
GIT
remote: https://github.com/finnlabs/representable
revision: 5f8fbcb1e61720699135c39be3f36a725ca870ad
specs:
representable (1.8.1)
multi_json
nokogiri
uber
GIT
remote: https://github.com/finnlabs/rspec-example_disabler.git
revision: deb9c38e3f4e3688724583ac1ff58e1ae8aba409
@ -162,8 +153,8 @@ GEM
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
diff-lcs (1.2.5)
disposable (0.0.4)
representable (~> 1.8.1)
disposable (0.0.9)
representable (~> 2.0)
uber
equalizer (0.0.9)
erubis (2.7.0)
@ -310,14 +301,18 @@ GEM
rdoc (3.12.2)
json (~> 1.4)
redcarpet (3.0.0)
reform (1.0.4)
reform (1.2.6)
activemodel
disposable (~> 0.0.4)
representable (~> 1.8.1)
uber (~> 0.0.4)
disposable (~> 0.0.5)
representable (~> 2.1.0)
uber (~> 0.0.11)
representable (2.1.5)
multi_json
nokogiri
uber (~> 0.0.7)
request_store (1.1.0)
roar (0.12.7)
representable (>= 1.6.0)
roar (1.0.0)
representable (>= 2.0.1, <= 3.0.0)
rspec (2.99.0)
rspec-core (~> 2.99.0)
rspec-expectations (~> 2.99.0)
@ -388,7 +383,7 @@ GEM
polyglot
polyglot (>= 0.3.1)
tzinfo (0.3.43)
uber (0.0.10)
uber (0.0.13)
uglifier (2.1.1)
execjs (>= 0.3.0)
multi_json (~> 1.0, >= 1.0.2)
@ -471,10 +466,9 @@ DEPENDENCIES
rails_autolink (~> 1.1.6)
rb-readline (~> 0.5.1)
rdoc (>= 2.4.2)
reform (~> 1.0.4)
representable!
reform (~> 1.2.6)
request_store (~> 1.1.0)
roar (~> 0.12.6)
roar (~> 1.0.0)
rspec (~> 2.99.0)
rspec-activemodel-mocks
rspec-example_disabler!

@ -1,6 +1,6 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2014 the OpenProject Foundation (OPF)
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
@ -26,12 +26,8 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
describe ::API::V3::Categories::CategoryModel do
subject(:model) { ::API::V3::Categories::CategoryModel.new(category) }
let(:category) { FactoryGirl.build(:category, attributes) }
let(:attributes) { { name: 'Specific Category' } }
its(:name) { should eq 'Specific Category' }
module Grape
class Endpoint
include ::API::V3::Utilities::PathHelper
end
end

@ -0,0 +1,55 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'representable'
module OpenProject::RepresentablePatch
def self.included(base)
base.class_eval do
def self.as_strategy=(strategy)
raise 'The :as_strategy option should respond to #call?' unless strategy.respond_to?(:call)
@as_strategy = strategy
end
def self.as_strategy
@as_strategy
end
def self.property(name, options = {}, &block)
options = { as: as_strategy.call(name.to_s) }.merge(options) if as_strategy
super
end
end
end
end
unless Representable::Decorator.included_modules.include?(OpenProject::RepresentablePatch)
Representable::Decorator.send(:include, OpenProject::RepresentablePatch)
end

@ -989,6 +989,10 @@ Updates an activity's comment and, on success, returns the updated activity.
"href": "/api/v3/work_packages/1298",
"title": "nisi eligendi officiis eos delectus quis voluptas dolores"
},
"category": {
"href": "/api/v3/categories/1298",
"title": "eligend isi"
},
"children": [
{
"href": "/api/v3/work_packages/1529",

@ -28,13 +28,13 @@
#++
require 'roar/decorator'
require 'roar/representer/json/hal'
require 'roar/json/hal'
module API
module Decorators
class Collection < Roar::Decorator
include Roar::Representer::JSON::HAL
include Roar::Representer::Feature::Hypermedia
include Roar::JSON::HAL
include Roar::Hypermedia
include OpenProject::StaticRouting::UrlHelpers
attr_reader :current_user, :as

@ -0,0 +1,84 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'roar/decorator'
require 'roar/json/hal'
module API
module Decorators
# This class is purely used for backporting categories to OP4.0. It should not be present in
# OpenProject 4.1 (you can use API::Decorators::Collection there)
class CollectionFourDotOne < Roar::Decorator
include Roar::JSON::HAL
include Roar::Hypermedia
include API::Utilities::UrlHelper
def initialize(models, total, self_link, context: {})
@total = total
@self_link = self_link
@context = context
super(models)
end
class_attribute :element_decorator_class
def self.element_decorator(klass)
self.element_decorator_class = klass
end
def element_decorator
self.class.element_decorator_class
end
as_strategy = API::Utilities::CamelCasingStrategy.new
link :self do
{ href: @self_link }
end
property :_type, getter: -> (*) { 'Collection' }
property :total, getter: -> (*) { @total }, exec_context: :decorator
property :count, getter: -> (*) { empty? ? 0 : count }
collection :elements,
getter: -> (*) {
represented.map { |model|
element_decorator.new(model, context)
}
},
exec_context: :decorator,
embedded: true
private
attr_reader :context
end
end
end

@ -0,0 +1,126 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'roar/decorator'
require 'roar/hypermedia'
require 'roar/json/hal'
require 'api/v3/utilities/path_helper'
module API
module Decorators
class Single < ::Roar::Decorator
include ::Roar::JSON::HAL
include ::Roar::Hypermedia
include ::API::V3::Utilities::PathHelper
attr_reader :context
class_attribute :as_strategy
self.as_strategy = ::API::Utilities::CamelCasingStrategy.new
def initialize(model, context = {})
@context = context
super(model)
end
property :_type,
exec_context: :decorator,
render_nil: false
def self.self_link(path: nil, title_getter: -> (*) { represented.name })
link :self do
path = _type.underscore unless path
link_object = { href: api_v3_paths.send(path, represented.id) }
title = instance_eval(&title_getter)
link_object[:title] = title if title
link_object
end
end
def self.linked_property(property,
path: property,
getter: property,
title_getter: -> (*) { call_or_send_to_represented(getter).name },
show_if: -> (*) { true },
embed_as: nil)
link property.to_s.camelize(:lower) do
next unless instance_eval(&show_if)
value = call_or_send_to_represented(getter)
link_object = { href: (api_v3_paths.send(path, value.id) if value) }
if value
title = instance_eval(&title_getter)
link_object[:title] = title if title
end
link_object
end
if embed_as
embed_property property,
getter: getter,
decorator: embed_as,
show_if: show_if
end
end
def self.embed_property(property, getter: property, decorator:, show_if: true)
property property,
exec_context: :decorator,
getter: -> (*) { call_or_send_to_represented(getter) },
embedded: true,
decorator: decorator,
if: show_if
end
protected
def current_user
context[:current_user]
end
private
def call_or_send_to_represented(callable_or_name)
if callable_or_name.respond_to? :call
instance_exec(&callable_or_name)
else
represented.send(callable_or_name)
end
end
def datetime_formatter
::API::V3::Utilities::DateTimeFormatter
end
def _type; end
end
end
end

@ -1,7 +1,7 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2014 the OpenProject Foundation (OPF)
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
@ -27,17 +27,13 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'reform'
require 'reform/form/coercion'
module API
module V3
module Categories
class CategoryModel < Reform::Form
include Coercion
module Utilities
module UrlHelper
include OpenProject::StaticRouting::UrlHelpers
include ActionView::Helpers::UrlHelper
property :name, type: String
end
def controller; end # The URL helpers need a controller, even if it's nil
end
end
end

@ -28,14 +28,15 @@
#++
require 'roar/decorator'
require 'roar/representer/json/hal'
require 'roar/hypermedia'
require 'roar/json/hal'
module API
module V3
module Activities
class ActivityRepresenter < Roar::Decorator
include Roar::Representer::JSON::HAL
include Roar::Representer::Feature::Hypermedia
include Roar::JSON::HAL
include Roar::Hypermedia
include OpenProject::StaticRouting::UrlHelpers
self.as_strategy = API::Utilities::CamelCasingStrategy.new

@ -28,14 +28,14 @@
#++
require 'roar/decorator'
require 'roar/representer/json/hal'
require 'roar/json/hal'
module API
module V3
module Attachments
class AttachmentRepresenter < Roar::Decorator
include Roar::Representer::JSON::HAL
include Roar::Representer::Feature::Hypermedia
include Roar::JSON::HAL
include Roar::Hypermedia
include OpenProject::StaticRouting::UrlHelpers
self.as_strategy = API::Utilities::CamelCasingStrategy.new

@ -33,13 +33,17 @@ module API
class CategoriesAPI < Grape::API
resources :categories do
before do
@categories = @project.categories
@categories = @categories.map { |category| CategoryModel.new(category) }
end
get do
CategoryCollectionRepresenter.new(@categories, @project)
namespace ':id' do
before do
@category = Category.find(params[:id])
authorize(:view_project, context: @category.project)
end
get do
CategoryRepresenter.new(@category)
end
end
end

@ -0,0 +1,50 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
module API
module V3
module Categories
class CategoriesByProjectAPI < Grape::API
resources :categories do
before do
@categories = @project.categories
end
get do
self_link = api_v3_paths.categories(@project.identifier)
CategoryCollectionRepresenter.new(@categories,
@categories.count,
self_link)
end
end
end
end
end
end

@ -1,7 +1,7 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2014 the OpenProject Foundation (OPF)
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
@ -27,37 +27,11 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'roar/decorator'
require 'representable/json/collection'
require 'roar/representer/json/hal'
module API
module V3
module Categories
class CategoryCollectionRepresenter < Roar::Decorator
include Roar::Representer::JSON::HAL
include OpenProject::StaticRouting::UrlHelpers
self.as_strategy = API::Utilities::CamelCasingStrategy.new
attr_reader :project
def initialize(model, project)
@project = project
super(model)
end
link :self do
"#{root_path}api/v3/projects/#{project.id}/categories"
end
property :_type, exec_context: :decorator
collection :categories, embedded: true, extend: CategoryRepresenter, getter: ->(_) { self }
def _type
'Categories'
end
class CategoryCollectionRepresenter < ::API::Decorators::CollectionFourDotOne
element_decorator ::API::V3::Categories::CategoryRepresenter
end
end
end

@ -27,22 +27,32 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'roar/decorator'
require 'roar/representer/json/hal'
module API
module V3
module Categories
class CategoryRepresenter < Roar::Decorator
include Roar::Representer::JSON::HAL
include Roar::Representer::Feature::Hypermedia
include OpenProject::StaticRouting::UrlHelpers
class CategoryRepresenter < ::API::Decorators::Single
link :self do
{
href: api_v3_paths.category(represented.id),
title: "#{represented.name}"
}
end
self.as_strategy = API::Utilities::CamelCasingStrategy.new
link :project do
{
href: api_v3_paths.project(represented.project.id),
title: represented.project.name
}
end
property :_type, exec_context: :decorator
link :user do
{
href: api_v3_paths.user(represented.assigned_to.id),
title: represented.assigned_to.name
} if represented.assigned_to
end
property :id, getter: -> (*) { model.id }, render_nil: true
property :id, render_nil: true
property :name, render_nil: true
def _type

@ -29,13 +29,13 @@
require 'roar/decorator'
require 'representable/json/collection'
require 'roar/representer/json/hal'
require 'roar/json/hal'
module API
module V3
module Priorities
class PriorityCollectionRepresenter < Roar::Decorator
include Roar::Representer::JSON::HAL
include Roar::JSON::HAL
include OpenProject::StaticRouting::UrlHelpers
self.as_strategy = API::Utilities::CamelCasingStrategy.new

@ -28,14 +28,14 @@
#++
require 'roar/decorator'
require 'roar/representer/json/hal'
require 'roar/json/hal'
module API
module V3
module Priorities
class PriorityRepresenter < Roar::Decorator
include Roar::Representer::JSON::HAL
include Roar::Representer::Feature::Hypermedia
include Roar::JSON::HAL
include Roar::Hypermedia
include OpenProject::StaticRouting::UrlHelpers
self.as_strategy = API::Utilities::CamelCasingStrategy.new

@ -28,14 +28,14 @@
#++
require 'roar/decorator'
require 'roar/representer/json/hal'
require 'roar/json/hal'
module API
module V3
module Projects
class ProjectRepresenter < Roar::Decorator
include Roar::Representer::JSON::HAL
include Roar::Representer::Feature::Hypermedia
include Roar::JSON::HAL
include Roar::Hypermedia
include OpenProject::StaticRouting::UrlHelpers
self.as_strategy = API::Utilities::CamelCasingStrategy.new

@ -40,14 +40,14 @@ module API
before do
@project = Project.find(params[:id])
@model = ProjectModel.new(@project)
authorize(:view_project, context: @project)
end
get do
authorize(:view_project, context: @project)
ProjectRepresenter.new(@model)
end
mount API::V3::Categories::CategoriesAPI
mount API::V3::Categories::CategoriesByProjectAPI
mount API::V3::Versions::VersionsAPI
end

@ -28,14 +28,14 @@
#++
require 'roar/decorator'
require 'roar/representer/json/hal'
require 'roar/json/hal'
module API
module V3
module Queries
class QueryRepresenter < Roar::Decorator
include Roar::Representer::JSON::HAL
include Roar::Representer::Feature::Hypermedia
include Roar::JSON::HAL
include Roar::Hypermedia
include OpenProject::StaticRouting::UrlHelpers
self.as_strategy = API::Utilities::CamelCasingStrategy.new

@ -38,6 +38,7 @@ module API
mount ::API::V3::Activities::ActivitiesAPI
mount ::API::V3::Attachments::AttachmentsAPI
mount ::API::V3::Categories::CategoriesAPI
mount ::API::V3::Priorities::PrioritiesAPI
mount ::API::V3::Projects::ProjectsAPI
mount ::API::V3::Queries::QueriesAPI

@ -28,13 +28,13 @@
#++
require 'roar/decorator'
require 'roar/representer/json/hal'
require 'roar/json/hal'
module API
module V3
class RootRepresenter < Roar::Decorator
include Roar::Representer::JSON::HAL
include Roar::Representer::Feature::Hypermedia
include Roar::JSON::HAL
include Roar::Hypermedia
include OpenProject::StaticRouting::UrlHelpers
self.as_strategy = ::API::Utilities::CamelCasingStrategy.new

@ -29,13 +29,13 @@
require 'roar/decorator'
require 'representable/json/collection'
require 'roar/representer/json/hal'
require 'roar/json/hal'
module API
module V3
module Statuses
class StatusCollectionRepresenter < Roar::Decorator
include Roar::Representer::JSON::HAL
include Roar::JSON::HAL
include OpenProject::StaticRouting::UrlHelpers
self.as_strategy = API::Utilities::CamelCasingStrategy.new

@ -28,14 +28,14 @@
#++
require 'roar/decorator'
require 'roar/representer/json/hal'
require 'roar/json/hal'
module API
module V3
module Statuses
class StatusRepresenter < Roar::Decorator
include Roar::Representer::JSON::HAL
include Roar::Representer::Feature::Hypermedia
include Roar::JSON::HAL
include Roar::Hypermedia
include OpenProject::StaticRouting::UrlHelpers
self.as_strategy = API::Utilities::CamelCasingStrategy.new

@ -28,14 +28,14 @@
#++
require 'roar/decorator'
require 'roar/representer/json/hal'
require 'roar/json/hal'
module API
module V3
module Users
class UserRepresenter < Roar::Decorator
include Roar::Representer::JSON::HAL
include Roar::Representer::Feature::Hypermedia
include Roar::JSON::HAL
include Roar::Hypermedia
include OpenProject::StaticRouting::UrlHelpers
include AvatarHelper

@ -0,0 +1,178 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
module API
module V3
module Utilities
module PathHelper
include API::Utilities::UrlHelper
class ApiV3Path
def self.root
"#{root_path}api/v3"
end
def self.activity(id)
"#{root}/activities/#{id}"
end
def self.attachment(id)
"#{root}/attachments/#{id}"
end
def self.available_assignees(project_id)
"#{project(project_id)}/available_assignees"
end
def self.available_responsibles(project_id)
"#{project(project_id)}/available_responsibles"
end
def self.available_watchers(work_package_id)
"#{work_package(work_package_id)}/available_watchers"
end
def self.categories(project_id)
"#{project(project_id)}/categories"
end
def self.category(id)
"#{root}/categories/#{id}"
end
def self.preview_textile(link)
preview_markup(:textile, link)
end
def self.priorities
"#{root}/priorities"
end
def self.projects
"#{root}/projects"
end
def self.project(id)
"#{projects}/#{id}"
end
def self.query(id)
"#{root}/queries/#{id}"
end
def self.relation(id)
"#{root}/relations/#{id}"
end
def self.statuses
"#{root}/statuses"
end
def self.status(id)
"#{statuses}/#{id}"
end
def self.users
"#{root}/users"
end
def self.user(id)
"#{users}/#{id}"
end
def self.user_lock(id)
"#{user(id)}/lock"
end
def self.version(version_id)
"#{root}/versions/#{version_id}"
end
def self.versions(project_id)
"#{project(project_id)}/versions"
end
def self.versions_projects(version_id)
"#{version(version_id)}/projects"
end
def self.watcher(id, work_package_id)
"#{work_package(work_package_id)}/watchers/#{id}"
end
def self.work_packages
"#{root}/work_packages"
end
def self.work_package(id)
"#{work_packages}/#{id}"
end
def self.work_package_activities(id)
"#{work_package(id)}/activities"
end
def self.work_package_relations(id)
"#{work_package(id)}/relations"
end
def self.work_package_relation(id, work_package_id)
"#{work_package_relations(work_package_id)}/#{id}"
end
def self.work_package_form(id)
"#{work_package(id)}/form"
end
def self.work_package_watchers(id)
"#{work_package(id)}/watchers"
end
def self.root_path
@@root_path ||= Class.new.tap do |c|
c.extend(::API::V3::Utilities::PathHelper)
end.root_path
end
def self.preview_markup(method, link)
path = "#{root}/render/#{method}"
path += "?#{link}" unless link.nil?
path
end
end
def api_v3_paths
ApiV3Path
end
end
end
end
end

@ -29,13 +29,13 @@
require 'roar/decorator'
require 'representable/json/collection'
require 'roar/representer/json/hal'
require 'roar/json/hal'
module API
module V3
module Versions
class VersionCollectionRepresenter < Roar::Decorator
include Roar::Representer::JSON::HAL
include Roar::JSON::HAL
include OpenProject::StaticRouting::UrlHelpers
self.as_strategy = API::Utilities::CamelCasingStrategy.new

@ -28,14 +28,14 @@
#++
require 'roar/decorator'
require 'roar/representer/json/hal'
require 'roar/json/hal'
module API
module V3
module Versions
class VersionRepresenter < Roar::Decorator
include Roar::Representer::JSON::HAL
include Roar::Representer::Feature::Hypermedia
include Roar::JSON::HAL
include Roar::Hypermedia
include OpenProject::StaticRouting::UrlHelpers
self.as_strategy = API::Utilities::CamelCasingStrategy.new

@ -28,14 +28,14 @@
#++
require 'roar/decorator'
require 'roar/representer/json/hal'
require 'roar/json/hal'
module API
module V3
module WorkPackages
class RelationRepresenter < Roar::Decorator
include Roar::Representer::JSON::HAL
include Roar::Representer::Feature::Hypermedia
include Roar::JSON::HAL
include Roar::Hypermedia
include OpenProject::StaticRouting::UrlHelpers
self.as_strategy = API::Utilities::CamelCasingStrategy.new

@ -118,7 +118,7 @@ module API
end
def category
::API::V3::Categories::CategoryModel.new(model.category) unless model.category.nil?
model.category
end
def activities

@ -28,15 +28,18 @@
#++
require 'roar/decorator'
require 'roar/representer/json/hal'
require 'roar/json/hal'
require 'api/v3/utilities/path_helper'
module API
module V3
module WorkPackages
class WorkPackageRepresenter < Roar::Decorator
include Roar::Representer::JSON::HAL
include Roar::Representer::Feature::Hypermedia
include Roar::JSON::HAL
include Roar::Hypermedia
include OpenProject::StaticRouting::UrlHelpers
include ::API::V3::Utilities::PathHelper
self.as_strategy = ::API::Utilities::CamelCasingStrategy.new
@ -199,6 +202,13 @@ module API
} unless represented.model.parent.nil? || !represented.model.parent.visible?
end
link :category do
{
href: api_v3_paths.category(represented.category.id),
title: represented.category.name
} unless represented.category.nil?
end
link :version do
{
href: version_path(represented.model.fixed_version),
@ -254,7 +264,7 @@ module API
property :author, embedded: true, class: ::API::V3::Users::UserModel, decorator: ::API::V3::Users::UserRepresenter, if: -> (*) { !author.nil? }, writeable: false
property :responsible, embedded: true, class: ::API::V3::Users::UserModel, decorator: ::API::V3::Users::UserRepresenter, if: -> (*) { !responsible.nil? }, writeable: false
property :assignee, embedded: true, class: ::API::V3::Users::UserModel, decorator: ::API::V3::Users::UserRepresenter, if: -> (*) { !assignee.nil? }, writeable: false
property :category, embedded: true, class: ::API::V3::Categories::CategoryModel, decorator: ::API::V3::Categories::CategoryRepresenter, if: -> (*) { !category.nil? }, writeable: false
property :category, embedded: true, decorator: ::API::V3::Categories::CategoryRepresenter, if: -> (*) { !category.nil? }, writeable: false
property :activities, embedded: true, exec_context: :decorator, writeable: false
property :watchers, embedded: true, exec_context: :decorator, if: -> (*) { current_user_allowed_to(:view_work_package_watchers, represented.model) }, writeable: false

@ -1,6 +1,6 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2014 the OpenProject Foundation (OPF)
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
@ -29,36 +29,12 @@
require 'spec_helper'
describe ::API::V3::Categories::CategoryCollectionRepresenter do
let(:project) { FactoryGirl.build(:project, id: 888) }
let(:categories) { FactoryGirl.build_list(:category, 3) }
let(:models) { categories.map { |category|
::API::V3::Categories::CategoryModel.new(category)
} }
let(:representer) { described_class.new(models, project) }
describe '#initialize' do
context 'with incorrect parameters' do
it 'should raise without a project' do
expect { described_class.new(models) }.to raise_error(ArgumentError)
end
end
end
let(:representer) { described_class.new(categories, 42, '/api/v3/projects/1/categories') }
context 'generation' do
subject(:generated) { representer.to_json }
it { should include_json('Categories'.to_json).at_path('_type') }
it { should have_json_type(Object).at_path('_links') }
it 'should link to self' do
expect(generated).to have_json_path('_links/self/href')
expect(parse_json(generated, '_links/self/href')).to match %r{/api/v3/projects/888/categories$}
end
subject(:collection) { representer.to_json }
describe 'categories' do
it { should have_json_path('_embedded/categories') }
it { should have_json_size(3).at_path('_embedded/categories') }
it { should have_json_path('_embedded/categories/2/name') }
end
it_behaves_like 'API V3 collection decorated', 42, 3, 'projects/1/categories', 'Category'
end
end

@ -30,22 +30,53 @@ require 'spec_helper'
describe ::API::V3::Categories::CategoryRepresenter do
let(:category) { FactoryGirl.build(:category) }
let(:model) { ::API::V3::Categories::CategoryModel.new(category) }
let(:representer) { described_class.new(model) }
let(:user) { FactoryGirl.build(:user) }
let(:representer) { described_class.new(category) }
context 'generation' do
subject(:generated) { representer.to_json }
it { should include_json('Category'.to_json).at_path('_type') }
shared_examples_for 'category has core values' do
it { is_expected.to include_json('Category'.to_json).at_path('_type') }
xit { should have_json_type(Object).at_path('_links') }
xit 'should link to self' do
expect(subject).to have_json_path('_links/self/href')
it { is_expected.to have_json_type(Object).at_path('_links') }
it 'should link to self' do
expect(subject).to have_json_path('_links/self/href')
end
it 'should display its name as title in self' do
expect(subject).to have_json_path('_links/self/title')
end
it 'should link to its project' do
expect(subject).to have_json_path('_links/project/href')
end
it 'should display its project title' do
expect(subject).to have_json_path('_links/project/title')
end
it { is_expected.to have_json_path('id') }
it { is_expected.to have_json_path('name') }
end
describe 'category' do
it { should have_json_path('id') }
it { should have_json_path('name') }
context 'default assignee not set' do
it_behaves_like 'category has core values'
it 'should not link to an assignee' do
expect(subject).to_not have_json_path('_links/user')
end
end
context 'default assignee set' do
let(:category) {
FactoryGirl.build(:category, assigned_to: user)
}
it_behaves_like 'category has core values'
it 'should link to its default assignee' do
expect(subject).to have_json_path('_links/user/href')
end
it 'should display the name of its default assignee' do
expect(subject).to have_json_path('_links/user/title')
end
end
end
end

@ -53,6 +53,9 @@ describe ::API::V3::Projects::ProjectRepresenter do
it 'should link to self' do
expect(subject).to have_json_path('_links/self/href')
end
it 'should have a title for link to self' do
expect(subject).to have_json_path('_links/self/title')
end
describe 'categories' do
it { should have_json_path('_links/categories') }

@ -0,0 +1,51 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
shared_examples_for 'API V3 collection decorated' do |total, count, self_link, type|
it { expect(collection).to be_json_eql('Collection'.to_json).at_path('_type') }
describe 'elements' do
it { expect(collection).to be_json_eql(type.to_json).at_path('_embedded/elements/0/_type') }
end
describe 'quantities' do
it { expect(collection).to be_json_eql(total.to_json).at_path('total') }
it { expect(collection).to be_json_eql(count.to_json).at_path('count') }
it { expect(collection).to have_json_size(count).at_path('_embedded/elements') }
end
describe '_links' do
let(:href) { "/api/v3/#{self_link}".to_json }
it { expect(collection).to be_json_eql(href).at_path('_links/self/href') }
end
end

@ -0,0 +1,279 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
describe ::API::V3::Utilities::PathHelper do
let(:helper) { Class.new.tap { |c| c.extend(::API::V3::Utilities::PathHelper) }.api_v3_paths }
shared_examples_for 'api v3 path' do
it { is_expected.to match(/^\/api\/v3/) }
end
describe '#root' do
subject { helper.root }
it_behaves_like 'api v3 path'
end
describe '#activity' do
subject { helper.activity 1 }
it_behaves_like 'api v3 path'
it { is_expected.to match(/^\/api\/v3\/activities\/1/) }
end
describe '#attachment' do
subject { helper.attachment 1 }
it_behaves_like 'api v3 path'
it { is_expected.to match(/^\/api\/v3\/attachments\/1/) }
end
describe '#available_assignees' do
subject { helper.available_assignees 42 }
it_behaves_like 'api v3 path'
it { is_expected.to match(/^\/api\/v3\/projects\/42\/available_assignees/) }
end
describe '#available_responsibles' do
subject { helper.available_responsibles 42 }
it_behaves_like 'api v3 path'
it { is_expected.to match(/^\/api\/v3\/projects\/42\/available_responsibles/) }
end
describe '#available_watchers' do
subject { helper.available_watchers 42 }
it_behaves_like 'api v3 path'
it { is_expected.to match(/^\/api\/v3\/work_packages\/42\/available_watchers/) }
end
describe '#categories' do
subject { helper.categories 42 }
it_behaves_like 'api v3 path'
it { is_expected.to match(/^\/api\/v3\/projects\/42\/categories/) }
end
describe '#category' do
subject { helper.category 42 }
it_behaves_like 'api v3 path'
it { is_expected.to match(/^\/api\/v3\/categories\/42/) }
end
describe '#preview_textile' do
subject { helper.preview_textile '/api/v3/work_packages/42' }
it_behaves_like 'api v3 path'
it { is_expected.to match(/^\/api\/v3\/render\/textile/) }
it { is_expected.to match(/\?\/api\/v3\/work_packages\/42$/) }
end
describe '#priorities' do
subject { helper.priorities }
it_behaves_like 'api v3 path'
it { is_expected.to match(/^\/api\/v3\/priorities/) }
end
describe 'projects paths' do
describe '#projects' do
subject { helper.projects }
it_behaves_like 'api v3 path'
it { is_expected.to match(/^\/api\/v3\/projects/) }
end
describe '#project' do
subject { helper.project 1 }
it_behaves_like 'api v3 path'
it { is_expected.to match(/^\/api\/v3\/projects\/1/) }
end
end
describe '#query' do
subject { helper.query 1 }
it_behaves_like 'api v3 path'
it { is_expected.to match(/^\/api\/v3\/queries\/1/) }
end
describe 'relations paths' do
describe '#relation' do
subject { helper.relation 1 }
it_behaves_like 'api v3 path'
it { is_expected.to match(/^\/api\/v3\/relations/) }
end
describe '#relation' do
subject { helper.relation 1 }
it_behaves_like 'api v3 path'
it { is_expected.to match(/^\/api\/v3\/relations\/1/) }
end
end
describe 'statuses paths' do
describe '#statuses' do
subject { helper.statuses }
it_behaves_like 'api v3 path'
it { is_expected.to match(/^\/api\/v3\/statuses/) }
end
describe '#status' do
subject { helper.status 1 }
it_behaves_like 'api v3 path'
it { is_expected.to match(/^\/api\/v3\/statuses\/1/) }
end
end
describe '#user' do
subject { helper.user 1 }
it_behaves_like 'api v3 path'
it { is_expected.to match(/^\/api\/v3\/users\/1/) }
end
describe '#version' do
subject { helper.version 42 }
it_behaves_like 'api v3 path'
it { is_expected.to match(/^\/api\/v3\/versions\/42/) }
end
describe '#versions' do
subject { helper.versions 42 }
it_behaves_like 'api v3 path'
it { is_expected.to match(/^\/api\/v3\/projects\/42\/versions/) }
end
describe '#versions_projects' do
subject { helper.versions_projects 42 }
it_behaves_like 'api v3 path'
it { is_expected.to match(/^\/api\/v3\/versions\/42\/projects/) }
end
describe 'work packages paths' do
shared_examples_for 'api v3 work packages path' do
it { is_expected.to match(/^\/api\/v3\/work_packages/) }
end
describe '#work_packages' do
subject { helper.work_packages }
it_behaves_like 'api v3 work packages path'
end
describe '#work_package' do
subject { helper.work_package 1 }
it_behaves_like 'api v3 work packages path'
it { is_expected.to match(/^\/api\/v3\/work_packages\/1/) }
end
describe '#work_package_activities' do
subject { helper.work_package_activities 42 }
it_behaves_like 'api v3 work packages path'
it { is_expected.to match(/^\/api\/v3\/work_packages\/42\/activities/) }
end
describe '#work_package_relations' do
subject { helper.work_package_relations 42 }
it_behaves_like 'api v3 work packages path'
it { is_expected.to match(/^\/api\/v3\/work_packages\/42\/relations/) }
end
describe '#work_package_relation' do
subject { helper.work_package_relation 1, 42 }
it_behaves_like 'api v3 work packages path'
it { is_expected.to match(/^\/api\/v3\/work_packages\/42\/relations\/1/) }
end
describe '#work_package_form' do
subject { helper.work_package_form 1 }
it_behaves_like 'api v3 work packages path'
it { is_expected.to match(/^\/api\/v3\/work_packages\/1\/form/) }
end
describe '#work_package_watchers' do
subject { helper.work_package_watchers 1 }
it_behaves_like 'api v3 work packages path'
it { is_expected.to match(/^\/api\/v3\/work_packages\/1\/watchers/) }
end
describe '#watcher' do
subject { helper.watcher 1, 42 }
it_behaves_like 'api v3 work packages path'
it { is_expected.to match(/^\/api\/v3\/work_packages\/42\/watchers\/1/) }
end
end
end

@ -32,36 +32,55 @@ require 'rack/test'
describe 'API v3 Category resource' do
include Rack::Test::Methods
let(:current_user) { FactoryGirl.create(:user) }
let(:role) { FactoryGirl.create(:role, permissions: []) }
let(:project) { FactoryGirl.create(:project, is_public: false) }
let(:categories) { FactoryGirl.create_list(:category, 3, project: project) }
let(:other_categories) { FactoryGirl.create_list(:category, 2) }
let(:role) { FactoryGirl.create(:role, permissions: [:view_project]) }
let(:private_project) { FactoryGirl.create(:project, is_public: false) }
let(:public_project) { FactoryGirl.create(:project, is_public: true) }
let(:anonymous_user) { FactoryGirl.create(:user) }
let(:privileged_user) do
FactoryGirl.create(:user,
member_in_project: private_project,
member_through_role: role)
end
let!(:categories) { FactoryGirl.create_list(:category, 3, project: private_project) }
let!(:other_categories) { FactoryGirl.create_list(:category, 2, project: public_project) }
let!(:user_categories) do
FactoryGirl.create_list(:category,
2,
project: private_project,
assigned_to: privileged_user)
end
describe '#get' do
describe 'categories by project' do
subject(:response) { last_response }
context 'logged in user' do
let(:get_path) { "/api/v3/projects/#{project.id}/categories" }
let(:get_path) { "/api/v3/projects/#{private_project.id}/categories" }
before do
allow(User).to receive(:current).and_return current_user
member = FactoryGirl.build(:member, user: current_user, project: project)
member.role_ids = [role.id]
member.save!
categories
other_categories
allow(User).to receive(:current).and_return privileged_user
get get_path
end
it 'should respond with 200' do
expect(subject.status).to eq(200)
it_behaves_like 'API V3 collection response', 5, 5, 'Category'
end
end
describe 'categories/:id' do
subject(:response) { last_response }
context 'logged in user' do
let(:get_path) { "/api/v3/categories/#{other_categories.first.id}" }
before do
allow(User).to receive(:current).and_return privileged_user
get get_path
end
it 'should respond with categories' do
expect(subject.body).to include_json('Categories'.to_json).at_path('_type')
expect(subject.body).to have_json_size(3).at_path('_embedded/categories')
context 'valid priority id' do
it 'should return HTTP 200' do
expect(response.status).to eql(200)
end
end
end
end

@ -0,0 +1,49 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See doc/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
shared_examples_for 'API V3 collection response' do |total, count, type|
subject { response.body }
it { expect(response.status).to eql(200) }
it { is_expected.to be_json_eql('Collection'.to_json).at_path('_type') }
it { is_expected.to be_json_eql(count.to_json).at_path('count') }
it { is_expected.to be_json_eql(total.to_json).at_path('total') }
it { is_expected.to have_json_size(count) .at_path('_embedded/elements') }
it 'has element of specified type if elements exist' do
if count > 0
is_expected.to be_json_eql(type.to_json).at_path('_embedded/elements/0/_type')
end
end
end
Loading…
Cancel
Save