Feature/guarded relation columns (#5784)

* refactor case statement

* guard relation columns by enterprise token

[ci skip]
pull/5786/head
ulferts 7 years ago committed by Oliver Günther
parent 7e896dac18
commit 4d95cc815f
  1. 39
      app/models/queries/work_packages/columns/relation_column.rb
  2. 6
      app/models/queries/work_packages/columns/relation_of_type_column.rb
  3. 8
      app/models/queries/work_packages/columns/relation_to_type_column.rb
  4. 18
      app/services/authorization/enterprise_service.rb
  5. 5
      spec/features/custom_fields/multi_value_custom_field_spec.rb
  6. 27
      spec/features/support/work_package_table.rb
  7. 54
      spec/features/work_packages/table/relations_spec.rb
  8. 3
      spec/features/work_packages/table_sorting_spec.rb
  9. 10
      spec/models/enterprise_token_spec.rb
  10. 31
      spec/models/queries/work_packages/columns/relation_of_type_column_spec.rb
  11. 50
      spec/models/queries/work_packages/columns/relation_to_type_column_spec.rb
  12. 86
      spec/models/query_spec.rb
  13. 235
      spec/requests/api/v3/queries/create_form_api_spec.rb
  14. 233
      spec/requests/api/v3/queries/update_form_api_spec.rb
  15. 17
      spec/services/authorization/enterprise_service_spec.rb
  16. 110
      spec/support/components/work_packages/columns.rb
  17. 1
      spec/views/layouts/base.html.erb_spec.rb

@ -0,0 +1,39 @@
#-- 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 Queries::WorkPackages::Columns::RelationColumn < Queries::WorkPackages::Columns::WorkPackageColumn
attr_accessor :type
def self.granted_by_enterprise_token
EnterpriseToken.allows_to?(:work_package_query_relation_columns)
end
private_class_method :granted_by_enterprise_token
end

@ -28,9 +28,7 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
class Queries::WorkPackages::Columns::RelationOfTypeColumn < Queries::WorkPackages::Columns::WorkPackageColumn
attr_accessor :type
class Queries::WorkPackages::Columns::RelationOfTypeColumn < Queries::WorkPackages::Columns::RelationColumn
def initialize(type)
super
@ -52,6 +50,8 @@ class Queries::WorkPackages::Columns::RelationOfTypeColumn < Queries::WorkPackag
end
def self.instances(_context = nil)
return [] unless granted_by_enterprise_token
Relation::TYPES.map { |_key, type| new(type) }
end
end

@ -28,9 +28,7 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
class Queries::WorkPackages::Columns::RelationToTypeColumn < Queries::WorkPackages::Columns::WorkPackageColumn
attr_accessor :type
class Queries::WorkPackages::Columns::RelationToTypeColumn < Queries::WorkPackages::Columns::RelationColumn
def initialize(type)
super
@ -48,7 +46,9 @@ class Queries::WorkPackages::Columns::RelationToTypeColumn < Queries::WorkPackag
end
def self.instances(context = nil)
if context
if !granted_by_enterprise_token
[]
elsif context
context.types
else
Type.all

@ -1,4 +1,5 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
@ -30,6 +31,11 @@
class Authorization::EnterpriseService
attr_accessor :token
GUARDED_ACTIONS = %i(define_custom_style
multiselect_custom_fields
edit_attribute_groups
work_package_query_relation_columns).freeze
def initialize(token)
self.token = token
end
@ -49,16 +55,8 @@ class Authorization::EnterpriseService
private
def process(action)
case action
when :define_custom_style
true # Every non-expired token
when :multiselect_custom_fields
true
when :edit_attribute_groups
true
else
false
end
# Every non-expired token
GUARDED_ACTIONS.include?(action)
end
def result(bool)

@ -23,6 +23,7 @@ describe "multi select custom values", js: true do
let(:wp_page) { Pages::FullWorkPackage.new work_package }
let(:wp_table) { Pages::WorkPackagesTable.new project }
let(:hierarchy) { ::Components::WorkPackages::Hierarchies.new }
let(:columns) { ::Components::WorkPackages::Columns.new }
let(:user) { FactoryGirl.create :admin }
context "with existing custom values" do
@ -93,7 +94,6 @@ describe "multi select custom values", js: true do
field.field_type = 'select'
field
end
include_context 'work package table helpers'
before do
work_package
@ -104,7 +104,8 @@ describe "multi select custom values", js: true do
wp_table.visit!
wp_table.expect_work_package_listed(work_package)
wp_table.expect_work_package_listed(work_package2)
add_wp_table_column(custom_field.name)
columns.add custom_field.name
end
it 'should be usable in the table context' do

@ -27,33 +27,6 @@
#++
shared_context 'work package table helpers' do
def remove_wp_table_column(column_name)
click_button('Settings')
click_link('Columns ...')
# This is faster than has_selector but does not wait for anything.
# So if problems occur, switch to has_selector?
if find('.select2-choices').text.include?(column_name)
find('.select2-search-choice', text: column_name)
.click_link('select2-search-choice-close')
end
click_button('Apply')
end
def add_wp_table_column(column_name)
click_button('Settings')
click_link('Columns ...')
input = find '.select2-search-field input'
input.set column_name
result = find '.select2-result-label'
result.click
click_button('Apply')
end
def sort_wp_table_by(column_name, order: :desc)
click_button('Settings')
click_link('Sort by ...')

@ -9,6 +9,7 @@ describe 'Work Package table relations', js: true do
let(:wp_table) { Pages::WorkPackagesTable.new(project) }
let(:relations) { ::Components::WorkPackages::Relations.new(relations) }
let(:columns) { ::Components::WorkPackages::Columns.new }
let(:wp_timeline) { Pages::WorkPackagesTimeline.new(project) }
let!(:wp_from) { FactoryGirl.create(:work_package, project: project, type: type2) }
@ -27,33 +28,42 @@ describe 'Work Package table relations', js: true do
to: wp_to2,
relation_type: Relation::TYPE_FOLLOWS)
end
let!(:query) do
query = FactoryGirl.build(:query, user: user, project: project)
query.column_names = ['subject']
query.filters.clear
before do
login_as(user)
query.save!
query
end
describe 'relations can be displayed and expanded' do
include_context 'work package table helpers'
let(:type_column_id) { "relationsToType#{type.id}" }
let(:type_column_follows) { 'relationsOfTypeFollows' }
let(:relation_columns_allowed) { true }
let!(:query) do
query = FactoryGirl.build(:query, user: user, project: project)
query.column_names = ['subject']
query.filters.clear
before do
# There does not seem to appear a way to generate a valid token
# for testing purposes
allow(EnterpriseToken)
.to receive(:allows_to?)
.and_return(false)
query.save!
query
end
allow(EnterpriseToken)
.to receive(:allows_to?)
.with(:work_package_query_relation_columns)
.and_return(relation_columns_allowed)
let(:type_column_id) { "relationsToType#{type.id}" }
let(:type_column_follows) { 'relationsOfTypeFollows' }
login_as(user)
end
it do
describe 'with relation columns allowed by the enterprise token' do
it 'displays expandable relation columns' do
# Now visiting the query for category
wp_table.visit_query(query)
wp_table.expect_work_package_listed(wp_from, wp_to, wp_to2)
add_wp_table_column "Relations to #{type.name}"
add_wp_table_column "follows relations"
columns.add("Relations to #{type.name}")
columns.add("follows relations")
wp_from_row = wp_table.row(wp_from)
wp_from_to = wp_table.row(wp_to)
@ -100,4 +110,16 @@ describe 'Work Package table relations', js: true do
wp_timeline.expect_row_count(4)
end
end
describe 'with relation columns disallowed by the enterprise token' do
let(:relation_columns_allowed) { false }
it 'has no relation columns available for selection' do
# Now visiting the query for category
wp_table.visit_query(query)
columns.expect_column_not_available 'follows relations'
columns.expect_column_not_available "Relations to #{type.name}"
end
end
end

