query update and query update form wip

pull/5233/head
Markus Kahl 8 years ago
parent 2711a76fa0
commit 9f0123a703
  1. 7
      app/contracts/queries/base_contract.rb
  2. 7
      app/contracts/queries/create_contract.rb
  3. 35
      app/contracts/queries/update_contract.rb
  4. 58
      app/services/queries/update_query_service.rb
  5. 2
      config/locales/en.yml
  6. 29
      lib/api/v3/queries/create_query.rb
  7. 6
      lib/api/v3/queries/queries_api.rb
  8. 62
      lib/api/v3/queries/update_form_api.rb
  9. 68
      lib/api/v3/queries/update_form_representer.rb
  10. 7
      spec/requests/api/v3/queries/create_query_spec.rb
  11. 309
      spec/requests/api/v3/queries/update_form_api_spec.rb
  12. 186
      spec/requests/api/v3/queries/update_query_spec.rb

@ -46,6 +46,7 @@ module Queries
attr_reader :user
validate :validate_project
validate :user_allowed_to_make_public
def initialize(query, user)
super query
@ -60,5 +61,11 @@ module Queries
def project_visible?
Project.visible(user).where(id: project_id).exists?
end
def user_allowed_to_make_public
if is_public && !user.allowed_to?(:manage_public_queries, model.project)
errors.add :public, :error_unauthorized
end
end
end
end

@ -31,12 +31,5 @@ require 'queries/base_contract'
module Queries
class CreateContract < BaseContract
validate :user_allowed_to_make_public
def user_allowed_to_make_public
if is_public && !user.allowed_to?(:manage_public_queries, model.project)
errors.add :public, :error_unauthorized
end
end
end
end

@ -0,0 +1,35 @@
#-- 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 'queries/base_contract'
module Queries
class UpdateContract < BaseContract
end
end

@ -0,0 +1,58 @@
#-- 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.
#++
class UpdateQueryService
include Concerns::Contracted
attr_reader :user
self.contract = Queries::UpdateContract
def initialize(user:)
@user = user
end
def call(query)
update query
end
private
def update(query)
initialize_contract! query
result, errors = validate_and_save query
ServiceResult.new success: result, errors: errors, result: query
end
def initialize_contract!(query)
self.contract = self.class.contract.new query, user
end
end

@ -251,7 +251,7 @@ en:
activemodel:
errors:
models:
"queries/create_contract":
"queries/base_contract":
attributes:
project:
error_not_found: "not found"

@ -28,6 +28,7 @@
require 'api/v3/queries/query_representer'
require 'queries/create_query_service'
require 'queries/update_query_service'
module API
module V3
@ -45,6 +46,18 @@ module API
end
end
def update_query(query, request_body, current_user)
rep = representer.new query, current_user: current_user
query = rep.from_hash request_body
call = ::UpdateQueryService.new(user: current_user).call query
if call.success?
representer.new call.result, current_user: current_user, embed_links: true
else
fail ::API::Errors::ErrorBase.create_and_merge_errors(call.errors)
end
end
def representer
::API::V3::Queries::QueryRepresenter
end
@ -59,6 +72,22 @@ module API
# the service mutates the given query in place so we just return it
query
end
def foo
rep = representer.new Relation.new, current_user: current_user
relation = rep.from_json request.body.read
attributes = filter_attributes relation
service = ::UpdateRelationService.new relation: Relation.find_by_id!(params[:id]),
user: current_user
call = service.call attributes: attributes,
send_notifications: !(params[:notify] == 'false')
if call.success?
representer.new call.result, current_user: current_user, embed_links: true
else
fail ::API::Errors::ErrorBase.create_and_merge_errors(call.errors)
end
end
end
end
end

@ -98,6 +98,8 @@ module API
requires :id, desc: 'Query id'
end
route_param :id do
mount API::V3::Queries::UpdateFormAPI
before do
@query = Query.find(params[:id])
@ -106,6 +108,10 @@ module API
end
end
patch do
update_query @query, request_body, current_user
end
get do
query_representer_response(@query, params)
end

