add POST /api/v3/views/team_planner

pull/9896/head
ulferts 3 years ago
parent 909635fc15
commit b34310ee48
No known key found for this signature in database
GPG Key ID: A205708DE1284017
  1. 24
      app/contracts/views/base_contract.rb
  2. 68
      config/constants/views.rb
  3. 31
      config/initializers/register_views.rb
  4. 4
      lib/api/v3/utilities/path_helper.rb
  5. 2
      lib/api/v3/views/view_representer.rb
  6. 12
      lib/api/v3/views/views_api.rb
  7. 5
      lib/open_project/plugins/acts_as_op_engine.rb
  8. 24
      modules/team_planner/app/contracts/team_planner/views/contract_strategy.rb
  9. 5
      modules/team_planner/lib/open_project/team_planner/engine.rb
  10. 68
      modules/team_planner/spec/contracts/views/create_contract_team_planner_spec.rb
  11. 0
      modules/team_planner/spec/lib/api/v3/utilities/path_helper_spec.rb
  12. 111
      modules/team_planner/spec/requests/api/v3/views/create_resource_spec.rb
  13. 0
      spec/contracts/views/create_contract_work_packages_table_spec.rb
  14. 28
      spec/contracts/views/shared_contract_examples.rb
  15. 5
      spec/contracts/views/update_contract_spec.rb
  16. 4
      spec/lib/api/v3/utilities/path_helper_spec.rb
  17. 10
      spec/requests/api/v3/views/create_resource_spec.rb
  18. 1
      spec/requests/api/v3/views/index_resource_spec.rb

@ -34,26 +34,40 @@ module Views
validate :query_present,
:query_manageable
def valid?(*_args)
super
# Registered views can have an additional contract configured that
# needs to validate additionally. E.g. a contract can have additional permission checks.
if (strategy_class = Constants::Views.contract_strategy(model.type))
strategy = strategy_class.new(model, user)
strategy.valid?
errors.merge!(strategy.errors)
end
errors.empty?
end
private
def type_allowed
# TODO: fetch from registry
unless ['work_packages_table'].include?(model.type)
unless Constants::Views.registered?(model.type)
errors.add(:type, :inclusion)
end
end
def query_present
unless model.query
if model.query.blank?
errors.add(:query, :blank)
end
end
def query_manageable
return unless model.query
return if model.query.blank?
if query_visible?
errors.add(:query, :error_unauthorized) unless query_permissions?
errors.add(:base, :error_unauthorized) unless query_permissions?
else
errors.add(:query, :does_not_exist)
end

@ -0,0 +1,68 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2021 the OpenProject GmbH
#
# 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 COPYRIGHT and LICENSE files for more details.
#++
module Constants
module Views
class << self
def add(type,
contract_strategy: nil)
@registered ||= {}
@registered[canonical_type(type)] = { contract_strategy: contract_strategy }
end
def registered_types
registered.keys
end
def registered?(type)
type && registered_types.include?(canonical_type(type))
end
def type(type)
searched_type = canonical_type(type)
registered_types.find { |type| type == searched_type }
end
def contract_strategy(type)
if registered?(type)
registered[canonical_type(type)][:contract_strategy]&.constantize
end
end
attr_reader :registered
private
def canonical_type(type)
type.to_s.camelize.to_sym
end
end
end
end

@ -0,0 +1,31 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2021 the OpenProject GmbH
#
# 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 COPYRIGHT and LICENSE files for more details.
#++
require Rails.root.join('config/constants/views')
Constants::Views.add :WorkPackagesTable

@ -420,8 +420,8 @@ module API
index :view
show :view
def self.views_work_packages_table
"#{views}/work_packages_table"
def self.views_type(type)
"#{views}/#{type}"
end
def self.versions_available_projects

@ -88,7 +88,7 @@ module API
date_time_property :updated_at
def _type
'Views::WorkPackagesTable'
"Views::#{Constants::Views.type(represented.type)}"
end
end
end