@ -48,6 +48,7 @@ describe 'Select work package row', type: :feature do
FactoryGirl.create(:version, project: project,
name: 'zzz_version')
end
let(:columns) { ::Components::WorkPackages::Columns.new }
before do
login_as(user)
@ -68,7 +69,7 @@ describe 'Select work package row', type: :feature do
end
it 'sorts by version although version is not selected as a column' do
remove_wp_table_column('Version')
columns.remove 'Version'
sort_wp_table_by('Version')

@ -57,9 +57,13 @@ RSpec.describe EnterpriseToken, type: :model do
end
it 'forwards to EnterpriseTokenService for checks' do
expect(service_double).to receive(:call).with(:forbidden_action)
expect(service_double)
.to receive(:call)
.with(:forbidden_action)
.and_return double('ServiceResult', result: false)
expect(service_double).to receive(:call).with(:allowed_action)
expect(service_double)
.to receive(:call)
.with(:allowed_action)
.and_return double('ServiceResult', result: true)
expect(EnterpriseToken.allows_to?(:forbidden_action)).to eq false
@ -102,11 +106,9 @@ RSpec.describe EnterpriseToken, type: :model do
end
describe "Configuration file has `ee_manager_visible` set to false" do
it 'does not show banners promoting EE' do
expect(OpenProject::Configuration).to receive(:ee_manager_visible?).and_return(false)
expect(EnterpriseToken.show_banners?).to be_falsey
end
end
end