@ -0,0 +1,62 @@
#-- 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/v3/queries/query_representer'
require 'queries/create_query_service'
module API
module V3
module Queries
class UpdateFormAPI < ::API::OpenProjectAPI
resource :form do
helpers ::API::V3::Queries::CreateQuery
post do
query = @query
representer = ::API::V3::Queries::QueryRepresenter.create query, current_user: current_user
query = representer.from_hash Hash(request_body)
contract = ::Queries::UpdateContract.new query, current_user
contract.validate
query.user = current_user
api_errors = ::API::Errors::ErrorBase.create_errors(contract.errors)
# errors for invalid data (e.g. validation errors) are handled inside the form
if api_errors.all? { |error| error.code == 422 }
status 200
UpdateFormRepresenter.new query, current_user: current_user, errors: api_errors
else
fail ::API::Errors::MultipleErrors.create_if_many(api_errors)
end
end
end
end
end
end
end

@ -0,0 +1,68 @@
#-- 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
class CreateFormRepresenter < FormRepresenter
link :self do
{
href: api_v3_paths.query_form(represented.id),
method: :post
}
end
link :validate do
{
href: api_v3_paths.query_form(represented.id),
method: :post
}
end
link :commit do
if allow_commit?
{
href: api_v3_paths.queries(represented.id),
method: :post
}
end
end
private
def allow_commit?
represented.name.present? && (
(!represented.is_public && current_user.allowed_to?(:save_queries, represented.project)) ||
(represented.is_public && current_user.allowed_to?(:manage_public_queries, represented.project))
) && @errors.empty?
end
end
end
end
end

@ -120,6 +120,13 @@ describe "POST /api/v3/queries", type: :request do
expect(query.columns.map(&:name)).to eq [:id, :subject, :status, :assigned_to]
expect(query.user).to eq user
expect(query.project).to eq project
expect(query.filters.size).to eq 1
filter = query.filters.first
expect(filter.name).to eq :status_id
expect(filter.operator).to eq "="
expect(filter.values).to eq [status.id.to_s]
end
end