@ -35,11 +35,17 @@ module API
.new(model: View)
.mount
resources :work_packages_table do
route_param :type_name, type: String, desc: 'View name' do
after_validation do
@type = params['type_name']
raise API::Errors::NotFound unless Constants::Views.registered?(@type)
end
post &::API::V3::Utilities::Endpoints::Create
.new(model: View,
params_modifier: ->(params) {
params.merge(type: 'work_packages_table')
params_modifier: ->(body_params) {
body_params.merge(type: @type)
})
.mount
end

@ -29,6 +29,7 @@
require_dependency 'open_project/ui/extensible_tabs'
require_dependency 'config/constants/api_patch_registry'
require_dependency 'config/constants/open_project/activity'
require_dependency 'config/constants/views'
module OpenProject::Plugins
module ActsAsOpEngine
@ -215,6 +216,10 @@ module OpenProject::Plugins
::OpenProject::Ui::ExtensibleTabs.add(key, name: name, partial: partial, path: path, label: label, only_if: only_if)
end
def add_view(type, contract_strategy: nil)
Constants::Views.add(type, contract_strategy: contract_strategy)
end
def add_api_path(path_name, &block)
config.to_prepare do
::API::V3::Utilities::PathHelper::ApiV3Path.class_eval do

@ -0,0 +1,24 @@
module ::TeamPlanner
module Views
class ContractStrategy < ::BaseContract
validate :manageable
private
def manageable
return if model.query.blank?
errors.add(:base, :error_unauthorized) unless query_permissions?
end
def query_permissions?
# TODO: This currently does not differentiate between public and private queries since it isn't specified yet.
user_allowed_on_query?(:manage_team_planner)
end
def user_allowed_on_query?(permission)
user.allowed_to?(permission, model.query.project, global: model.query.project.nil?)
end
end
end
end

@ -33,7 +33,7 @@ module OpenProject::TeamPlanner
dependencies: %i[view_work_packages]
permission :manage_team_planner,
{ 'team_planner/team_planner': %i[index] },
dependencies: %i[view_team_planner add_work_packages edit_work_packages manage_public_queries]
dependencies: %i[view_team_planner add_work_packages edit_work_packages save_queries manage_public_queries]
end
menu :project_menu,
@ -44,5 +44,8 @@ module OpenProject::TeamPlanner
icon: 'icon2 icon-calendar',
badge: 'label_menu_badge.pre_alpha'
end
add_view :TeamPlanner,
contract_strategy: 'TeamPlanner::Views::ContractStrategy'
end
end

@ -0,0 +1,68 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2021 the OpenProject GmbH
#
# 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 COPYRIGHT and LICENSE files for more details.
#++
require 'spec_helper'
require 'contracts/shared/model_contract_shared_context'
require 'contracts/views/shared_contract_examples'
describe Views::CreateContract do
it_behaves_like 'view contract', true do
let(:view) do
View.new(query: view_query,
type: view_type)
end
let(:view_type) do
'team_planner'
end
let(:permissions) { %i[view_work_packages save_queries manage_team_planner] }
subject(:contract) do
described_class.new(view, current_user)
end
describe 'validation' do
context 'with the type being nil' do
let(:view_type) { nil }
it_behaves_like 'contract is invalid', type: :inclusion
end
context 'with the type not being one of the configured' do
let(:view_type) { 'blubs' }
it_behaves_like 'contract is invalid', type: :inclusion
end
context 'without the :manage_team_planner permission' do
let(:permissions) { %i[view_work_packages save_queries] }
it_behaves_like 'contract is invalid', base: :error_unauthorized
end
end
end
end

@ -0,0 +1,111 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2020 the OpenProject GmbH
#
# 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 COPYRIGHT and LICENSE files for more details.
require 'spec_helper'
describe ::API::V3::Views::ViewsAPI,
'create',
content_type: :json,
type: :request do
include API::V3::Utilities::PathHelper
shared_let(:permitted_user) { FactoryBot.create(:user) }
shared_let(:role) do
FactoryBot.create(:role,
permissions: %w[view_work_packages
save_queries
manage_public_queries
manage_team_planner])
end
shared_let(:project) do
FactoryBot.create(:project,
members: { permitted_user => role })
end
shared_let(:public_query) do
FactoryBot.create(:query,
project: project,
is_public: true)
end
let(:additional_setup) do
# to be overwritten by some specs
end
let(:body) do
{
_links: {
query: {
href: api_v3_paths.query(public_query.id)
}
}
}.to_json
end
let(:send_request) do
post api_v3_paths.views_type('team_planner'), body
end
current_user { permitted_user }
subject(:response) { last_response }
before do
additional_setup
send_request
end
describe 'POST /api/v3/views/team_planner' do
context 'with a user allowed to save the query' do
it 'returns 201 CREATED' do
expect(response.status)
.to eq(201)
end
it 'returns the view' do
expect(response.body)
.to be_json_eql('Views::TeamPlanner'.to_json)
.at_path('_type')
expect(response.body)
.to be_json_eql(View.last.id.to_json)
.at_path('id')
end
end
context 'with a user not allowed to manage team planners' do
let(:additional_setup) do
role.update_attribute(:permissions,
%w[view_work_packages
save_queries
manage_public_queries])
end
it_behaves_like 'unauthorized access'
end
end
end

@ -29,7 +29,7 @@
require 'spec_helper'
require 'contracts/shared/model_contract_shared_context'
shared_examples_for 'view contract' do
shared_examples_for 'view contract' do |disabled_permission_checks|
include_context 'ModelContract shared context'
let(:current_user) do
@ -87,23 +87,25 @@ shared_examples_for 'view contract' do
it_behaves_like 'contract is valid'
end
context 'with the query being private, the user being the query user and not having the :save_queries permission' do
let(:permissions) { %i[view_work_packages] }
unless disabled_permission_checks
context 'with the query being private, the user being the query user and not having the :save_queries permission' do
let(:permissions) { %i[view_work_packages] }
it_behaves_like 'contract is invalid', query: :error_unauthorized
end
it_behaves_like 'contract is invalid', base: :error_unauthorized
end
context 'with the query being public, the user being the query user but only having the :save_queries permission' do
let(:query_public) { true }
context 'with the query being public, the user being the query user but only having the :save_queries permission' do
let(:query_public) { true }
it_behaves_like 'contract is invalid', query: :error_unauthorized
end
it_behaves_like 'contract is invalid', base: :error_unauthorized
end
context 'with the query being public, the user not being the query user but having the :manage_public_queries permission' do
let(:query_public) { true }
let(:permissions) { %i[view_work_packages manage_public_queries] }
context 'with the query being public, the user not being the query user but having the :manage_public_queries permission' do
let(:query_public) { true }
let(:permissions) { %i[view_work_packages manage_public_queries] }
it_behaves_like 'contract is valid'
it_behaves_like 'contract is valid'
end
end
end
end

@ -46,10 +46,9 @@ describe Views::UpdateContract do
describe 'validation' do
context 'with the type being changed' do
# TODO: once a second type is defined, use that here
let(:view_type) { 'another_type' }
let(:view_type) { 'team_planner' }
it_behaves_like 'contract is invalid', type: [:error_readonly, :inclusion]
it_behaves_like 'contract is invalid', type: %i[error_readonly]
end
end
end

@ -503,8 +503,8 @@ describe ::API::V3::Utilities::PathHelper do
it_behaves_like 'show', :view
it_behaves_like 'index', :view
describe '#views_work_packages_table' do
subject { helper.views_work_packages_table }
describe '#views_type' do
subject { helper.views_type('work_packages_table') }
it_behaves_like 'api v3 path', '/views/work_packages_table'
end

@ -61,7 +61,7 @@ describe ::API::V3::Views::ViewsAPI,
end
let(:send_request) do
post api_v3_paths.views_work_packages_table, body
post api_v3_paths.views_type('work_packages_table'), body
end
current_user { permitted_user }
@ -106,4 +106,12 @@ describe ::API::V3::Views::ViewsAPI,
end
end
end
describe 'POST /api/v3/views/bogus' do
let(:send_request) do
post api_v3_paths.views_type('bogus'), body
end
it_behaves_like 'not found'
end
end

@ -32,7 +32,6 @@ describe ::API::V3::Views::ViewsAPI,
'index',
type: :request,
content_type: :json do
include API::V3::Utilities::PathHelper
shared_let(:permitted_user) { FactoryBot.create(:user) }

Loading…
Cancel
Save