@ -33,6 +33,7 @@ describe Queries::WorkPackages::Columns::RelationOfTypeColumn, type: :model do
let(:project) { FactoryGirl.build_stubbed(:project) }
let(:type) { FactoryGirl.build_stubbed(:type) }
let(:instance) { described_class.new(type) }
let(:enterprise_token_allows) { true }
it_behaves_like 'query column'
@ -41,17 +42,33 @@ describe Queries::WorkPackages::Columns::RelationOfTypeColumn, type: :model do
stub_const('Relation::TYPES',
relation1: { name: :label_relates_to, sym_name: :label_relates_to, order: 1, sym: :relation1 },
relation2: { name: :label_duplicates, sym_name: :label_duplicated_by, order: 2, sym: :relation2 })
allow(EnterpriseToken)
.to receive(:allows_to?)
.with(:work_package_query_relation_columns)
.and_return(enterprise_token_allows)
end
it 'contains the type columns' do
expect(described_class.instances.length)
.to eq 2
context 'with a valid enterprise token' do
it 'contains the type columns' do
expect(described_class.instances.length)
.to eq 2
expect(described_class.instances[0].sym)
.to eq :relation1
expect(described_class.instances[1].sym)
.to eq :relation2
end
end
expect(described_class.instances[0].sym)
.to eq :relation1
context 'without a valid enterprise token' do
let(:enterprise_token_allows) { false }
expect(described_class.instances[1].sym)
.to eq :relation2
it 'is empty' do
expect(described_class.instances)
.to be_empty
end
end
end
end

@ -33,10 +33,18 @@ describe Queries::WorkPackages::Columns::RelationToTypeColumn, type: :model do
let(:project) { FactoryGirl.build_stubbed(:project) }
let(:type) { FactoryGirl.build_stubbed(:type) }
let(:instance) { described_class.new(type) }
let(:enterprise_token_allows) { true }
it_behaves_like 'query column'
describe 'instances' do
before do
allow(EnterpriseToken)
.to receive(:allows_to?)
.with(:work_package_query_relation_columns)
.and_return(enterprise_token_allows)
end
context 'within project' do
before do
allow(project)
@ -44,12 +52,23 @@ describe Queries::WorkPackages::Columns::RelationToTypeColumn, type: :model do
.and_return([type])
end
it 'contains the type columns' do
expect(described_class.instances(project).length)
.to eq 1
context 'with a valid enterprise token' do
it 'contains the type columns' do
expect(described_class.instances(project).length)
.to eq 1
expect(described_class.instances(project)[0].type)
.to eq type
end
end
context 'without a valid enterprise token' do
let(:enterprise_token_allows) { false }
expect(described_class.instances(project)[0].type)
.to eq type
it 'is empty' do
expect(described_class.instances)
.to be_empty
end
end
end
@ -60,12 +79,23 @@ describe Queries::WorkPackages::Columns::RelationToTypeColumn, type: :model do
.and_return([type])
end
it 'contains the type columns' do
expect(described_class.instances.length)
.to eq 1
context 'with a valid enterprise token' do
it 'contains the type columns' do
expect(described_class.instances.length)
.to eq 1
expect(described_class.instances[0].type)
.to eq type
end
end
context 'without a valid enterprise token' do
let(:enterprise_token_allows) { false }
expect(described_class.instances[0].type)
.to eq type
it 'is empty' do
expect(described_class.instances)
.to be_empty
end
end
end
end