@ -0,0 +1,309 @@
#-- 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 'spec_helper'
require 'rack/test'
describe "POST /api/v3/queries/form", type: :request do
include API::V3::Utilities::PathHelper
let(:path) { api_v3_paths.query_form }
let(:user) { FactoryGirl.create(:admin) }
let!(:project) { FactoryGirl.create(:project_with_types) }
let!(:query) { FactoryGirl.create :query, name: "Existing Query", is_public: true }
let(:parameters) { {} }
let(:override_params) { {} }
let(:form) { JSON.parse response.body }
before do
login_as(user)
post path,
params: parameters.merge(override_params).to_json,
headers: { 'CONTENT_TYPE' => 'application/json' }
end
it 'should return 200(OK)' do
expect(response.status).to eq(200)
end
it 'should be of type form' do
expect(form["_type"]).to eq "Form"
end
it 'has the available_projects link for creation in the schema' do
expect(form.dig("_embedded", "schema", "project", "_links", "allowedValues", "href"))
.to eq "/api/v3/queries/available_projects"
end
describe 'with empty parameters' do
it 'has 0 validation errors' do
require 'pry'; binding.pry
expect(form.dig("_embedded", "validationErrors").size).to eq 0
end
end
describe 'with all minimum parameters' do
let(:parameters) do
{
name: "Some Query"
}
end
it 'has 0 validation errors' do
expect(form.dig("_embedded", "validationErrors")).to be_empty
end
it 'has the given name set' do
expect(form.dig("_embedded", "payload", "name")).to eq parameters[:name]
end
end
describe 'with all parameters given' do
let(:status) { FactoryGirl.create :status }
let(:parameters) do
{
name: "Some Query",
public: true,
sums: true,
filters: [
{
name: "Status",
_links: {
filter: {
href: "/api/v3/queries/filters/status"
},
operator: {
"href": "/api/v3/queries/operators/="
},
values: [
{
href: "/api/v3/statuses/#{status.id}",
}
]
}
}
],
_links: {
project: {
href: "/api/v3/projects/#{project.id}"
},
columns: [
{
href: "/api/v3/queries/columns/id"
},
{
href: "/api/v3/queries/columns/subject"
}
],
sortBy: [
{
href: "/api/v3/queries/sort_bys/id-desc"
},
{
href: "/api/v3/queries/sort_bys/assignee-asc"
}
],
groupBy: {
href: "/api/v3/queries/group_bys/assignee"
}
}
}
end
it 'has 0 validation errors' do
expect(form.dig("_embedded", "validationErrors")).to be_empty
end
it 'has a commit link' do
expect(form.dig("_links", "commit")).to be_present
end
it 'has the given name set' do
expect(form.dig("_embedded", "payload", "name")).to eq parameters[:name]
end
it 'has the project set' do
project_link = { "href" => "/api/v3/projects/#{project.id}" }
expect(form.dig("_embedded", "payload", "_links", "project")).to eq project_link
end
it 'is set to public' do
expect(form.dig("_embedded", "payload", "public")).to eq true
end
it 'has the filters set' do
filters = [
{
"_links" => {
"filter" => { "href" => "/api/v3/queries/filters/status" },
"operator" => { "href" => "/api/v3/queries/operators/=" },
"values" => [
{ "href" => "/api/v3/statuses/#{status.id}" }
]
}
}
]
expect(form.dig("_embedded", "payload", "filters")).to eq filters
end
it 'has the columns set' do
columns = [
{ "href" => "/api/v3/queries/columns/id" },
{ "href" => "/api/v3/queries/columns/subject" }
]
expect(form.dig("_embedded", "payload", "_links", "columns")).to eq columns
end
it 'has the groupBy set' do
group_by = { "href" => "/api/v3/queries/group_bys/assignee" }
expect(form.dig("_embedded", "payload", "_links", "groupBy")).to eq group_by
end
it 'has the columns set' do
sort_by = [
{ "href" => "/api/v3/queries/sort_bys/id-desc" },
{ "href" => "/api/v3/queries/sort_bys/assignee-asc" }
]
expect(form.dig("_embedded", "payload", "_links", "sortBy")).to eq sort_by
end
context "with the project referred to by its identifier" do
let(:override_params) do
links = parameters[:_links]
links[:project] = {
href: "/api/v3/projects/#{project.identifier}"
}
{ _links: links }
end
it "still finds the project" do
project_link = { "href" => "/api/v3/projects/#{project.id}" }
expect(form.dig("_embedded", "payload", "_links", "project")).to eq project_link
end
end
context "with groupBy specified as a GET parameter" do
let(:path) { api_v3_paths.query_form + "?groupBy=author"}
let(:override_params) do
links = parameters[:_links]
links.delete :groupBy
{ _links: links }
end
it "initializes the form with the given groupBy" do
expect(form.dig("_embedded", "payload", "_links", "groupBy", "href"))
.to eq "/api/v3/queries/group_bys/author"
end
end
context "with an unknown filter" do
let(:override_params) do
filter = parameters[:filters][0]
filter[:_links][:filter][:href] = "/api/v3/queries/filters/statuz"
{ filters: [filter] }
end
it "returns a validation error" do
expect(form.dig("_embedded", "validationErrors", "base", "message")).to eq "Statuz does not exist."
end
end
context "with an unknown column" do
let(:override_params) do
column = { href: "/api/v3/queries/columns/wurst" }
links = parameters[:_links]
links[:columns] = links[:columns] + [column]
{ _links: links }
end
it "returns a validation error" do
expect(form.dig("_embedded", "validationErrors", "columnNames", "message"))
.to eq "Invalid query column: wurst"
end
end
context "with an invalid groupBy column" do
let(:override_params) do
column = { href: "/api/v3/queries/group_bys/foobar" }
links = parameters[:_links]
links[:groupBy] = column
{ _links: links }
end
it "returns a validation error" do
expect(form.dig("_embedded", "validationErrors", "groupBy", "message"))
.to eq "Can't group by: foobar"
end
end
context "with an invalid sort criterion" do
let(:override_params) do
sort_criterion = { href: "/api/v3/queries/sort_bys/spentTime-desc" }
links = parameters[:_links]
links[:sortBy] = links[:sortBy] + [sort_criterion]
{ _links: links }
end
it "returns a validation error" do
expect(form.dig("_embedded", "validationErrors", "sortCriteria", "message"))
.to eq "Can't sort by column: spent_hours"
end
end
context "with an unauthorized user trying to set the query public" do
let(:user) { FactoryGirl.create :user }
it "should reject the request" do
expect(form.dig("_embedded", "validationErrors", "public", "message"))
.to eq "Public - The user has no permission to create public queries."
end
end
end
end

