From 8c8d9b7e57ba262f4f54e8670aa6e7977e71134f Mon Sep 17 00:00:00 2001
From: Hagen Schink
Date: Mon, 28 Apr 2014 13:08:04 +0200
Subject: [PATCH 01/36] Add cukes
---
app/views/time_entries/reports/show.html.erb | 3 ++-
features/issues/time_entries.feature | 18 ++++++++++++++++++
features/step_definitions/time_entry_steps.rb | 16 +++++++++++++---
features/support/paths.rb | 4 ++++
4 files changed, 37 insertions(+), 4 deletions(-)
diff --git a/app/views/time_entries/reports/show.html.erb b/app/views/time_entries/reports/show.html.erb
index 9fdb497799..d2afd8f575 100644
--- a/app/views/time_entries/reports/show.html.erb
+++ b/app/views/time_entries/reports/show.html.erb
@@ -47,7 +47,8 @@ See doc/COPYRIGHT.rdoc for more details.
<%= render :partial => 'timelog/date_range' %>
<%= render 'timelog/time_entry_tabs' %>
- <%= l(:label_details) %> : <%= select_tag 'columns', options_for_select([[l(:label_year), 'year'],
+
+ <%= l(:label_details) %> : <%= select_tag 'columns', options_for_select([[l(:label_year), 'year'],
[l(:label_month), 'month'],
[l(:label_week), 'week'],
[l(:label_day_plural).titleize, 'day']], @columns),
diff --git a/features/issues/time_entries.feature b/features/issues/time_entries.feature
index 2f87d9945a..dfaf459f50 100644
--- a/features/issues/time_entries.feature
+++ b/features/issues/time_entries.feature
@@ -65,3 +65,21 @@ Feature: Tracking Time
Then I should see a time entry with 4 hours and comment "updated test"
And I should see a total spent time of 4 hours
+ @javascript
+ Scenario: Selecting time period
+ When I go to the time entry page of issue "issue1"
+ And I select "yesterday" from "period"
+ Then I should not see a total spent time of 0 hours
+
+ When I click "Apply"
+ Then I should see a total spent time of 0 hours
+
+ @javascript
+ Scenario: Selecting month for spent time report
+ When I go to the time entry report page of issue "issue1"
+ And I select "Year" from "Details"
+ And I select "Project" from "Add"
+ Then I should not see a total spent time of 4 hours
+
+ When I click "Apply" within ".timelog-report-selection"
+ Then I should see a total spent time of 4 hours
diff --git a/features/step_definitions/time_entry_steps.rb b/features/step_definitions/time_entry_steps.rb
index 99e3df925d..7adf7b71bb 100644
--- a/features/step_definitions/time_entry_steps.rb
+++ b/features/step_definitions/time_entry_steps.rb
@@ -49,9 +49,19 @@ Then(/^I should see a time entry with (\d+) hours and comment "(.*)"$/) do |hour
expect(page).to have_content(comment)
end
-Then(/^I should see a total spent time of (\d+) hours$/) do |hours|
- within('div.total-hours') do
- expect(find("span.hours-int")).to have_content hours
+Then(/^I should (not )?see a total spent time of (\d+) hours$/) do |negative, hours|
+ available = find('div.total-hours') rescue false
+
+ if available || !negative
+ within('div.total-hours') do
+ element = find("span.hours-int")
+
+ if negative
+ expect(element).not_to have_content hours
+ else
+ expect(element).to have_content hours
+ end
+ end
end
end
diff --git a/features/support/paths.rb b/features/support/paths.rb
index 2159364587..3825997395 100644
--- a/features/support/paths.rb
+++ b/features/support/paths.rb
@@ -352,6 +352,10 @@ module NavigationHelpers
issue_id = WorkPackage.find_by_subject($1).id
"/work_packages/#{issue_id}/time_entries"
+ when /^the time entry report page of issue "(.+)"$/
+ issue_id = WorkPackage.find_by_subject($1).id
+ "/work_packages/#{issue_id}/time_entries/report"
+
when /^the move new page of the work package "(.+)"$/
work_package_id = WorkPackage.find_by_subject($1).id
"/work_packages/#{work_package_id}/move/new?copy="
From 98c3e37d19b798f6090aa3973db19765704390f9 Mon Sep 17 00:00:00 2001
From: Hagen Schink
Date: Mon, 28 Apr 2014 11:10:01 +0200
Subject: [PATCH 02/36] Prevent page reload on time period selection
---
app/views/timelog/_date_range.html.erb | 1 -
1 file changed, 1 deletion(-)
diff --git a/app/views/timelog/_date_range.html.erb b/app/views/timelog/_date_range.html.erb
index 8abd5fd010..f42e1a4da7 100644
--- a/app/views/timelog/_date_range.html.erb
+++ b/app/views/timelog/_date_range.html.erb
@@ -34,7 +34,6 @@ See doc/COPYRIGHT.rdoc for more details.
<%= label_tag "period_type_list", l(:description_date_range_list), :class => "hidden-for-sighted" %>
<%= radio_button_tag 'period_type', '1', !@free_period, :onclick => 'Form.Element.disable("from");Form.Element.disable("to");Form.Element.enable("period");', :id => "period_type_list"%>
<%= select_tag 'period', options_for_period_select(params[:period]),
- :onchange => 'this.form.submit();',
:onfocus => '$("period_type_1").checked = true;',
:disabled => @free_period %>
From ff046c826de9b8e26af44d1624089694c4f9a5b8 Mon Sep 17 00:00:00 2001
From: Hagen Schink
Date: Mon, 28 Apr 2014 11:10:21 +0200
Subject: [PATCH 03/36] Prevent page reload on time/column selection
---
app/views/time_entries/reports/show.html.erb | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/app/views/time_entries/reports/show.html.erb b/app/views/time_entries/reports/show.html.erb
index d2afd8f575..23006d9749 100644
--- a/app/views/time_entries/reports/show.html.erb
+++ b/app/views/time_entries/reports/show.html.erb
@@ -51,14 +51,13 @@ See doc/COPYRIGHT.rdoc for more details.
<%= l(:label_details) %> : <%= select_tag 'columns', options_for_select([[l(:label_year), 'year'],
[l(:label_month), 'month'],
[l(:label_week), 'week'],
- [l(:label_day_plural).titleize, 'day']], @columns),
- :onchange => "this.form.submit();" %>
+ [l(:label_day_plural).titleize, 'day']], @columns) %>
<%= l(:button_add) %> : <%= select_tag('criterias[]', options_for_select([[]] + (@available_criterias.keys - @criterias).collect{|k| [l_or_humanize(@available_criterias[k][:label]), k]}),
- :onchange => "this.form.submit();",
:style => 'width: 200px',
:id => nil,
:disabled => (@criterias.length >= 3), :id => "criterias") %>
+ <%= link_to_function l(:button_apply), '$("query_form").submit()', :class => 'icon icon-yes' %>
<%= link_to l(:button_clear), {:project_id => @project, :issue_id => @issue, :period_type => params[:period_type], :period => params[:period], :from => @from, :to => @to, :columns => @columns}, :class => 'icon icon-undo' %>
<% end %>
From c146860e9288b7876880a0a68d40698b9a25dff0 Mon Sep 17 00:00:00 2001
From: Hagen Schink
Date: Thu, 12 Jun 2014 13:03:53 +0200
Subject: [PATCH 04/36] Fix mouse pointer for directory expander
---
app/assets/stylesheets/scm.css.sass | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/assets/stylesheets/scm.css.sass b/app/assets/stylesheets/scm.css.sass
index 6f30903442..82414e3502 100644
--- a/app/assets/stylesheets/scm.css.sass
+++ b/app/assets/stylesheets/scm.css.sass
@@ -418,6 +418,7 @@ tr.dir
&.dir-expander
@include icon-common
margin-right: -15px
+ cursor: pointer
&:before
content: "\e089"
&.loading
From 6a9d565689b00928a83ad6dda6acf2293eb351c7 Mon Sep 17 00:00:00 2001
From: Marek Takac
Date: Thu, 12 Jun 2014 16:17:28 +0200
Subject: [PATCH 05/36] Created basic queries API endpoint & disabled
experimental queries API endpoints
---
config/routes.rb | 10 +++++-----
lib/api/root.rb | 3 ---
lib/api/v3/queries/queries_api.rb | 24 ++++++++++++++++++++++++
lib/api/v3/root.rb | 1 +
4 files changed, 30 insertions(+), 8 deletions(-)
create mode 100644 lib/api/v3/queries/queries_api.rb
diff --git a/config/routes.rb b/config/routes.rb
index 5d1cfcc9f3..13a21ade4b 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -118,11 +118,11 @@ OpenProject::Application.routes.draw do
get :column_data, on: :collection
get :column_sums, on: :collection
end
- resources :queries, only: [:show, :create, :update, :destroy] do
- get :available_columns, on: :collection
- get :custom_field_filters, on: :collection
- get :grouped, on: :collection
- end
+ # resources :queries, only: [:show, :create, :update, :destroy] do
+ # get :available_columns, on: :collection
+ # get :custom_field_filters, on: :collection
+ # get :grouped, on: :collection
+ # end
resources :projects, only: [:show, :index] do
resources :work_packages, only: [:index] do
diff --git a/lib/api/root.rb b/lib/api/root.rb
index 983c2d9cfb..b8ccea4f76 100644
--- a/lib/api/root.rb
+++ b/lib/api/root.rb
@@ -38,12 +38,9 @@ module API
format 'hal+json'
helpers do
- # Needs refactoring - Will have to find a way how to access sessions in all enviroments
def current_user
return User.current if Rails.env.test?
-
user_id = env['rack.session']['user_id']
-
User.current = user_id ? User.find(user_id) : User.anonymous
end
diff --git a/lib/api/v3/queries/queries_api.rb b/lib/api/v3/queries/queries_api.rb
new file mode 100644
index 0000000000..e4adac60db
--- /dev/null
+++ b/lib/api/v3/queries/queries_api.rb
@@ -0,0 +1,24 @@
+module API
+ module V3
+ module Queries
+ class QueriesAPI < Grape::API
+
+ resources :queries do
+
+ params do
+ requires :id, desc: 'Query id'
+ end
+ namespace ':id' do
+
+ get do
+ { query: 'query' }.to_json
+ end
+
+ end
+
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/root.rb b/lib/api/v3/root.rb
index 9395a0f4e7..7c90637dae 100644
--- a/lib/api/v3/root.rb
+++ b/lib/api/v3/root.rb
@@ -37,6 +37,7 @@ module API
version 'v3', using: :path
mount API::V3::WorkPackages::WorkPackagesAPI
+ mount API::V3::Queries::QueriesAPI
end
end
end
From 49fa88258c6bc6ab66c06315e98d1741dd3b71df Mon Sep 17 00:00:00 2001
From: Marek Takac
Date: Thu, 12 Jun 2014 16:30:15 +0200
Subject: [PATCH 06/36] Created API query model
---
lib/api/v3/queries/query_model.rb | 54 +++++++++++++++++++++++++++++++
1 file changed, 54 insertions(+)
create mode 100644 lib/api/v3/queries/query_model.rb
diff --git a/lib/api/v3/queries/query_model.rb b/lib/api/v3/queries/query_model.rb
new file mode 100644
index 0000000000..a5ba53abaf
--- /dev/null
+++ b/lib/api/v3/queries/query_model.rb
@@ -0,0 +1,54 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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 'reform'
+require 'reform/form/coercion'
+
+module API
+ module V3
+ module Queries
+ class QueryModel < Reform::Form
+ include Composition
+ include Coercion
+
+ model :query
+
+ property :name, on: :query, type: String
+ property :project_id, on: :query, type: Integer
+ property :user_id, on: :query, type: Integer
+ property :filters, on: :query, type: String
+ property :is_public, on: :query, type: String
+ property :column_names, on: :query, type: String
+ property :sort_criteria, on: :query, type: String
+ property :group_by, on: :query, type: String
+ property :display_sums, on: :query, type: String
+ end
+ end
+ end
+end
From a87b90ad81e6e5f773a3377d6ab2b07dfc859813 Mon Sep 17 00:00:00 2001
From: Marek Takac
Date: Thu, 12 Jun 2014 16:36:55 +0200
Subject: [PATCH 07/36] Created queries representer
---
lib/api/v3/queries/query_representer.rb | 70 +++++++++++++++++++++++++
1 file changed, 70 insertions(+)
create mode 100644 lib/api/v3/queries/query_representer.rb
diff --git a/lib/api/v3/queries/query_representer.rb b/lib/api/v3/queries/query_representer.rb
new file mode 100644
index 0000000000..891cbb4831
--- /dev/null
+++ b/lib/api/v3/queries/query_representer.rb
@@ -0,0 +1,70 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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/representer/json/hal'
+
+module API
+ module V3
+ module Queries
+ class QueryRepresenter < Roar::Decorator
+ include Roar::Representer::JSON::HAL
+ include Roar::Representer::Feature::Hypermedia
+ include Rails.application.routes.url_helpers
+
+ self.as_strategy = API::Utilities::CamelCasingStrategy.new
+
+ property :_type, exec_context: :decorator
+
+ link :self do
+ { href: "http://localhost:3000/api/v3/work_packages/#{represented.id}", title: "#{represented.name}" }
+ end
+
+ property :id, getter: -> (*) { query.id }, render_nil: true
+ property :name, render_nil: true
+ property :project_id, getter: -> (*) { query.project.id }
+ property :project_name, getter: -> (*) { query.project.try(:name) }
+ property :user_id, getter: -> (*) { query.user.try(:id) }, render_nil: true
+ property :user_name, getter: -> (*) { query.user.try(:name) }, render_nil: true
+ property :user_login, getter: -> (*) { query.user.try(:login) }, render_nil: true
+ property :user_mail, getter: -> (*) { query.user.try(:mail) }, render_nil: true
+ property :filters, render_nil: true
+ property :is_public, render_nil: true
+ property :column_names, render_nil: true
+ property :sort_criteria, render_nil: true
+ property :group_by, render_nil: true
+ property :display_sums, render_nil: true
+
+ def _type
+ "Query"
+ end
+ end
+ end
+ end
+end
From ecce489a9e64464b29b6ffda41145959b1375df7 Mon Sep 17 00:00:00 2001
From: Marek Takac
Date: Fri, 13 Jun 2014 14:43:19 +0200
Subject: [PATCH 08/36] Created unstar action for queries
---
config/routes.rb | 12 ++---
lib/api/v3/queries/queries_api.rb | 24 ++++++++-
lib/api/v3/queries/query_representer.rb | 6 +--
lib/redmine.rb | 1 +
spec/api/query_resource_spec.rb | 69 +++++++++++++++++++++++++
5 files changed, 101 insertions(+), 11 deletions(-)
create mode 100644 spec/api/query_resource_spec.rb
diff --git a/config/routes.rb b/config/routes.rb
index 13a21ade4b..233c648dff 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -113,16 +113,16 @@ OpenProject::Application.routes.draw do
end
- namespace :v3 do
+ namespace :v3_experimental do
resources :work_packages, only: [:index] do
get :column_data, on: :collection
get :column_sums, on: :collection
end
- # resources :queries, only: [:show, :create, :update, :destroy] do
- # get :available_columns, on: :collection
- # get :custom_field_filters, on: :collection
- # get :grouped, on: :collection
- # end
+ resources :queries, only: [:show, :create, :update, :destroy] do
+ get :available_columns, on: :collection
+ get :custom_field_filters, on: :collection
+ get :grouped, on: :collection
+ end
resources :projects, only: [:show, :index] do
resources :work_packages, only: [:index] do
diff --git a/lib/api/v3/queries/queries_api.rb b/lib/api/v3/queries/queries_api.rb
index e4adac60db..bdb3495c15 100644
--- a/lib/api/v3/queries/queries_api.rb
+++ b/lib/api/v3/queries/queries_api.rb
@@ -10,10 +10,30 @@ module API
end
namespace ':id' do
- get do
- { query: 'query' }.to_json
+ before do
+ @query = Query.find(params[:id])
+ model = QueryModel.new(query: @query)
+ @representer = QueryRepresenter.new(model)
end
+ patch :star do
+ # authorize(:queries_api, :patch, @query.project)
+ normalized_query_name = @query.name.parameterize.underscore
+ query_menu_item = MenuItems::QueryMenuItem.find_or_initialize_by_name_and_navigatable_id normalized_query_name, @query.id, title: @query.name
+
+ if query_menu_item.valid?
+ query_menu_item.save!
+ @representer.to_json
+ else
+ raise ValidationError.new(query_menu_item)
+ end
+ end
+
+ patch :unstar do
+ query_menu_item = @query.query_menu_item
+ query_menu_item.destroy
+ @representer.to_json
+ end
end
end
diff --git a/lib/api/v3/queries/query_representer.rb b/lib/api/v3/queries/query_representer.rb
index 891cbb4831..24c93b0216 100644
--- a/lib/api/v3/queries/query_representer.rb
+++ b/lib/api/v3/queries/query_representer.rb
@@ -43,7 +43,7 @@ module API
property :_type, exec_context: :decorator
link :self do
- { href: "http://localhost:3000/api/v3/work_packages/#{represented.id}", title: "#{represented.name}" }
+ { href: "http://localhost:3000/api/v3/queries/#{represented.query.id}", title: "#{represented.name}" }
end
property :id, getter: -> (*) { query.id }, render_nil: true
@@ -55,11 +55,11 @@ module API
property :user_login, getter: -> (*) { query.user.try(:login) }, render_nil: true
property :user_mail, getter: -> (*) { query.user.try(:mail) }, render_nil: true
property :filters, render_nil: true
- property :is_public, render_nil: true
+ property :is_public, getter: -> (*) { query.is_public.to_s }, render_nil: true
property :column_names, render_nil: true
property :sort_criteria, render_nil: true
property :group_by, render_nil: true
- property :display_sums, render_nil: true
+ property :display_sums, getter: -> (*) { query.display_sums.to_s }, render_nil: true
def _type
"Query"
diff --git a/lib/redmine.rb b/lib/redmine.rb
index e89770f3e6..a1ba536caf 100644
--- a/lib/redmine.rb
+++ b/lib/redmine.rb
@@ -109,6 +109,7 @@ Redmine::AccessControl.map do |map|
:queries => [:index, :create, :update, :available_columns, :custom_field_filters, :grouped],
:work_packages => [:show, :index],
:work_packages_api => [:get],
+ :queries_api => [:get],
:'work_packages/reports' => [:report, :report_details],
:planning_elements => [:index, :all, :show, :recycle_bin],
:planning_element_journals => [:index]}
diff --git a/spec/api/query_resource_spec.rb b/spec/api/query_resource_spec.rb
new file mode 100644
index 0000000000..12814ddff1
--- /dev/null
+++ b/spec/api/query_resource_spec.rb
@@ -0,0 +1,69 @@
+require 'spec_helper'
+require 'rack/test'
+
+describe 'API v3 Query resource' do
+ include Rack::Test::Methods
+
+ let(:work_package) { FactoryGirl.create(:work_package, :project_id => project.id) }
+ let(:project) { FactoryGirl.create(:project, :identifier => 'test_project', :is_public => false) }
+ let(:current_user) { FactoryGirl.create(:user) }
+ let(:role) { FactoryGirl.create(:role, permissions: [:view_work_packages]) }
+ let(:unauthorize_user) { FactoryGirl.create(:user) }
+ let(:query) { FactoryGirl.create(:public_query) }
+ let(:private_query) { FactoryGirl.create(:private_query, project: project) }
+
+ describe '#get' do
+ let(:get_path) { "/api/v3/queries/#{private_query.id}" }
+ let(:expected_response) do
+ {
+ "_type" => 'Query',
+ "_links" => {
+ "self" => {
+ "href" => "http://localhost:3000/api/v3/queries/#{private_query.id}",
+ "title" => private_query.name
+ }
+ },
+ "id" => private_query.id,
+ "name" => private_query.name,
+ "projectId" => private_query.project_id,
+ "projectName" => private_query.project.name,
+ "userId" => private_query.user_id,
+ "userName" => private_query.user.try(:name),
+ "userLogin" => private_query.user.try(:login),
+ "userMail" => private_query.user.try(:mail),
+ "filters" => private_query.filters,
+ "isPublic" => private_query.is_public.to_s,
+ "columnNames" => private_query.column_names,
+ "sortCriteria" => private_query.sort_criteria,
+ "groupBy" => private_query.group_by,
+ "displaySums" => private_query.display_sums.to_s
+ }
+ end
+
+ context 'accessing private queries' do
+ context 'when acting as a user with permission to view query' do
+ before(:each) do
+ allow(User).to receive(:current).and_return current_user
+ member = FactoryGirl.build(:member, user: current_user, project: work_package.project)
+ member.role_ids = [role.id]
+ member.save!
+ get get_path
+ end
+
+ it 'should respond with 200' do
+ last_response.status.should eq(200)
+ end
+
+ it 'should respond with work package in HAL+JSON format' do
+ parsed_response = JSON.parse(last_response.body)
+ parsed_response.should eq(expected_response)
+ end
+ end
+ end
+
+ end
+
+ describe '#star' do
+
+ end
+end
From 698d4dc7cc8df28548786f6c753db22480988303 Mon Sep 17 00:00:00 2001
From: Marek Takac
Date: Fri, 13 Jun 2014 15:21:34 +0200
Subject: [PATCH 09/36] Template for tests
---
config/routes.rb | 6 ++--
lib/api/v3/queries/queries_api.rb | 3 +-
lib/redmine.rb | 2 +-
spec/api/query_resource_spec.rb | 51 ++++---------------------------
4 files changed, 11 insertions(+), 51 deletions(-)
diff --git a/config/routes.rb b/config/routes.rb
index 233c648dff..18f9e78587 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -30,7 +30,7 @@
OpenProject::Application.routes.draw do
root :to => 'welcome#index', :as => 'home'
-
+ mount API::Root => '/'
rails_relative_url_root = OpenProject::Configuration['rails_relative_url_root'] || ''
# Redirect deprecated issue links to new work packages uris
@@ -113,7 +113,7 @@ OpenProject::Application.routes.draw do
end
- namespace :v3_experimental do
+ namespace :v3 do
resources :work_packages, only: [:index] do
get :column_data, on: :collection
get :column_sums, on: :collection
@@ -519,6 +519,4 @@ OpenProject::Application.routes.draw do
match '/:controller(/:action(/:id))'
match '/robots' => 'welcome#robots', :defaults => { :format => :txt }
root :to => 'account#login'
-
- mount API::Root => '/'
end
diff --git a/lib/api/v3/queries/queries_api.rb b/lib/api/v3/queries/queries_api.rb
index bdb3495c15..3ce67a060d 100644
--- a/lib/api/v3/queries/queries_api.rb
+++ b/lib/api/v3/queries/queries_api.rb
@@ -17,7 +17,7 @@ module API
end
patch :star do
- # authorize(:queries_api, :patch, @query.project)
+ authorize(:queries_api, :star, @query.project)
normalized_query_name = @query.name.parameterize.underscore
query_menu_item = MenuItems::QueryMenuItem.find_or_initialize_by_name_and_navigatable_id normalized_query_name, @query.id, title: @query.name
@@ -30,6 +30,7 @@ module API
end
patch :unstar do
+ authorize(:queries_api, :unstar, @query.project)
query_menu_item = @query.query_menu_item
query_menu_item.destroy
@representer.to_json
diff --git a/lib/redmine.rb b/lib/redmine.rb
index a1ba536caf..3219c1871e 100644
--- a/lib/redmine.rb
+++ b/lib/redmine.rb
@@ -109,7 +109,7 @@ Redmine::AccessControl.map do |map|
:queries => [:index, :create, :update, :available_columns, :custom_field_filters, :grouped],
:work_packages => [:show, :index],
:work_packages_api => [:get],
- :queries_api => [:get],
+ :queries_api => [:star, :unstar],
:'work_packages/reports' => [:report, :report_details],
:planning_elements => [:index, :all, :show, :recycle_bin],
:planning_element_journals => [:index]}
diff --git a/spec/api/query_resource_spec.rb b/spec/api/query_resource_spec.rb
index 12814ddff1..ef496fa5ee 100644
--- a/spec/api/query_resource_spec.rb
+++ b/spec/api/query_resource_spec.rb
@@ -12,58 +12,19 @@ describe 'API v3 Query resource' do
let(:query) { FactoryGirl.create(:public_query) }
let(:private_query) { FactoryGirl.create(:private_query, project: project) }
- describe '#get' do
- let(:get_path) { "/api/v3/queries/#{private_query.id}" }
- let(:expected_response) do
- {
- "_type" => 'Query',
- "_links" => {
- "self" => {
- "href" => "http://localhost:3000/api/v3/queries/#{private_query.id}",
- "title" => private_query.name
- }
- },
- "id" => private_query.id,
- "name" => private_query.name,
- "projectId" => private_query.project_id,
- "projectName" => private_query.project.name,
- "userId" => private_query.user_id,
- "userName" => private_query.user.try(:name),
- "userLogin" => private_query.user.try(:login),
- "userMail" => private_query.user.try(:mail),
- "filters" => private_query.filters,
- "isPublic" => private_query.is_public.to_s,
- "columnNames" => private_query.column_names,
- "sortCriteria" => private_query.sort_criteria,
- "groupBy" => private_query.group_by,
- "displaySums" => private_query.display_sums.to_s
- }
- end
+ describe '#star' do
+ context 'anonymous user' do
- context 'accessing private queries' do
- context 'when acting as a user with permission to view query' do
- before(:each) do
- allow(User).to receive(:current).and_return current_user
- member = FactoryGirl.build(:member, user: current_user, project: work_package.project)
- member.role_ids = [role.id]
- member.save!
- get get_path
- end
+ end
- it 'should respond with 200' do
- last_response.status.should eq(200)
- end
+ context 'user with permissions for the project' do
- it 'should respond with work package in HAL+JSON format' do
- parsed_response = JSON.parse(last_response.body)
- parsed_response.should eq(expected_response)
- end
- end
end
+ context 'user without permissions for the project'
end
- describe '#star' do
+ describe '#unstar' do
end
end
From e7a3afb08f5cd9e29e8f427da0dabb941ae21b3a Mon Sep 17 00:00:00 2001
From: Marek Takac
Date: Fri, 13 Jun 2014 17:38:35 +0200
Subject: [PATCH 10/36] Added 'is_starred' property to the query
---
lib/api/v3/queries/query_representer.rb | 6 ++++++
spec/api/query_resource_spec.rb | 4 +++-
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/lib/api/v3/queries/query_representer.rb b/lib/api/v3/queries/query_representer.rb
index 24c93b0216..a7f68c3fb1 100644
--- a/lib/api/v3/queries/query_representer.rb
+++ b/lib/api/v3/queries/query_representer.rb
@@ -60,10 +60,16 @@ module API
property :sort_criteria, render_nil: true
property :group_by, render_nil: true
property :display_sums, getter: -> (*) { query.display_sums.to_s }, render_nil: true
+ property :is_starred, getter: -> (*) { is_starred.to_s }, exec_context: :decorator
def _type
"Query"
end
+
+ def is_starred
+ return true if !represented.query.query_menu_item.nil?
+ false
+ end
end
end
end
diff --git a/spec/api/query_resource_spec.rb b/spec/api/query_resource_spec.rb
index ef496fa5ee..560582acae 100644
--- a/spec/api/query_resource_spec.rb
+++ b/spec/api/query_resource_spec.rb
@@ -21,7 +21,9 @@ describe 'API v3 Query resource' do
end
- context 'user without permissions for the project'
+ context 'user without permissions for the project' do
+
+ end
end
describe '#unstar' do
From a94fbeb7a1a38d15d93c7a9fec3b8b5d8fe202ec Mon Sep 17 00:00:00 2001
From: Ion Biziiac
Date: Fri, 13 Jun 2014 12:18:16 +0300
Subject: [PATCH 11/36] Fix text alignment consistency in the versions tables
---
app/assets/stylesheets/default/main.css.erb | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/app/assets/stylesheets/default/main.css.erb b/app/assets/stylesheets/default/main.css.erb
index 45cdb14c9f..21bab0ef38 100644
--- a/app/assets/stylesheets/default/main.css.erb
+++ b/app/assets/stylesheets/default/main.css.erb
@@ -121,9 +121,9 @@ tr.message td.created_on { white-space: nowrap; }
tr.message td.last_message { white-space: nowrap; }
tr.version td.closed, tr.version td.closed a { text-decoration: line-through; }
-tr.version td.name { padding-left: 20px; }
-tr.version.shared td.name { background: url(<%= asset_path 'link.png' %>) no-repeat 0% 70%; }
-tr.version td.date, tr.version td.status, tr.version td.sharing { text-align: center; white-space: nowrap; }
+tr.version.shared td.name { background: url(<%= asset_path 'link.png' %>) no-repeat 0% 70%; padding-left: 20px; }
+tr.version td.date, tr.version td.status, tr.version td.sharing { white-space: nowrap; }
+tr.version td { padding: 3px 6px; text-align: left; }
tr.user td { width: 13%; }
tr.user td.email { width: 18%; }
From b516f6abbc79034c9fe5f76206b9fb64f9700244 Mon Sep 17 00:00:00 2001
From: Hagen Schink
Date: Mon, 16 Jun 2014 14:14:27 +0200
Subject: [PATCH 12/36] Fix expander and directory icon padding
---
app/views/repositories/_dir_list_content.html.erb | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/views/repositories/_dir_list_content.html.erb b/app/views/repositories/_dir_list_content.html.erb
index 1ee98a6aef..3772013339 100644
--- a/app/views/repositories/_dir_list_content.html.erb
+++ b/app/views/repositories/_dir_list_content.html.erb
@@ -35,7 +35,7 @@ See doc/COPYRIGHT.rdoc for more details.
<% if entry.is_dir? %>
-
+ :class => (entry.is_dir? ? 'icon-context icon-folder' : "icon icon-file #{Redmine::MimeType.css_class_of(ent_name)}")%>
<%= (entry.size ? number_to_human_size(entry.size) : "?") unless entry.is_dir? %>
<% changeset = @project.repository.find_changeset_by_name(entry.lastrev.identifier) if entry.lastrev && entry.lastrev.identifier %>
From 3b90a18fe1279f61b755de9ab415b32f5a13226a Mon Sep 17 00:00:00 2001
From: Hagen Schink
Date: Mon, 16 Jun 2014 14:15:38 +0200
Subject: [PATCH 13/36] Fix expander icon's left's left margin
---
app/assets/stylesheets/scm.css.sass | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/app/assets/stylesheets/scm.css.sass b/app/assets/stylesheets/scm.css.sass
index 82414e3502..7508c51056 100644
--- a/app/assets/stylesheets/scm.css.sass
+++ b/app/assets/stylesheets/scm.css.sass
@@ -417,10 +417,11 @@ tr.dir
span
&.dir-expander
@include icon-common
- margin-right: -15px
cursor: pointer
&:before
content: "\e089"
+ margin-left: 5px
+ padding: 0
&.loading
span.dir-expander:before
content: "\e07a"
From d37b33aca5b9f5208c65e21495c3e18e272baf24 Mon Sep 17 00:00:00 2001
From: Hagen Schink
Date: Mon, 16 Jun 2014 14:16:07 +0200
Subject: [PATCH 14/36] Make filename indentation themable
---
app/assets/stylesheets/default/main.css.erb | 1 -
app/assets/stylesheets/global/_variables.sass | 2 ++
app/assets/stylesheets/scm.css.sass | 8 ++++++++
3 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/app/assets/stylesheets/default/main.css.erb b/app/assets/stylesheets/default/main.css.erb
index 45cdb14c9f..f6b4dcff2b 100644
--- a/app/assets/stylesheets/default/main.css.erb
+++ b/app/assets/stylesheets/default/main.css.erb
@@ -101,7 +101,6 @@ tr.entry td.filename { width: 30%; }
tr.entry td.size { text-align: right; font-size: 90%; }
tr.entry td.revision, tr.entry td.author { text-align: center; }
tr.entry td.age { text-align: right; }
-tr.entry.file td.filename a { margin-left: 16px; }
tr span.expander {cursor: pointer;}
tr.open span .expand { display:none; }
diff --git a/app/assets/stylesheets/global/_variables.sass b/app/assets/stylesheets/global/_variables.sass
index 0b4b2b708a..8a90e0fab1 100644
--- a/app/assets/stylesheets/global/_variables.sass
+++ b/app/assets/stylesheets/global/_variables.sass
@@ -167,3 +167,5 @@ $wiki_toc_header_font_size: 10px !default
$wiki_toc_ul_font_size: 0.8em !default
$journal_attribute_font_size: 11px !default
+
+$repository_entry_filename_margin_left: 24px !default
diff --git a/app/assets/stylesheets/scm.css.sass b/app/assets/stylesheets/scm.css.sass
index 7508c51056..59ef018661 100644
--- a/app/assets/stylesheets/scm.css.sass
+++ b/app/assets/stylesheets/scm.css.sass
@@ -431,3 +431,11 @@ tr.dir
&.open
span.dir-expander:before
content: "\e082"
+
+tr
+ &.entry
+ &.file
+ td
+ &.filename
+ a
+ margin-left: $repository_entry_filename_margin_left
From 28cf8705a531a1c1aba8aa7ba4f8c936c2e8d889 Mon Sep 17 00:00:00 2001
From: Hagen Schink
Date: Mon, 16 Jun 2014 14:31:33 +0200
Subject: [PATCH 15/36] Add changelog entry
---
doc/CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md
index 2d2ecceb08..dc3353b7f1 100644
--- a/doc/CHANGELOG.md
+++ b/doc/CHANGELOG.md
@@ -31,6 +31,7 @@ See doc/COPYRIGHT.rdoc for more details.
* `#4019` Fix focus on project creation
* `#4021` Separate focus for main menu expander
+* `#4258` Text alignment consistency in tables
* `#6288` Editing relations in modal dialog leads to warning
* `#7898` Watchers are not sorted alphabetically in work package screen
From 97bfff11d033350a2cd1ca32a85c812eb0c08a40 Mon Sep 17 00:00:00 2001
From: Marek Takac
Date: Mon, 16 Jun 2014 15:45:20 +0200
Subject: [PATCH 16/36] Added some tests and handled permissions for #star and
#unstar methods
---
lib/api/v3/queries/queries_api.rb | 12 +-
lib/redmine.rb | 5 +-
spec/api/query_resource_spec.rb | 361 +++++++++++++++++++++++++++++-
3 files changed, 365 insertions(+), 13 deletions(-)
diff --git a/lib/api/v3/queries/queries_api.rb b/lib/api/v3/queries/queries_api.rb
index 3ce67a060d..9647dcaad8 100644
--- a/lib/api/v3/queries/queries_api.rb
+++ b/lib/api/v3/queries/queries_api.rb
@@ -17,7 +17,10 @@ module API
end
patch :star do
- authorize(:queries_api, :star, @query.project)
+ authorize(:queries, :star, @query.project)
+ if !@query.is_public? && @query.user_id != current_user.id
+ raise API::Errors::Unauthorized.new(current_user)
+ end
normalized_query_name = @query.name.parameterize.underscore
query_menu_item = MenuItems::QueryMenuItem.find_or_initialize_by_name_and_navigatable_id normalized_query_name, @query.id, title: @query.name
@@ -30,9 +33,14 @@ module API
end
patch :unstar do
- authorize(:queries_api, :unstar, @query.project)
+ authorize(:queries, :unstar, @query.project)
+ if !@query.is_public? && @query.user_id != current_user.id
+ raise API::Errors::Unauthorized.new(current_user)
+ end
query_menu_item = @query.query_menu_item
+ return @representer.to_json if @query.query_menu_item.nil?
query_menu_item.destroy
+ @query.reload
@representer.to_json
end
end
diff --git a/lib/redmine.rb b/lib/redmine.rb
index 3219c1871e..a5154668bb 100644
--- a/lib/redmine.rb
+++ b/lib/redmine.rb
@@ -109,7 +109,6 @@ Redmine::AccessControl.map do |map|
:queries => [:index, :create, :update, :available_columns, :custom_field_filters, :grouped],
:work_packages => [:show, :index],
:work_packages_api => [:get],
- :queries_api => [:star, :unstar],
:'work_packages/reports' => [:report, :report_details],
:planning_elements => [:index, :all, :show, :recycle_bin],
:planning_element_journals => [:index]}
@@ -138,8 +137,8 @@ Redmine::AccessControl.map do |map|
map.permission :manage_work_package_relations, {:work_package_relations => [:create, :destroy]}
map.permission :manage_subtasks, {}
# Queries
- map.permission :manage_public_queries, {:queries => [:new, :edit, :destroy]}, :require => :member
- map.permission :save_queries, {:queries => [:new, :edit, :destroy]}, :require => :loggedin
+ map.permission :manage_public_queries, {:queries => [:new, :edit, :star, :unstar, :destroy]}, :require => :member
+ map.permission :save_queries, {:queries => [:new, :edit, :star, :unstar, :destroy]}, :require => :loggedin
# Watchers
map.permission :view_work_package_watchers, {}
map.permission :add_work_package_watchers, {:watchers => [:new, :create]}
diff --git a/spec/api/query_resource_spec.rb b/spec/api/query_resource_spec.rb
index 560582acae..706397f8cf 100644
--- a/spec/api/query_resource_spec.rb
+++ b/spec/api/query_resource_spec.rb
@@ -4,29 +4,374 @@ require 'rack/test'
describe 'API v3 Query resource' do
include Rack::Test::Methods
- let(:work_package) { FactoryGirl.create(:work_package, :project_id => project.id) }
let(:project) { FactoryGirl.create(:project, :identifier => 'test_project', :is_public => false) }
let(:current_user) { FactoryGirl.create(:user) }
- let(:role) { FactoryGirl.create(:role, permissions: [:view_work_packages]) }
+ let(:manage_public_queries_role) { FactoryGirl.create(:role, permissions: [:manage_public_queries]) }
+ let(:save_queries_role) { FactoryGirl.create(:role, permissions: [:save_queries]) }
+ let(:role_without_query_permissions) { FactoryGirl.create(:role, permissions: [:view_work_packages]) }
let(:unauthorize_user) { FactoryGirl.create(:user) }
- let(:query) { FactoryGirl.create(:public_query) }
- let(:private_query) { FactoryGirl.create(:private_query, project: project) }
describe '#star' do
- context 'anonymous user' do
-
+ let(:star_path) { "/api/v3/queries/#{query.id}/star" }
+ let(:filters) do
+ query.filters.map{ |f| {f.field.to_s => { "operator" => f.operator, "values" => f.values }}}
+ end
+ let(:expected_response) do
+ {
+ "_type" => 'Query',
+ "_links" => {
+ "self" => {
+ "href" => "http://localhost:3000/api/v3/queries/#{query.id}",
+ "title" => query.name
+ }
+ },
+ "id" => query.id,
+ "name" => query.name,
+ "projectId" => query.project_id,
+ "projectName" => query.project.name,
+ "userId" => query.user_id,
+ "userName" => query.user.try(:name),
+ "userLogin" => query.user.try(:login),
+ "userMail" => query.user.try(:mail),
+ "filters" => filters,
+ "isPublic" => query.is_public.to_s,
+ "columnNames" => query.column_names,
+ "sortCriteria" => query.sort_criteria,
+ "groupBy" => query.group_by,
+ "displaySums" => query.display_sums.to_s,
+ "isStarred" => "true"
+ }
end
- context 'user with permissions for the project' do
+ describe 'public queries' do
+ let(:query) { FactoryGirl.create(:public_query, project: project) }
+
+ context 'user with permission to manage public queries' do
+ before(:each) do
+ allow(User).to receive(:current).and_return current_user
+ member = FactoryGirl.build(:member, user: current_user, project: project)
+ member.role_ids = [manage_public_queries_role.id]
+ member.save!
+ end
+
+ context 'when starring an unstarred query' do
+ before(:each) { patch star_path }
+
+ it 'should respond with 200' do
+ last_response.status.should eq(200)
+ end
+
+ it 'should return the query in HAL+JSON format' do
+ parsed_response = JSON.parse(last_response.body)
+ parsed_response.should eq(expected_response)
+ end
+
+ it 'should return the query with "isStarred" property set to true' do
+ parsed_response = JSON.parse(last_response.body)
+ parsed_response['isStarred'].should eq('true')
+ end
+ end
+
+ context 'when starring already starred query' do
+ before(:each) { patch star_path }
+
+ it 'should respond with 200' do
+ last_response.status.should eq(200)
+ end
+
+ it 'should return the query in HAL+JSON format' do
+ parsed_response = JSON.parse(last_response.body)
+ parsed_response.should eq(expected_response)
+ end
+
+ it 'should return the query with "isStarred" property set to true' do
+ parsed_response = JSON.parse(last_response.body)
+ parsed_response['isStarred'].should eq('true')
+ end
+ end
+
+ context 'when trying to star nonexistent query' do
+ let(:star_path) { "/api/v3/queries/999/star" }
+ before(:each) { patch star_path }
+
+ it 'should respond with 404' do
+ last_response.status.should eq(404)
+ end
+
+ it 'should respond with explanatory error message' do
+ parsed_errors = JSON.parse(last_response.body)['errors']
+ parsed_errors.should eq([{ 'key' => 'not_found', 'messages' => ['Couldn\'t find Query with id=999']}])
+ end
+ end
+ end
+
+ context 'user without permission to manage public queries' do
+ before(:each) do
+ allow(User).to receive(:current).and_return current_user
+ member = FactoryGirl.build(:member, user: current_user, project: project)
+ member.role_ids = [role_without_query_permissions.id]
+ member.save!
+ patch star_path
+ end
+ it 'should respond with 403' do
+ last_response.status.should eq(403)
+ end
+
+ it 'should respond with explanatory error message' do
+ parsed_errors = JSON.parse(last_response.body)['errors']
+ parsed_errors.should eq([{ 'key' => 'not_authorized', 'messages' => ['You are not authorize to access this resource']}])
+ end
+ end
end
- context 'user without permissions for the project' do
+ describe 'private queries' do
+ context 'user with permission to save queries' do
+ let(:query) { FactoryGirl.create(:private_query, project: project, user: current_user) }
+ before(:each) do
+ allow(User).to receive(:current).and_return current_user
+ member = FactoryGirl.build(:member, user: current_user, project: project)
+ member.role_ids = [save_queries_role.id]
+ member.save!
+ patch star_path
+ end
+
+ context 'starring his own query' do
+
+ it 'should respond with 200' do
+ last_response.status.should eq(200)
+ end
+
+ it 'should return the query in HAL+JSON format' do
+ parsed_response = JSON.parse(last_response.body)
+ parsed_response.should eq(expected_response)
+ end
+
+ it 'should return the query with "isStarred" property set to true' do
+ parsed_response = JSON.parse(last_response.body)
+ parsed_response['isStarred'].should eq('true')
+ end
+ end
+
+ context 'trying to star somebody else\'s query' do
+ let(:another_user) { FactoryGirl.create(:user) }
+ let(:query) { FactoryGirl.create(:private_query, project: project, user: another_user) }
+
+ it 'should respond with 403' do
+ last_response.status.should eq(403)
+ end
+
+ it 'should respond with explanatory error message' do
+ parsed_errors = JSON.parse(last_response.body)['errors']
+ parsed_errors.should eq([{ 'key' => 'not_authorized', 'messages' => ['You are not authorize to access this resource']}])
+ end
+ end
+ end
+
+ context 'user without permission to save queries' do
+ let(:query) { FactoryGirl.create(:private_query, project: project, user: current_user) }
+ before(:each) do
+ allow(User).to receive(:current).and_return current_user
+ member = FactoryGirl.build(:member, user: current_user, project: project)
+ member.role_ids = [role_without_query_permissions.id]
+ member.save!
+ patch star_path
+ end
+
+ it 'should respond with 403' do
+ last_response.status.should eq(403)
+ end
+
+ it 'should respond with explanatory error message' do
+ parsed_errors = JSON.parse(last_response.body)['errors']
+ parsed_errors.should eq([{ 'key' => 'not_authorized', 'messages' => ['You are not authorize to access this resource']}])
+ end
+ end
end
end
describe '#unstar' do
+ let(:unstar_path) { "/api/v3/queries/#{query.id}/unstar" }
+ let(:filters) do
+ query.filters.map{ |f| {f.field.to_s => { "operator" => f.operator, "values" => f.values }}}
+ end
+ let(:expected_response) do
+ {
+ "_type" => 'Query',
+ "_links" => {
+ "self" => {
+ "href" => "http://localhost:3000/api/v3/queries/#{query.id}",
+ "title" => query.name
+ }
+ },
+ "id" => query.id,
+ "name" => query.name,
+ "projectId" => query.project_id,
+ "projectName" => query.project.name,
+ "userId" => query.user_id,
+ "userName" => query.user.try(:name),
+ "userLogin" => query.user.try(:login),
+ "userMail" => query.user.try(:mail),
+ "filters" => filters,
+ "isPublic" => query.is_public.to_s,
+ "columnNames" => query.column_names,
+ "sortCriteria" => query.sort_criteria,
+ "groupBy" => query.group_by,
+ "displaySums" => query.display_sums.to_s,
+ "isStarred" => "true"
+ }
+ end
+
+ describe 'public queries' do
+ let(:query) { FactoryGirl.create(:public_query, project: project) }
+
+ context 'user with permission to manage public queries' do
+ before(:each) do
+ allow(User).to receive(:current).and_return current_user
+ member = FactoryGirl.build(:member, user: current_user, project: project)
+ member.role_ids = [manage_public_queries_role.id]
+ member.save!
+ end
+
+ context 'when unstarring a starred query' do
+ before(:each) do
+ FactoryGirl.create(:query_menu_item, query: query)
+ patch unstar_path
+ end
+
+ it 'should respond with 200' do
+ last_response.status.should eq(200)
+ end
+
+ it 'should return the query in HAL+JSON format' do
+ parsed_response = JSON.parse(last_response.body)
+ parsed_response.should eq(expected_response.tap{ |r| r["isStarred"] = "false" })
+ end
+
+ it 'should return the query with "isStarred" property set to false' do
+ parsed_response = JSON.parse(last_response.body)
+ parsed_response['isStarred'].should eq('false')
+ end
+ end
+
+ context 'when unstarring an unstarred query' do
+ before(:each) { patch unstar_path }
+
+ it 'should respond with 200' do
+ last_response.status.should eq(200)
+ end
+
+ it 'should return the query in HAL+JSON format' do
+ parsed_response = JSON.parse(last_response.body)
+ parsed_response.should eq(expected_response.tap{ |r| r["isStarred"] = "false" })
+ end
+
+ it 'should return the query with "isStarred" property set to true' do
+ parsed_response = JSON.parse(last_response.body)
+ parsed_response['isStarred'].should eq('false')
+ end
+
+ end
+
+ context 'when trying to unstar nonexistent query' do
+ let(:unstar_path) { "/api/v3/queries/999/unstar" }
+ before(:each) { patch unstar_path }
+
+ it 'should respond with 404' do
+ last_response.status.should eq(404)
+ end
+
+ it 'should respond with explanatory error message' do
+ parsed_errors = JSON.parse(last_response.body)['errors']
+ parsed_errors.should eq([{ 'key' => 'not_found', 'messages' => ['Couldn\'t find Query with id=999']}])
+ end
+ end
+ end
+
+ context 'user without permission to manage public queries' do
+ before(:each) do
+ allow(User).to receive(:current).and_return current_user
+ member = FactoryGirl.build(:member, user: current_user, project: project)
+ member.role_ids = [role_without_query_permissions.id]
+ member.save!
+ patch unstar_path
+ end
+
+ it 'should respond with 403' do
+ last_response.status.should eq(403)
+ end
+
+ it 'should respond with explanatory error message' do
+ parsed_errors = JSON.parse(last_response.body)['errors']
+ parsed_errors.should eq([{ 'key' => 'not_authorized', 'messages' => ['You are not authorize to access this resource']}])
+ end
+ end
+ end
+
+ describe 'private queries' do
+ context 'user with permission to save queries' do
+ let(:query) { FactoryGirl.create(:private_query, project: project, user: current_user) }
+ before(:each) do
+ allow(User).to receive(:current).and_return current_user
+ member = FactoryGirl.build(:member, user: current_user, project: project)
+ member.role_ids = [save_queries_role.id]
+ member.save!
+ patch unstar_path
+ end
+
+ context 'unstarring his own query' do
+
+ it 'should respond with 200' do
+ last_response.status.should eq(200)
+ end
+
+ it 'should return the query in HAL+JSON format' do
+ parsed_response = JSON.parse(last_response.body)
+ parsed_response.should eq(expected_response.tap{ |r| r["isStarred"] = "false" })
+ end
+
+ it 'should return the query with "isStarred" property set to true' do
+ parsed_response = JSON.parse(last_response.body)
+ parsed_response['isStarred'].should eq('false')
+ end
+ end
+
+ context 'trying to unstar somebody else\'s query' do
+ let(:another_user) { FactoryGirl.create(:user) }
+ let(:query) { FactoryGirl.create(:private_query, project: project, user: another_user) }
+
+ it 'should respond with 403' do
+ last_response.status.should eq(403)
+ end
+
+ it 'should respond with explanatory error message' do
+ parsed_errors = JSON.parse(last_response.body)['errors']
+ parsed_errors.should eq([{ 'key' => 'not_authorized', 'messages' => ['You are not authorize to access this resource']}])
+ end
+ end
+ end
+
+ context 'user without permission to save queries' do
+ let(:query) { FactoryGirl.create(:private_query, project: project, user: current_user) }
+ before(:each) do
+ allow(User).to receive(:current).and_return current_user
+ member = FactoryGirl.build(:member, user: current_user, project: project)
+ member.role_ids = [role_without_query_permissions.id]
+ member.save!
+ patch unstar_path
+ end
+
+ it 'should respond with 403' do
+ last_response.status.should eq(403)
+ end
+
+ it 'should respond with explanatory error message' do
+ parsed_errors = JSON.parse(last_response.body)['errors']
+ parsed_errors.should eq([{ 'key' => 'not_authorized', 'messages' => ['You are not authorize to access this resource']}])
+ end
+ end
+ end
end
end
From 83974f95567deade95047e004c1f0b9cfe0cc10c Mon Sep 17 00:00:00 2001
From: Marek Takac
Date: Mon, 16 Jun 2014 17:45:36 +0200
Subject: [PATCH 17/36] Quick & dirty fix for queries starring and unstarring
permissions
---
lib/api/v3/queries/queries_api.rb | 16 ++++++++++------
lib/redmine.rb | 2 +-
2 files changed, 11 insertions(+), 7 deletions(-)
diff --git a/lib/api/v3/queries/queries_api.rb b/lib/api/v3/queries/queries_api.rb
index 9647dcaad8..ceef4e92aa 100644
--- a/lib/api/v3/queries/queries_api.rb
+++ b/lib/api/v3/queries/queries_api.rb
@@ -17,9 +17,11 @@ module API
end
patch :star do
- authorize(:queries, :star, @query.project)
- if !@query.is_public? && @query.user_id != current_user.id
- raise API::Errors::Unauthorized.new(current_user)
+ if !@query.is_public?
+ raise API::Errors::Unauthorized.new(current_user) unless @query.user_id == current_user.id
+ authorize(:queries, :star_private, @query.project)
+ else
+ authorize(:queries, :star, @query.project)
end
normalized_query_name = @query.name.parameterize.underscore
query_menu_item = MenuItems::QueryMenuItem.find_or_initialize_by_name_and_navigatable_id normalized_query_name, @query.id, title: @query.name
@@ -33,9 +35,11 @@ module API
end
patch :unstar do
- authorize(:queries, :unstar, @query.project)
- if !@query.is_public? && @query.user_id != current_user.id
- raise API::Errors::Unauthorized.new(current_user)
+ if !@query.is_public?
+ raise API::Errors::Unauthorized.new(current_user) unless @query.user_id == current_user.id
+ authorize(:queries, :unstar_private, @query.project)
+ else
+ authorize(:queries, :unstar, @query.project)
end
query_menu_item = @query.query_menu_item
return @representer.to_json if @query.query_menu_item.nil?
diff --git a/lib/redmine.rb b/lib/redmine.rb
index a5154668bb..b25f08ebe7 100644
--- a/lib/redmine.rb
+++ b/lib/redmine.rb
@@ -138,7 +138,7 @@ Redmine::AccessControl.map do |map|
map.permission :manage_subtasks, {}
# Queries
map.permission :manage_public_queries, {:queries => [:new, :edit, :star, :unstar, :destroy]}, :require => :member
- map.permission :save_queries, {:queries => [:new, :edit, :star, :unstar, :destroy]}, :require => :loggedin
+ map.permission :save_queries, {:queries => [:new, :edit, :star_private, :unstar_private, :destroy]}, :require => :loggedin
# Watchers
map.permission :view_work_package_watchers, {}
map.permission :add_work_package_watchers, {:watchers => [:new, :create]}
From b1c71ae15fbda9071d1e229cf88b9525d5dceee3 Mon Sep 17 00:00:00 2001
From: Michael Frister
Date: Tue, 17 Jun 2014 13:33:44 +0200
Subject: [PATCH 18/36] Gemfile: Fetch representable via https
---
Gemfile | 2 +-
Gemfile.lock | 18 +++++++++---------
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/Gemfile b/Gemfile
index d0bd139c98..eeed1651ff 100644
--- a/Gemfile
+++ b/Gemfile
@@ -194,7 +194,7 @@ end
# API gems
gem 'grape', '~> 0.7.0'
-gem 'representable', :github => 'finnlabs/representable'
+gem 'representable', git: 'https://github.com/finnlabs/representable'
gem 'roar', '~> 0.12.6'
gem 'reform', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 52e38fcd26..63158834b0 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,12 +1,3 @@
-GIT
- remote: git://github.com/finnlabs/representable.git
- revision: 5f8fbcb1e61720699135c39be3f36a725ca870ad
- specs:
- representable (1.8.1)
- multi_json
- nokogiri
- uber
-
GIT
remote: https://github.com/Compass/compass-rails
revision: f97a4f41518204683aeec7037da3d9b8c57ef4cb
@@ -22,6 +13,15 @@ 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
From 3cbe82aabfa4e6a292d1600994f78b3381912887 Mon Sep 17 00:00:00 2001
From: Christian Ratz
Date: Tue, 17 Jun 2014 14:01:45 +0200
Subject: [PATCH 19/36] fixes wiki font-size after bad resolved conflicts
in ed93fb982d19efef28d
---
app/assets/stylesheets/content/_wiki.sass | 3 ++-
app/assets/stylesheets/global/_variables.sass | 1 +
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/app/assets/stylesheets/content/_wiki.sass b/app/assets/stylesheets/content/_wiki.sass
index d58c026473..fcdbe4ca72 100644
--- a/app/assets/stylesheets/content/_wiki.sass
+++ b/app/assets/stylesheets/content/_wiki.sass
@@ -28,7 +28,7 @@
@import global/all
div.wiki
- font-size: $wiki_toc_ul_font_size
+ font-size: $wiki_default_font_size
line-height: 1.6em
h1, h2
margin: 1em 0 1em 0
@@ -62,6 +62,7 @@ div.wiki
margin-right: 12px
margin-left: 0
display: table
+ font-size: $wiki_toc_ul_font_size
&.right
float: right
margin-left: 12px
diff --git a/app/assets/stylesheets/global/_variables.sass b/app/assets/stylesheets/global/_variables.sass
index 14384dbbcb..f4e12abe22 100644
--- a/app/assets/stylesheets/global/_variables.sass
+++ b/app/assets/stylesheets/global/_variables.sass
@@ -174,6 +174,7 @@ $my_page_edit_box_border_color: #06799F !default
$action_menu_bg_color: #FFFFFF
+$wiki_default_font_size: $global_font_size
$wiki_toc_header_font_size: 10px !default
$wiki_toc_ul_font_size: 0.8em !default
From 7c86e9ee3dc60fea9d4e5c9fa10ffd5e9b30a37d Mon Sep 17 00:00:00 2001
From: Till Breuer
Date: Tue, 17 Jun 2014 15:17:35 +0200
Subject: [PATCH 20/36] Add blank option to group dialog drop down
---
.../angular/controllers/dialogs/group-by.js | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/app/assets/javascripts/angular/controllers/dialogs/group-by.js b/app/assets/javascripts/angular/controllers/dialogs/group-by.js
index b33c8d4c30..f1f3665daf 100644
--- a/app/assets/javascripts/angular/controllers/dialogs/group-by.js
+++ b/app/assets/javascripts/angular/controllers/dialogs/group-by.js
@@ -59,13 +59,20 @@ angular.module('openproject.workPackages.controllers')
$scope.workPackageTableData = WorkPackagesTableService.getWorkPackagesTableData();
+ function buildOptions() {
+ var blankOption = { id: null, label: ' ', other: null };
+
+ $scope.groupableColumnsData = $scope.groupableColumns.map(function(column){
+ return { id: column.name, label: column.title, other: column.title };
+ });
+ $scope.groupableColumnsData.unshift(blankOption);
+ }
+
$scope.$watch('workPackageTableData.groupableColumns', function(groupableColumns){
if (!groupableColumns) return;
$scope.groupableColumns = groupableColumns;
- $scope.groupableColumnsData = groupableColumns.map(function(column){
- return { id: column.name, label: column.title, other: column.title };
- });
+ buildOptions();
var currentGroupBy = $scope.groupableColumnsData.filter(function(column){
return column.id == QueryService.getGroupBy();
From 940655312ab085248ad74dcefb805a26862cd193 Mon Sep 17 00:00:00 2001
From: Marek Takac
Date: Tue, 17 Jun 2014 16:01:35 +0200
Subject: [PATCH 21/36] Refactor API authorize method
---
lib/api/root.rb | 12 +++++++++--
lib/api/v3/queries/queries_api.rb | 21 ++++++++-----------
lib/api/v3/work_packages/work_packages_api.rb | 2 +-
lib/redmine.rb | 2 +-
4 files changed, 21 insertions(+), 16 deletions(-)
diff --git a/lib/api/root.rb b/lib/api/root.rb
index b8ccea4f76..a223599621 100644
--- a/lib/api/root.rb
+++ b/lib/api/root.rb
@@ -45,11 +45,19 @@ module API
end
# Split into two methods: one for authentication, one for authorization
- def authorize(api, endpoint, project = nil, projects = nil, global = false)
+ def authorize(api, endpoint, options)
if current_user.nil? || current_user.anonymous?
raise API::Errors::Unauthenticated.new
end
- is_authorized = AuthorizationService.new(api, endpoint, project, projects, global, current_user).perform
+
+ if !options[:allow].nil?
+ raise API::Errors::Unauthorized.new(current_user) unless options[:allow]
+ end
+
+ global = options[:global] || false
+
+ is_authorized = AuthorizationService.new(api, endpoint, options[:project], options[:projects],
+ global, current_user).perform
unless is_authorized
raise API::Errors::Unauthorized.new(current_user)
end
diff --git a/lib/api/v3/queries/queries_api.rb b/lib/api/v3/queries/queries_api.rb
index ceef4e92aa..b6394d2e83 100644
--- a/lib/api/v3/queries/queries_api.rb
+++ b/lib/api/v3/queries/queries_api.rb
@@ -16,13 +16,15 @@ module API
@representer = QueryRepresenter.new(model)
end
- patch :star do
- if !@query.is_public?
- raise API::Errors::Unauthorized.new(current_user) unless @query.user_id == current_user.id
- authorize(:queries, :star_private, @query.project)
- else
- authorize(:queries, :star, @query.project)
+ helpers do
+ def allowed_to_manage_stars?
+ (@query.is_public? && current_user.allowed_to?(:manage_public_queries, @query.project)) ||
+ (!@query.is_public? && current_user.allowed_to?(:save_queries, @query.project) && @query.user_id == current_user.id)
end
+ end
+
+ patch :star do
+ authorize(:queries, :star, project: @query.project, allow: allowed_to_manage_stars?)
normalized_query_name = @query.name.parameterize.underscore
query_menu_item = MenuItems::QueryMenuItem.find_or_initialize_by_name_and_navigatable_id normalized_query_name, @query.id, title: @query.name
@@ -35,12 +37,7 @@ module API
end
patch :unstar do
- if !@query.is_public?
- raise API::Errors::Unauthorized.new(current_user) unless @query.user_id == current_user.id
- authorize(:queries, :unstar_private, @query.project)
- else
- authorize(:queries, :unstar, @query.project)
- end
+ authorize(:queries, :unstar, project: @query.project, allow: allowed_to_manage_stars?)
query_menu_item = @query.query_menu_item
return @representer.to_json if @query.query_menu_item.nil?
query_menu_item.destroy
diff --git a/lib/api/v3/work_packages/work_packages_api.rb b/lib/api/v3/work_packages/work_packages_api.rb
index 752f863ebf..95836be639 100644
--- a/lib/api/v3/work_packages/work_packages_api.rb
+++ b/lib/api/v3/work_packages/work_packages_api.rb
@@ -17,7 +17,7 @@ module API
end
get do
- authorize(:work_packages_api, :get, @work_package.project)
+ authorize(:work_packages_api, :get, project: @work_package.project)
@representer.to_json
end
diff --git a/lib/redmine.rb b/lib/redmine.rb
index b25f08ebe7..a5154668bb 100644
--- a/lib/redmine.rb
+++ b/lib/redmine.rb
@@ -138,7 +138,7 @@ Redmine::AccessControl.map do |map|
map.permission :manage_subtasks, {}
# Queries
map.permission :manage_public_queries, {:queries => [:new, :edit, :star, :unstar, :destroy]}, :require => :member
- map.permission :save_queries, {:queries => [:new, :edit, :star_private, :unstar_private, :destroy]}, :require => :loggedin
+ map.permission :save_queries, {:queries => [:new, :edit, :star, :unstar, :destroy]}, :require => :loggedin
# Watchers
map.permission :view_work_package_watchers, {}
map.permission :add_work_package_watchers, {:watchers => [:new, :create]}
From d01ef0584b507845b377309f6604b6b4a83f482a Mon Sep 17 00:00:00 2001
From: Marek Takac
Date: Tue, 17 Jun 2014 16:04:24 +0200
Subject: [PATCH 22/36] Extract authenticate method from API authorize method
---
lib/api/root.rb | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/lib/api/root.rb b/lib/api/root.rb
index a223599621..05d3d4ecb1 100644
--- a/lib/api/root.rb
+++ b/lib/api/root.rb
@@ -44,11 +44,13 @@ module API
User.current = user_id ? User.find(user_id) : User.anonymous
end
+ def authenticate
+ raise API::Errors::Unauthenticated.new if current_user.nil? || current_user.anonymous?
+ end
+
# Split into two methods: one for authentication, one for authorization
def authorize(api, endpoint, options)
- if current_user.nil? || current_user.anonymous?
- raise API::Errors::Unauthenticated.new
- end
+ authenticate
if !options[:allow].nil?
raise API::Errors::Unauthorized.new(current_user) unless options[:allow]
From d14f0258fc707f3aafd3f4a727cb99cfded8ad53 Mon Sep 17 00:00:00 2001
From: Marek Takac
Date: Tue, 17 Jun 2014 16:16:10 +0200
Subject: [PATCH 23/36] Refactoring of API authorize method
---
lib/api/root.rb | 19 ++++++++-----------
1 file changed, 8 insertions(+), 11 deletions(-)
diff --git a/lib/api/root.rb b/lib/api/root.rb
index 05d3d4ecb1..a58f2d39db 100644
--- a/lib/api/root.rb
+++ b/lib/api/root.rb
@@ -48,21 +48,13 @@ module API
raise API::Errors::Unauthenticated.new if current_user.nil? || current_user.anonymous?
end
- # Split into two methods: one for authentication, one for authorization
def authorize(api, endpoint, options)
- authenticate
-
- if !options[:allow].nil?
+ unless options[:allow].nil?
raise API::Errors::Unauthorized.new(current_user) unless options[:allow]
end
-
- global = options[:global] || false
-
is_authorized = AuthorizationService.new(api, endpoint, options[:project], options[:projects],
- global, current_user).perform
- unless is_authorized
- raise API::Errors::Unauthorized.new(current_user)
- end
+ !!options[:global], current_user).perform
+ raise API::Errors::Unauthorized.new(current_user) unless is_authorized
is_authorized
end
end
@@ -77,6 +69,11 @@ module API
Rack::Response.new(not_found.to_json, not_found.code, not_found.headers).finish
end
+ # run authentication before each request
+ before do
+ authenticate
+ end
+
mount API::V3::Root
end
end
From 67b33cbb7d5866ea774a7d5543b4e21cece72342 Mon Sep 17 00:00:00 2001
From: Christian Ratz
Date: Tue, 17 Jun 2014 16:30:00 +0200
Subject: [PATCH 24/36] change attachment journals description length
---
..._attachment_journals_description_length.rb | 38 +++++++++++++++++++
1 file changed, 38 insertions(+)
create mode 100644 db/migrate/20130807083716_change_attachment_journals_description_length.rb
diff --git a/db/migrate/20130807083716_change_attachment_journals_description_length.rb b/db/migrate/20130807083716_change_attachment_journals_description_length.rb
new file mode 100644
index 0000000000..8abff5dc5f
--- /dev/null
+++ b/db/migrate/20130807083716_change_attachment_journals_description_length.rb
@@ -0,0 +1,38 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2014 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.
+#++
+
+
+class ChangeAttachmentJournalsDescriptionLength < ActiveRecord::Migration
+ def change
+ change_column :attachment_journals,
+ :description,
+ :text
+ end
+end
+
From 41eb6f6cd4053a9c3fac02c05d43469f0ae25da4 Mon Sep 17 00:00:00 2001
From: Marek Takac
Date: Tue, 17 Jun 2014 16:34:22 +0200
Subject: [PATCH 25/36] Refactored exception handling
---
lib/api/root.rb | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/lib/api/root.rb b/lib/api/root.rb
index a58f2d39db..31926746b1 100644
--- a/lib/api/root.rb
+++ b/lib/api/root.rb
@@ -59,14 +59,14 @@ module API
end
end
- rescue_from API::Errors::Validation, API::Errors::UnwritableProperty, API::Errors::Unauthorized,
- API::Errors::Unauthenticated do |e|
- Rack::Response.new(e.to_json, e.code, e.headers).finish
- end
-
- rescue_from ActiveRecord::RecordNotFound do |e|
- not_found = API::Errors::NotFound.new(e.message)
- Rack::Response.new(not_found.to_json, not_found.code, not_found.headers).finish
+ rescue_from :all do |e|
+ case e.class.to_s
+ when 'API::Errors::Validation', 'API::Errors::UnwritableProperty', 'API::Errors::Unauthorized', 'API::Errors::Unauthenticated'
+ Rack::Response.new(e.to_json, e.code, e.headers).finish
+ when 'ActiveRecord::RecordNotFound'
+ not_found = API::Errors::NotFound.new(e.message)
+ Rack::Response.new(not_found.to_json, not_found.code, not_found.headers).finish
+ end
end
# run authentication before each request
From 103845acc375c69163bd69ecf4cd93d02bb0563b Mon Sep 17 00:00:00 2001
From: Till Breuer
Date: Tue, 17 Jun 2014 16:37:22 +0200
Subject: [PATCH 26/36] Hotfix - always derive unused columns from cached
columns
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
don’t cache them separately as state changes would need to refresh the unused columns array [ci skip]
---
app/assets/javascripts/angular/services/query-service.js | 4 ----
1 file changed, 4 deletions(-)
diff --git a/app/assets/javascripts/angular/services/query-service.js b/app/assets/javascripts/angular/services/query-service.js
index 7341aa6908..452d574e78 100644
--- a/app/assets/javascripts/angular/services/query-service.js
+++ b/app/assets/javascripts/angular/services/query-service.js
@@ -145,10 +145,6 @@ angular.module('openproject.services')
},
loadAvailableUnusedColumns: function(projectIdentifier) {
- if(availableUnusedColumns.length) {
- return $q.when(availableUnusedColumns);
- }
-
return QueryService.loadAvailableColumns(projectIdentifier)
.then(function(available_columns) {
availableUnusedColumns = WorkPackagesTableHelper.getColumnDifference(available_columns, QueryService.getSelectedColumns());
From d637354013ca775448e8985029cef8f9dc7607fe Mon Sep 17 00:00:00 2001
From: Ion Biziiac
Date: Tue, 17 Jun 2014 16:30:14 +0300
Subject: [PATCH 27/36] Add specs for Fix: APIv2 does not rewire parents
correctly
---
.../v2/planning_elements_controller_spec.rb | 23 +++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/spec/controllers/api/v2/planning_elements_controller_spec.rb b/spec/controllers/api/v2/planning_elements_controller_spec.rb
index a84e4c4792..1364bdd829 100644
--- a/spec/controllers/api/v2/planning_elements_controller_spec.rb
+++ b/spec/controllers/api/v2/planning_elements_controller_spec.rb
@@ -238,6 +238,29 @@ describe Api::V2::PlanningElementsController do
end
end
+ describe 'w/ cross-project relations' do
+ before do
+ Setting.stub(:cross_project_work_package_relations?).and_return(true)
+ end
+
+ let!(:project1) { FactoryGirl.create(:project, :identifier => 'project-1') }
+ let!(:project2) { FactoryGirl.create(:project, :identifier => 'project-2') }
+ let!(:ticket_a) { FactoryGirl.create(:work_package, :id => 1, :project_id => project1.id) }
+ let!(:ticket_b) { FactoryGirl.create(:work_package, :id => 2, :project_id => project1.id, :parent_id => ticket_a.id) }
+ let!(:ticket_c) { FactoryGirl.create(:work_package, :id => 3, :project_id => project1.id, :parent_id => ticket_b.id) }
+ let!(:ticket_d) { FactoryGirl.create(:work_package, :id => 4, :project_id => project1.id) }
+ let!(:ticket_e) { FactoryGirl.create(:work_package, :id => 5, :project_id => project2.id, :parent_id => ticket_d.id) }
+ let!(:ticket_f) { FactoryGirl.create(:work_package, :id => 6, :project_id => project1.id, :parent_id => ticket_e.id) }
+
+ become_admin { [project1, project2] }
+
+ it 'rewires ancestors correctly' do
+ get 'index', project_id: project1.id, :format => 'xml'
+
+ expect(assigns(:planning_elements).last.parent_id).to eq(ticket_d.id)
+ end
+ end
+
describe 'changed since' do
let!(:work_package) do
work_package = Timecop.travel(5.hours.ago) do
From 4a13ce44c3a04ab60c0a20ae0ce60f994c3d25c7 Mon Sep 17 00:00:00 2001
From: Ion Biziiac
Date: Tue, 17 Jun 2014 16:30:30 +0300
Subject: [PATCH 28/36] Fix APIv2 does not rewire parents correctly
---
app/controllers/api/v2/planning_elements_controller.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/controllers/api/v2/planning_elements_controller.rb b/app/controllers/api/v2/planning_elements_controller.rb
index b02d68be00..b7b9985624 100644
--- a/app/controllers/api/v2/planning_elements_controller.rb
+++ b/app/controllers/api/v2/planning_elements_controller.rb
@@ -321,7 +321,7 @@ module Api
# re-wire the parent of this pe to the first ancestor found in the filtered set
# re-wiring is only needed, when there is actually a parent, and the parent has been filtered out
if pe.parent_id && !filtered_ids.include?(pe.parent_id)
- ancestors = @planning_elements.select{|candidate| candidate.lft < pe.lft && candidate.rgt > pe.rgt }
+ ancestors = @planning_elements.select{|candidate| candidate.lft < pe.lft && candidate.rgt > pe.rgt && candidate.root_id == pe.root_id }
# the greatest lower boundary is the first ancestor not filtered
pe.parent_id = ancestors.empty? ? nil : ancestors.sort_by{|ancestor| ancestor.lft }.last.id
end
From 88b1c64b26cfebd2eb859922f3b48372142d08af Mon Sep 17 00:00:00 2001
From: Marek Takac
Date: Tue, 17 Jun 2014 16:50:49 +0200
Subject: [PATCH 29/36] Allowed for admin to star / unstar any query
---
lib/api/v3/queries/queries_api.rb | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lib/api/v3/queries/queries_api.rb b/lib/api/v3/queries/queries_api.rb
index b6394d2e83..01bc35d437 100644
--- a/lib/api/v3/queries/queries_api.rb
+++ b/lib/api/v3/queries/queries_api.rb
@@ -19,7 +19,8 @@ module API
helpers do
def allowed_to_manage_stars?
(@query.is_public? && current_user.allowed_to?(:manage_public_queries, @query.project)) ||
- (!@query.is_public? && current_user.allowed_to?(:save_queries, @query.project) && @query.user_id == current_user.id)
+ (!@query.is_public? && (current_user.admin? ||
+ (current_user.allowed_to?(:save_queries, @query.project) && @query.user_id == current_user.id)))
end
end
From 751593b9d64facc9a8165d7fbfd358ec353fe75a Mon Sep 17 00:00:00 2001
From: Till Breuer
Date: Tue, 17 Jun 2014 17:10:50 +0200
Subject: [PATCH 30/36] Update angular context menu
This fixes the column context menu when wp#index is update via state changes (when a query is switched)
---
bower.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/bower.json b/bower.json
index b564140c32..a50b349091 100644
--- a/bower.json
+++ b/bower.json
@@ -22,7 +22,7 @@
"jquery-migrate": "~1.2.1",
"momentjs": "~2.6.0",
"moment-timezone": "~0.0.6",
- "angular-context-menu": "0.1.1"
+ "angular-context-menu": "0.1.2"
},
"devDependencies": {
"mocha": "~1.14.0",
From 637db9c8dbb103c774e4574275fd078d2c533540 Mon Sep 17 00:00:00 2001
From: Till Breuer
Date: Tue, 17 Jun 2014 17:18:02 +0200
Subject: [PATCH 31/36] =?UTF-8?q?Hotfix=20-=20Remove=20=E2=80=98Hide=20col?=
=?UTF-8?q?umn=E2=80=99=20for=20id=20column=20from=20column=20context=20me?=
=?UTF-8?q?nu?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Currently the ‘id’ column is not modeled appropriately on the backend-side but hard-coded in the template
---
public/templates/work_packages/column_context_menu.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/public/templates/work_packages/column_context_menu.html b/public/templates/work_packages/column_context_menu.html
index afba424dea..7bf00d39d6 100644
--- a/public/templates/work_packages/column_context_menu.html
+++ b/public/templates/work_packages/column_context_menu.html
@@ -38,7 +38,7 @@
-
From 6eb061c01e78caeac7a2688fb7aa188c8f402bf4 Mon Sep 17 00:00:00 2001
From: Marek Takac
Date: Tue, 17 Jun 2014 17:23:39 +0200
Subject: [PATCH 32/36] Simplified #star endpoint
---
lib/api/root.rb | 3 +++
lib/api/v3/queries/queries_api.rb | 13 +++++--------
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/lib/api/root.rb b/lib/api/root.rb
index 31926746b1..dd74cc478d 100644
--- a/lib/api/root.rb
+++ b/lib/api/root.rb
@@ -66,6 +66,9 @@ module API
when 'ActiveRecord::RecordNotFound'
not_found = API::Errors::NotFound.new(e.message)
Rack::Response.new(not_found.to_json, not_found.code, not_found.headers).finish
+ when 'ActiveRecord::RecordInvalid'
+ error = API::Errors::Validation.new(e.record)
+ Rack::Response.new(error.to_json, error.code, error.headers).finish
end
end
diff --git a/lib/api/v3/queries/queries_api.rb b/lib/api/v3/queries/queries_api.rb
index 01bc35d437..898c3012b5 100644
--- a/lib/api/v3/queries/queries_api.rb
+++ b/lib/api/v3/queries/queries_api.rb
@@ -27,14 +27,11 @@ module API
patch :star do
authorize(:queries, :star, project: @query.project, allow: allowed_to_manage_stars?)
normalized_query_name = @query.name.parameterize.underscore
- query_menu_item = MenuItems::QueryMenuItem.find_or_initialize_by_name_and_navigatable_id normalized_query_name, @query.id, title: @query.name
-
- if query_menu_item.valid?
- query_menu_item.save!
- @representer.to_json
- else
- raise ValidationError.new(query_menu_item)
- end
+ query_menu_item = MenuItems::QueryMenuItem.find_or_initialize_by_name_and_navigatable_id(
+ normalized_query_name, @query.id, title: @query.name
+ )
+ query_menu_item.save!
+ @representer.to_json
end
patch :unstar do
From bd38e2ebeac61bb7a1c6855ca2b91ad932858895 Mon Sep 17 00:00:00 2001
From: Till Breuer
Date: Tue, 17 Jun 2014 17:55:25 +0200
Subject: [PATCH 33/36] =?UTF-8?q?Remove=20drop=20down=20anchor=20right=20f?=
=?UTF-8?q?rom=20drop=20down=20associated=20to=20=E2=80=98id=E2=80=99=20co?=
=?UTF-8?q?lumn?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../ui_components/has-dropdown-menu-directive.js | 11 +++++++++--
.../templates/work_packages/column_context_menu.html | 4 +++-
2 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/app/assets/javascripts/angular/ui_components/has-dropdown-menu-directive.js b/app/assets/javascripts/angular/ui_components/has-dropdown-menu-directive.js
index c3ad1f56b5..39e3d149d4 100644
--- a/app/assets/javascripts/angular/ui_components/has-dropdown-menu-directive.js
+++ b/app/assets/javascripts/angular/ui_components/has-dropdown-menu-directive.js
@@ -76,8 +76,8 @@ angular.module('openproject.uiComponents')
ctrl.open();
contextMenu.open(locals)
- .then(function(menuElement) {
- menuElement.css(getCssPositionProperties(menuElement, element));
+ .then(function(element) {
+ menuElement = element;
});
}
@@ -87,6 +87,10 @@ angular.module('openproject.uiComponents')
contextMenu.close();
}
+ function positionDropdown() {
+ menuElement.css(getCssPositionProperties(menuElement, element));
+ }
+
element.bind(triggerOnEvent, function(event) {
event.preventDefault();
event.stopPropagation();
@@ -95,6 +99,9 @@ angular.module('openproject.uiComponents')
toggle();
});
+ // set css position parameters after the digest has been completed
+ if (contextMenu.active()) positionDropdown();
+
scope.$root.$broadcast('openproject.markDropdownsAsClosed', element);
});
diff --git a/public/templates/work_packages/column_context_menu.html b/public/templates/work_packages/column_context_menu.html
index 7bf00d39d6..47b27fdece 100644
--- a/public/templates/work_packages/column_context_menu.html
+++ b/public/templates/work_packages/column_context_menu.html
@@ -1,4 +1,6 @@
-