@ -31,6 +31,14 @@ require 'spec_helper'
describe Query, type: :model do
let(:query) { FactoryGirl.build(:query) }
let(:project) { FactoryGirl.build_stubbed(:project) }
let(:relation_columns_allowed) { true }
before do
allow(EnterpriseToken)
.to receive(:allows_to?)
.with(:work_package_query_relation_columns)
.and_return(relation_columns_allowed)
end
describe '.new_default' do
it 'set the default sortation' do
@ -176,6 +184,14 @@ describe Query, type: :model do
it 'does not include the relation columns for types not in project' do
expect(query.available_columns.map(&:name)).not_to include :"relations_to_type_#{type_not_in_project.id}"
end
context 'with the enterprise token disallowing relation columns' do
let(:relation_columns_allowed) { false }
it 'excludes the relation columns' do
expect(query.available_columns.map(&:name)).not_to include :"relations_to_type_#{type_in_project.id}"
end
end
end
context 'global' do
@ -187,41 +203,83 @@ describe Query, type: :model do
expect(query.available_columns.map(&:name)).to include(:"relations_to_type_#{type_in_project.id}",
:"relations_to_type_#{type_not_in_project.id}")
end
context 'with the enterprise token disallowing relation columns' do
let(:relation_columns_allowed) { false }
it 'excludes the relation columns' do
expect(query.available_columns.map(&:name)).not_to include(:"relations_to_type_#{type_in_project.id}",
:"relations_to_type_#{type_not_in_project.id}")
end
end
end
end
context 'relation_of_type columns' do
it 'includes the relation columns for every relation type' do
before do
stub_const('Relation::TYPES',
relation1: { name: :label_relates_to, sym_name: :label_relates_to, order: 1, sym: :relation1 },
relation2: { name: :label_duplicates, sym_name: :label_duplicated_by, order: 2, sym: :relation2 })
end
it 'includes the relation columns for every relation type' do
expect(query.available_columns.map(&:name)).to include(:relations_of_type_relation1,
:relations_of_type_relation2)
end
context 'with the enterprise token disallowing relation columns' do
let(:relation_columns_allowed) { false }
it 'excludes the relation columns' do
expect(query.available_columns.map(&:name)).not_to include(:relations_of_type_relation1,
:relations_of_type_relation2)
end
end
end
end
describe '.available_columns' do
it 'has all static columns, cf columns and relation columns' do
custom_field = FactoryGirl.create(:list_wp_custom_field)
type = FactoryGirl.create(:type)
let(:custom_field) { FactoryGirl.create(:list_wp_custom_field) }
let(:type) { FactoryGirl.create(:type) }
before do
stub_const('Relation::TYPES',
relation1: { name: :label_relates_to, sym_name: :label_relates_to, order: 1, sym: :relation1 },
relation2: { name: :label_duplicates, sym_name: :label_duplicated_by, order: 2, sym: :relation2 })
end
context 'with the enterprise token allowing relation columns' do
it 'has all static columns, cf columns and relation columns' do
expected_columns = %i(id project assigned_to author
category created_at due_date estimated_hours
parent done_ratio priority responsible
spent_hours start_date status subject type
updated_at fixed_version) +
[:"cf_#{custom_field.id}"] +
[:"relations_to_type_#{type.id}"] +
%i(relations_of_type_relation1 relations_of_type_relation2)
expected_columns = %i(id project assigned_to author
category created_at due_date estimated_hours
parent done_ratio priority responsible
spent_hours start_date status subject type
updated_at fixed_version) +
[:"cf_#{custom_field.id}"] +
[:"relations_to_type_#{type.id}"] +
%i(relations_of_type_relation1 relations_of_type_relation2)
expect(Query.available_columns.map(&:name)).to include *expected_columns
end
end
context 'with the enterprise token disallowing relation columns' do
let(:relation_columns_allowed) { false }
expect(Query.available_columns.map(&:name)).to include *expected_columns
it 'has all static columns, cf columns but no relation columns' do
expected_columns = %i(id project assigned_to author
category created_at due_date estimated_hours
parent done_ratio priority responsible
spent_hours start_date status subject type
updated_at fixed_version) +
[:"cf_#{custom_field.id}"]
unexpected_columns = [:"relations_to_type_#{type.id}"] +
%i(relations_of_type_relation1 relations_of_type_relation2)
expect(Query.available_columns.map(&:name)).to include *expected_columns
expect(Query.available_columns.map(&:name)).not_to include *unexpected_columns
end
end
end