@ -0,0 +1,186 @@
#-- 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 "PATCH /api/v3/queries/:id", type: :request do
let(:user) { FactoryGirl.create :admin }
let(:status) { FactoryGirl.create :status }
let(:project) { FactoryGirl.create :project }
let!(:query) { FactoryGirl.create :global_query, name: "A Query", is_public: false, display_sums: false }
let(:params) do
{
name: "Dummy Query",
public: true,
filters: [
{
name: "Status",
_links: {
filter: {
href: "/api/v3/queries/filters/status"
},
operator: {
"href": "/api/v3/queries/operators/="
},
schema: {
"href": "/api/v3/queries/filter_instance_schemas/status"
},
values: [
{
href: "/api/v3/statuses/#{status.id}",
}
]
}
}
],
_links: {
project: {
href: "/api/v3/projects/#{project.id}"
},
columns: [
{
href: "/api/v3/queries/columns/id"
},
{
href: "/api/v3/queries/columns/subject"
},
{
href: "/api/v3/queries/columns/status"
},
{
href: "/api/v3/queries/columns/assignee"
}
],
sortBy: [
{
href: "/api/v3/queries/sort_bys/id-desc"
},
{
href: "/api/v3/queries/sort_bys/assignee-asc"
}
],
groupBy: {
href: "/api/v3/queries/group_bys/assignee"
}
}
}
end
before do
login_as user
end
describe "updating a query" do
before do
patch "/api/v3/queries/#{query.id}",
params: params.to_json,
headers: { "Content-Type": "application/json" }
end
it 'should return 200 (ok)' do
expect(response.status).to eq(200)
end
it 'should render the created query' do
json = JSON.parse(response.body)
expect(json["_type"]).to eq "Query"
expect(json["name"]).to eq "Dummy Query"
end
it 'should update the query correctly' do
query = Query.first
expect(query.group_by_column.name).to eq :assigned_to
expect(query.sort_criteria).to eq [["id", "desc"], ["assigned_to", "asc"]]
expect(query.columns.map(&:name)).to eq [:id, :subject, :status, :assigned_to]
expect(query.project).to eq project
expect(query.is_public).to eq true
expect(query.display_sums).to eq false
expect(query.filters.size).to eq 1
filter = query.filters.first
expect(filter.name).to eq :status_id
expect(filter.operator).to eq "="
expect(filter.values).to eq [status.id.to_s]
end
describe "with empty params" do
let(:params) { {} }
it "should not change anything" do
json = JSON.parse(response.body)
expect(json["_type"]).to eq "Query"
expect(json["name"]).to eq "A Query"
end
end
end
context "with invalid parameters" do
def post!
patch "/api/v3/queries/#{query.id}",
params: params.to_json,
headers: { "Content-Type": "application/json" }
end
def json
JSON.parse response.body
end
it "yields a 422 error given an unknown project" do
params[:_links][:project][:href] = "/api/v3/projects/#{project.id}42"
post!
expect(response.status).to eq 422
expect(json["message"]).to eq "Project not found"
end
it "yields a 422 error given an unknown operator" do
params[:filters][0][:_links][:operator][:href] = "/api/v3/queries/operators/wut"
post!
expect(response.status).to eq 422
expect(json["message"]).to eq "Status Operator is not included in the list"
end
it "yields a 422 error given an unknown filter" do
params[:filters][0][:_links][:filter][:href] = "/api/v3/queries/filters/statuz"
post!
expect(response.status).to eq 422
expect(json["message"]).to eq "Statuz does not exist."
end
end
end
Loading…
Cancel
Save