@ -41,6 +41,55 @@ describe "POST /api/v3/queries/form", type: :request do
let(:override_params) { {} }
let(:form) { JSON.parse response.body }
let(:static_columns_json) do
%w(id project assignee author
category createdAt dueDate estimatedTime
parent percentageDone priority responsible
spentTime startDate status subject type
updatedAt version).map do |id|
{
'_type': 'QueryColumn::Property',
'id': id
}
end
end
let(:custom_field_columns_json) do
[
{
'_type': 'QueryColumn::Property',
'id': "customField#{custom_field.id}"
}
]
end
let(:relation_to_type_columns_json) do
project.types.map do |type|
{
'_type': 'QueryColumn::RelationToType',
'id': "relationsToType#{type.id}"
}
end
end
let(:relation_of_type_columns_json) do
Relation::TYPES.map do |_, value|
{
'_type': 'QueryColumn::RelationOfType',
'id': "relationsOfType#{value[:sym].camelcase}"
}
end
end
let(:non_project_type_relation_column_json) do
[
{
'_type': 'QueryColumn::RelationToType',
'id': "relationsToType#{non_project_type.id}"
}
]
end
let(:additional_setup) {}
before do
@ -106,6 +155,8 @@ describe "POST /api/v3/queries/form", type: :request do
end
describe 'columns' do
let(:relation_columns_allowed) { true }
let(:custom_field) do
cf = FactoryGirl.create(:list_wp_custom_field)
project.work_package_custom_fields << cf
@ -122,47 +173,63 @@ describe "POST /api/v3/queries/form", type: :request do
custom_field
non_project_type
end
it 'has the static, custom field and relation columns' do
expected_columns = (%w(id project assignee author
category createdAt dueDate estimatedTime
parent percentageDone priority responsible
spentTime startDate status subject type
updatedAt version) + ["customField#{custom_field.id}"]).map do |id|
{
'_type': 'QueryColumn::Property',
'id': id
}
end
# There does not seem to appear a way to generate a valid token
# for testing purposes
allow(EnterpriseToken)
.to receive(:allows_to?)
.with(:work_package_query_relation_columns)
.and_return(relation_columns_allowed)
end
expected_columns += Type.all.map do |type|
{
'_type': 'QueryColumn::RelationToType',
'id': "relationsToType#{type.id}"
}
context 'with relation columns allowed by the enterprise token' do
it 'has the static, custom field and relation columns' do
expected_columns = static_columns_json +
custom_field_columns_json +
relation_to_type_columns_json +
relation_of_type_columns_json +
non_project_type_relation_column_json
actual_columns = form.dig('_embedded',
'schema',
'columns',
'_embedded',
'allowedValues')
.map do |column|
{
'_type': column['_type'],
'id': column['id']
}
end
expect(actual_columns).to include *expected_columns
end
end
expected_columns += Relation::TYPES.map do |_, value|
{
'_type': 'QueryColumn::RelationOfType',
'id': "relationsOfType#{value[:sym].camelcase}"
}
context 'with relation columns disallowed by the enterprise token' do
let(:relation_columns_allowed) { false }
it 'has the static and custom field' do
expected_columns = static_columns_json +
custom_field_columns_json
actual_columns = form.dig('_embedded',
'schema',
'columns',
'_embedded',
'allowedValues')
.map do |column|
{
'_type': column['_type'],
'id': column['id']
}
end
expect(actual_columns).to include *expected_columns
expect(actual_columns).not_to include(relation_to_type_columns_json)
expect(actual_columns).not_to include(relation_of_type_columns_json)
expect(actual_columns).not_to include(non_project_type_relation_column_json)
end
actual_columns = form.dig('_embedded',
'schema',
'columns',
'_embedded',
'allowedValues')
.map do |column|
{
'_type': column['_type'],
'id': column['id']
}
end
expect(actual_columns).to include *expected_columns
end
end
end
@ -180,6 +247,8 @@ describe "POST /api/v3/queries/form", type: :request do
end
describe 'columns' do
let(:relation_columns_allowed) { true }
let(:custom_field) do
cf = FactoryGirl.create(:list_wp_custom_field)
project.work_package_custom_fields << cf
@ -196,53 +265,63 @@ describe "POST /api/v3/queries/form", type: :request do
custom_field
non_project_type
end
it 'has the static, custom field and relation columns' do
expected_columns = (%w(id project assignee author
category createdAt dueDate estimatedTime
parent percentageDone priority responsible
spentTime startDate status subject type
updatedAt version) + ["customField#{custom_field.id}"]).map do |id|
{
'_type': 'QueryColumn::Property',
'id': id
}
end
# There does not seem to appear a way to generate a valid token
# for testing purposes
allow(EnterpriseToken)
.to receive(:allows_to?)
.with(:work_package_query_relation_columns)
.and_return(relation_columns_allowed)
end
expected_columns += project.types.map do |type|
{
'_type': 'QueryColumn::RelationToType',
'id': "relationsToType#{type.id}"
}
context 'with relation columns allowed by the enterprise token' do
it 'has the static, custom field and relation columns' do
expected_columns = static_columns_json +
custom_field_columns_json +
relation_to_type_columns_json +
relation_of_type_columns_json
actual_columns = form.dig('_embedded',
'schema',
'columns',
'_embedded',
'allowedValues')
.map do |column|
{
'_type': column['_type'],
'id': column['id']
}
end
expect(actual_columns).to include *expected_columns
expect(actual_columns).not_to include(non_project_type_relation_column_json)
end
end
expected_columns += Relation::TYPES.map do |_, value|
{
'_type': 'QueryColumn::RelationOfType',
'id': "relationsOfType#{value[:sym].camelcase}"
}
context 'with relation columns disallowed by the enterprise token' do
let(:relation_columns_allowed) { false }
it 'has the static and custom field' do
expected_columns = static_columns_json +
custom_field_columns_json
actual_columns = form.dig('_embedded',
'schema',
'columns',
'_embedded',
'allowedValues')
.map do |column|
{
'_type': column['_type'],
'id': column['id']
}
end
expect(actual_columns).to include *expected_columns
expect(actual_columns).not_to include(relation_to_type_columns_json)
expect(actual_columns).not_to include(relation_of_type_columns_json)
expect(actual_columns).not_to include(non_project_type_relation_column_json)
end
actual_columns = form.dig('_embedded',
'schema',
'columns',
'_embedded',
'allowedValues')
.map do |column|
{
'_type': column['_type'],
'id': column['id']
}
end
non_project_type_hash = {
'_type': 'QueryColumn::Relation',
'id': "relationsToType#{non_project_type.id}"
}
expect(actual_columns).to include *expected_columns
expect(actual_columns).not_to include(non_project_type_hash)
end
end
end

@ -112,6 +112,21 @@ describe "POST /api/v3/queries/form", type: :request do
end
describe 'columns' do
let(:relation_columns_allowed) { true }
let(:additional_setup) do
custom_field
non_project_type
# There does not seem to appear a way to generate a valid token
# for testing purposes
allow(EnterpriseToken)
.to receive(:allows_to?)
.with(:work_package_query_relation_columns)
.and_return(relation_columns_allowed)
end
let(:custom_field) do
cf = FactoryGirl.create(:list_wp_custom_field)
project.work_package_custom_fields << cf
@ -124,58 +139,102 @@ describe "POST /api/v3/queries/form", type: :request do
FactoryGirl.create(:type)
end
let(:additional_setup) do
custom_field
non_project_type
let(:static_columns_json) do
%w(id project assignee author
category createdAt dueDate estimatedTime
parent percentageDone priority responsible
spentTime startDate status subject type
updatedAt version).map do |id|
{
'_type': 'QueryColumn::Property',
'id': id
}
end
end
context 'within a project' do
it 'has the static, custom field and relation columns' do
expected_columns = (%w(id project assignee author
category createdAt dueDate estimatedTime
parent percentageDone priority responsible
spentTime startDate status subject type
updatedAt version) + ["customField#{custom_field.id}"]).map do |id|
{
'_type': 'QueryColumn::Property',
'id': id
}
end
let(:custom_field_columns_json) do
[
{
'_type': 'QueryColumn::Property',
'id': "customField#{custom_field.id}"
}
]
end
expected_columns += project.types.map do |type|
{
'_type': 'QueryColumn::RelationToType',
'id': "relationsToType#{type.id}"
}
end
let(:relation_to_type_columns_json) do
project.types.map do |type|
{
'_type': 'QueryColumn::RelationToType',
'id': "relationsToType#{type.id}"
}
end
end
expected_columns += Relation::TYPES.map do |_, value|
{
'_type': 'QueryColumn::RelationOfType',
'id': "relationsOfType#{value[:sym].camelcase}"
}
end
let(:relation_of_type_columns_json) do
Relation::TYPES.map do |_, value|
{
'_type': 'QueryColumn::RelationOfType',
'id': "relationsOfType#{value[:sym].camelcase}"
}
end
end
actual_columns = form.dig('_embedded',
'schema',
'columns',
'_embedded',
'allowedValues')
.map do |column|
{
'_type': column['_type'],
'id': column['id']
}
end
non_project_type_hash = {
'_type': 'QueryColumn::Relation',
let(:non_project_type_relation_column_json) do
[
{
'_type': 'QueryColumn::RelationToType',
'id': "relationsToType#{non_project_type.id}"
}
]
end
expect(actual_columns).to include(*expected_columns)
expect(actual_columns).not_to include(non_project_type_hash)
context 'within a project' do
context 'with relation columns allowed by the enterprise token' do
it 'has the static, custom field and relation columns' do
expected_columns = static_columns_json +
custom_field_columns_json +
relation_to_type_columns_json +
relation_of_type_columns_json
actual_columns = form.dig('_embedded',
'schema',
'columns',
'_embedded',
'allowedValues')
.map do |column|
{
'_type': column['_type'],
'id': column['id']
}
end
expect(actual_columns).to include(*expected_columns)
expect(actual_columns).not_to include(non_project_type_relation_column_json)
end
end
context 'with relation columns disallowed by the enterprise token' do
it 'has the static and custom field' do
expected_columns = static_columns_json +
custom_field_columns_json
actual_columns = form.dig('_embedded',
'schema',
'columns',
'_embedded',
'allowedValues')
.map do |column|
{
'_type': column['_type'],
'id': column['id']
}
end
expect(actual_columns).to include(*expected_columns)
expect(actual_columns).not_to include(non_project_type_relation_column_json)
expect(actual_columns).not_to include(relation_to_type_columns_json)
expect(actual_columns).not_to include(relation_of_type_columns_json)
end
end
end
@ -186,47 +245,61 @@ describe "POST /api/v3/queries/form", type: :request do
non_project_type
query.update_attribute(:project, nil)
# There does not seem to appear a way to generate a valid token
# for testing purposes
allow(EnterpriseToken)
.to receive(:allows_to?)
.with(:work_package_query_relation_columns)
.and_return(relation_columns_allowed)
end
it 'has the static, custom field and relation columns' do
expected_columns = (%w(id project assignee author
category createdAt dueDate estimatedTime
parent percentageDone priority responsible
spentTime startDate status subject type
updatedAt version) + ["customField#{custom_field.id}"]).map do |id|
{
'_type': 'QueryColumn::Property',
'id': id
}
end
expected_columns += Type.all.map do |type|
{
'_type': 'QueryColumn::RelationToType',
'id': "relationsToType#{type.id}"
}
context 'with relation columns allowed by the enterprise token' do
it 'has the static, custom field and relation columns' do
expected_columns = static_columns_json +
custom_field_columns_json +
relation_to_type_columns_json +
non_project_type_relation_column_json +
relation_of_type_columns_json
actual_columns = form.dig('_embedded',
'schema',
'columns',
'_embedded',
'allowedValues')
.map do |column|
{
'_type': column['_type'],
'id': column['id']
}
end
expect(actual_columns).to include(*expected_columns)
end
end
expected_columns += Relation::TYPES.map do |_, value|
{
'_type': 'QueryColumn::RelationOfType',
'id': "relationsOfType#{value[:sym].camelcase}"
}
context 'with relation columns disallowed by the enterprise token' do
it 'has the static, custom field and relation columns' do
expected_columns = static_columns_json +
custom_field_columns_json
actual_columns = form.dig('_embedded',
'schema',
'columns',
'_embedded',
'allowedValues')
.map do |column|
{
'_type': column['_type'],
'id': column['id']
}
end
expect(actual_columns).to include(*expected_columns)
expect(actual_columns).not_to include(non_project_type_relation_column_json)
expect(actual_columns).not_to include(relation_to_type_columns_json)
expect(actual_columns).not_to include(relation_of_type_columns_json)
end
actual_columns = form.dig('_embedded',
'schema',
'columns',
'_embedded',
'allowedValues')
.map do |column|
{
'_type': column['_type'],
'id': column['id']
}
end
expect(actual_columns).to include(*expected_columns)
end
end
end

@ -73,13 +73,18 @@ describe Authorization::EnterpriseService do
end
end
context 'valid action requires active token' do
let(:action) { :define_custom_style }
%i(define_custom_style
multiselect_custom_fields
edit_attribute_groups
work_package_query_relation_columns).each do |guarded_action|
context "guarded action #{guarded_action}" do
let(:action) { guarded_action }
it 'returns a true result' do
expect(result).to be_kind_of ServiceResult
expect(result.result).to be_truthy
expect(result.success?).to be_truthy
it 'returns a true result' do
expect(result).to be_kind_of ServiceResult
expect(result.result).to be_truthy
expect(result.success?).to be_truthy
end
end
end
end

@ -0,0 +1,110 @@
#-- 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 Components
module WorkPackages
class Columns
include Capybara::DSL
include RSpec::Matchers
def expect_column_available(name)
modal_open? or open_modal
within_modal do
page.find('#selected_columns').click
expect(page)
.to have_selector('li[role=option]', text: name)
end
end
def expect_column_not_available(name)
modal_open? or open_modal
within_modal do
page.find('#selected_columns').click
expect(page)
.to have_no_selector('li[role=option]', text: name)
end
end
def add(name)
modal_open? or open_modal
within_modal do
input = find '.select2-search-field input'
input.set name
result = find '.select2-result-label'
result.click
end
apply
end
def remove(name)
modal_open? or open_modal
within_modal do
# This is faster than has_selector but does not wait for anything.
# So if problems occur, switch to has_selector?
if find('.select2-choices').text.include?(name)
find('.select2-search-choice', text: name)
.click_link('select2-search-choice-close')
end
end
apply
end
def apply
@opened = false
click_button('Apply')
end
def open_modal
@opened = true
SettingsMenu.new.open_and_choose('Columns ...')
end
private
def within_modal
page.within('.columns-modal') do
yield
end
end
def modal_open?
!!@opened
end
end
end
end

@ -220,6 +220,7 @@ describe 'layouts/base', type: :view do
context "EE does not allow custom styles" do
before do
allow(EnterpriseToken).to receive(:current).and_return(a_token)
allow(a_token).to receive(:expired?).and_return(false)
allow(a_token).to receive(:allows_to?).with(:define_custom_style).and_return(false)
render

Loading…
Cancel
Save