add parent filter to wp query (#6235)

[ci skip]
pull/6109/head
ulferts 7 years ago committed by Oliver Günther
parent a51e4408c1
commit 3881b47e26
  1. 1
      app/models/queries/work_packages.rb
  2. 75
      app/models/queries/work_packages/filter/filter_for_wp_mixing.rb
  3. 46
      app/models/queries/work_packages/filter/id_filter.rb
  4. 47
      app/models/queries/work_packages/filter/parent_filter.rb
  5. 1
      app/models/queries/work_packages/filter/type_filter.rb
  6. 6
      app/models/work_package.rb
  7. 4
      frontend/app/components/wp-fast-table/wp-table-filters.ts
  8. 59
      lib/api/v3/queries/schemas/by_work_package_filter_dependency_representer.rb
  9. 22
      lib/api/v3/queries/schemas/id_filter_dependency_representer.rb
  10. 39
      lib/api/v3/queries/schemas/parent_filter_dependency_representer.rb
  11. 133
      spec/lib/api/v3/queries/schemas/parent_filter_dependency_representer_spec.rb
  12. 16
      spec/models/queries/work_packages/filter/id_filter_spec.rb
  13. 259
      spec/models/queries/work_packages/filter/parent_filter_spec.rb

@ -57,6 +57,7 @@ module Queries::WorkPackages
register.filter Query, filters_module::UpdatedAtFilter
register.filter Query, filters_module::VersionFilter
register.filter Query, filters_module::WatcherFilter
register.filter Query, filters_module::ParentFilter
columns_module = Queries::WorkPackages::Columns

@ -0,0 +1,75 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 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 docs/COPYRIGHT.rdoc for more details.
#++
module Queries::WorkPackages::Filter::FilterForWpMixin
def type
:list
end
def allowed_values
raise NotImplementedError, 'There would be too many candidates'
end
def value_objects
scope.find(values)
end
def allowed_objects
raise NotImplementedError, 'There would be too many candidates'
end
def available?
scope.exists?
end
def ar_object_filter?
true
end
def allowed_values_subset
scope.where(id: values).pluck(:id).map(&:to_s)
end
private
def scope
if context.project
WorkPackage
.visible
.for_projects(context.project.self_and_descendants)
else
WorkPackage.visible
end
end
def type_strategy
@type_strategy ||= Queries::Filters::Strategies::HugeList.new(self)
end
end

@ -28,48 +28,10 @@
# See docs/COPYRIGHT.rdoc for more details.
#++
class Queries::WorkPackages::Filter::IdFilter < Queries::WorkPackages::Filter::WorkPackageFilter
def type
:list
end
require_relative './filter_for_wp_mixing'
def allowed_values
raise NotImplementedError, 'There would be too many candidates'
end
class Queries::WorkPackages::Filter::IdFilter <
Queries::WorkPackages::Filter::WorkPackageFilter
def value_objects
raise NotImplementedError, 'There would be too many candidates'
end
def allowed_objects
raise NotImplementedError, 'There would be too many candidates'
end
def available?
scope.exists?
end
def ar_object_filter?
true
end
def allowed_values_subset
scope.where(id: values).pluck(:id).map(&:to_s)
end
private
def scope
if context.project
WorkPackage
.visible
.for_projects(context.project.self_and_descendants)
else
WorkPackage.visible
end
end
def type_strategy
@type_strategy ||= Queries::Filters::Strategies::HugeList.new(self)
end
include ::Queries::WorkPackages::Filter::FilterForWpMixin
end

@ -0,0 +1,47 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 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 docs/COPYRIGHT.rdoc for more details.
#++
require_relative './filter_for_wp_mixing'
class Queries::WorkPackages::Filter::ParentFilter <
Queries::WorkPackages::Filter::WorkPackageFilter
include ::Queries::WorkPackages::Filter::FilterForWpMixin
def includes
:parent_relation
end
def where
operator_strategy.sql_for_field(values,
Relation.table_name,
'from_id')
end
end

@ -1,4 +1,5 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 the OpenProject Foundation (OPF)

@ -299,6 +299,12 @@ class WorkPackage < ActiveRecord::Base
project && type ? (project.all_work_package_custom_fields & type.custom_fields) : []
end
# aliasing subject to name
# using :alias is not possible as AR will add the subject method later
def name
subject
end
def status_id=(sid)
self.status = nil
write_attribute(:status_id, sid)

@ -78,8 +78,8 @@ export class WorkPackageTableFilters extends WorkPackageTableBaseState<QueryFilt
let availableFilters = this.availableSchemas
.map(schema => (schema.filter.allowedValues as QueryFilterResource[])[0]);
// We do not use the id filter as of now as we do not have adequate
// We do not use the filters id and parent as of now as we do not have adequate
// means to select the values.
return _.filter(availableFilters, filter => filter.id !== 'id');
return _.filter(availableFilters, filter => filter.id !== 'id' && filter.id !== 'parent');
}
}

@ -0,0 +1,59 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 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 docs/COPYRIGHT.rdoc for more details.
#++
module API
module V3
module Queries
module Schemas
class ByWorkPackageFilterDependencyRepresenter <
FilterDependencyRepresenter
def json_cache_key
super + (filter.project.present? ? [filter.project.id] : [])
end
def href_callback
if filter.project
api_v3_paths.work_packages_by_project(filter.project.id)
else
api_v3_paths.work_packages
end
end
private
def type
'[]WorkPackage'
end
end
end
end
end
end

@ -32,27 +32,7 @@ module API
module V3
module Queries
module Schemas
class IdFilterDependencyRepresenter <
FilterDependencyRepresenter
def json_cache_key
super + (filter.project.present? ? [filter.project.id] : [])
end
def href_callback
if filter.project
api_v3_paths.work_packages_by_project(filter.project.id)
else
api_v3_paths.work_packages
end
end
private
def type
'[]WorkPackage'
end
end
class IdFilterDependencyRepresenter < ByWorkPackageFilterDependencyRepresenter; end
end
end
end

@ -0,0 +1,39 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 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 docs/COPYRIGHT.rdoc for more details.
#++
module API
module V3
module Queries
module Schemas
class ParentFilterDependencyRepresenter < ByWorkPackageFilterDependencyRepresenter; end
end
end
end
end

@ -0,0 +1,133 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 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 docs/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
describe ::API::V3::Queries::Schemas::ParentFilterDependencyRepresenter, clear_cache: true do
include ::API::V3::Utilities::PathHelper
let(:project) { FactoryGirl.build_stubbed(:project) }
let(:query) { FactoryGirl.build_stubbed(:query, project: project) }
let(:filter) { Queries::WorkPackages::Filter::ParentFilter.create!(context: query) }
let(:form_embedded) { false }
let(:instance) do
described_class.new(filter,
operator,
form_embedded: form_embedded)
end
subject(:generated) { instance.to_json }
context 'generation' do
context 'properties' do
describe 'values' do
context 'within project' do
let(:path) { 'values' }
let(:type) { '[]WorkPackage' }
let(:href) { api_v3_paths.work_packages_by_project(project.id) }
context "for operator 'Queries::Operators::Equals'" do
let(:operator) { Queries::Operators::Equals }
it_behaves_like 'filter dependency with allowed link'
end
context "for operator 'Queries::Operators::NotEquals'" do
let(:operator) { Queries::Operators::NotEquals }
it_behaves_like 'filter dependency with allowed link'
end
end
context 'outside of a project' do
let(:project) { nil }
let(:path) { 'values' }
let(:type) { '[]WorkPackage' }
let(:href) { api_v3_paths.work_packages }
context "for operator 'Queries::Operators::Equals'" do
let(:operator) { Queries::Operators::Equals }
it_behaves_like 'filter dependency with allowed link'
end
context "for operator 'Queries::Operators::NotEquals'" do
let(:operator) { Queries::Operators::NotEquals }
it_behaves_like 'filter dependency with allowed link'
end
end
end
end
describe 'caching' do
let(:operator) { Queries::Operators::Equals }
let(:other_project) { FactoryGirl.build_stubbed(:project) }
before do
# fill the cache
instance.to_json
end
it 'is cached' do
expect(instance)
.not_to receive(:to_hash)
instance.to_json
end
it 'busts the cache on a different operator' do
instance.send(:operator=, Queries::Operators::NotEquals)
expect(instance)
.to receive(:to_hash)
instance.to_json
end
it 'busts the cache on a different project' do
query.project = other_project
expect(instance)
.to receive(:to_hash)
instance.to_json
end
it 'busts the cache on changes to the locale' do
expect(instance)
.to receive(:to_hash)
I18n.with_locale(:de) do
instance.to_json
end
end
end
end
end

@ -57,7 +57,7 @@ describe Queries::WorkPackages::Filter::IdFilter, type: :model do
expect(instance).to be_available
end
it 'is fals if no work package exists/ is visible' do
it 'is false if no work package exists/ is visible' do
allow(WorkPackage)
.to receive_message_chain(:visible, :for_projects, :exists?)
.with(no_args)
@ -105,8 +105,18 @@ describe Queries::WorkPackages::Filter::IdFilter, type: :model do
end
describe '#value_object' do
it 'raises an error' do
expect { instance.value_objects }.to raise_error NotImplementedError
let(:visible_wp) { FactoryGirl.build_stubbed(:work_package) }
it 'returns the work package for the values' do
allow(WorkPackage)
.to receive_message_chain(:visible, :for_projects, :find)
.with(no_args)
.with(project)
.with(instance.values)
.and_return([visible_wp])
expect(instance.value_objects)
.to match_array [visible_wp]
end
end

@ -0,0 +1,259 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2018 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 docs/COPYRIGHT.rdoc for more details.
#++
require 'spec_helper'
describe Queries::WorkPackages::Filter::ParentFilter, type: :model do
let(:project) { FactoryGirl.build_stubbed(:project) }
let(:query) do
FactoryGirl.build_stubbed(:query, project: project)
end
it_behaves_like 'basic query filter' do
let(:class_key) { :parent }
let(:type) { :list }
before do
instance.context = query
end
describe '#available?' do
context 'within a project' do
it 'is true if any work package exists and is visible' do
allow(WorkPackage)
.to receive_message_chain(:visible, :for_projects, :exists?)
.with(no_args)
.with(project)
.with(no_args)
.and_return true
expect(instance).to be_available
end
it 'is false if no work package exists/ is visible' do
allow(WorkPackage)
.to receive_message_chain(:visible, :for_projects, :exists?)
.with(no_args)
.with(project)
.with(no_args)
.and_return false
expect(instance).not_to be_available
end
end
context 'outside of a project' do
let(:project) { nil }
it 'is true if any work package exists and is visible' do
allow(WorkPackage)
.to receive_message_chain(:visible, :exists?)
.with(no_args)
.and_return true
expect(instance).to be_available
end
it 'is false if no work package exists/ is visible' do
allow(WorkPackage)
.to receive_message_chain(:visible, :exists?)
.with(no_args)
.and_return false
expect(instance).not_to be_available
end
end
end
describe '#ar_object_filter?' do
it 'is true' do
expect(instance).to be_ar_object_filter
end
end
describe '#allowed_values' do
it 'raises an error' do
expect { instance.allowed_values }.to raise_error NotImplementedError
end
end
describe '#value_object' do
let(:visible_wp) { FactoryGirl.build_stubbed(:work_package) }
it 'returns the work package for the values' do
allow(WorkPackage)
.to receive_message_chain(:visible, :for_projects, :find)
.with(no_args)
.with(project)
.with(instance.values)
.and_return([visible_wp])
expect(instance.value_objects)
.to match_array [visible_wp]
end
end
describe '#allowed_objects' do
it 'raises an error' do
expect { instance.allowed_objects }.to raise_error NotImplementedError
end
end
describe '#valid_values!' do
let(:visible_wp) { FactoryGirl.build_stubbed(:work_package) }
let(:invisible_wp) { FactoryGirl.build_stubbed(:work_package) }
context 'within a project' do
it 'removes all non existing/non visible ids' do
instance.values = [visible_wp.id.to_s, invisible_wp.id.to_s, '999999']
allow(WorkPackage)
.to receive_message_chain(:visible, :for_projects, :where, :pluck)
.with(no_args)
.with(project)
.with(id: instance.values)
.with(:id)
.and_return([visible_wp.id])
instance.valid_values!
expect(instance.values)
.to match_array [visible_wp.id.to_s]
end
end
context 'outside of a project' do
let(:project) { nil }
it 'removes all non existing/non visible ids' do
instance.values = [visible_wp.id.to_s, invisible_wp.id.to_s, '999999']
allow(WorkPackage)
.to receive_message_chain(:visible, :where, :pluck)
.with(no_args)
.with(id: instance.values)
.with(:id)
.and_return([visible_wp.id])
instance.valid_values!
expect(instance.values)
.to match_array [visible_wp.id.to_s]
end
end
end
describe '#validate' do
let(:visible_wp) { FactoryGirl.build_stubbed(:work_package) }
let(:invisible_wp) { FactoryGirl.build_stubbed(:work_package) }
context 'within a project' do
it 'is valid if only visible wps are values' do
instance.values = [visible_wp.id.to_s]
allow(WorkPackage)
.to receive_message_chain(:visible, :for_projects, :where, :pluck)
.with(no_args)
.with(project)
.with(id: instance.values)
.with(:id)
.and_return([visible_wp.id])
expect(instance).to be_valid
end
it 'is invalid if invisible wps are values' do
instance.values = [invisible_wp.id.to_s, visible_wp.id.to_s]
allow(WorkPackage)
.to receive_message_chain(:visible, :for_projects, :where, :pluck)
.with(no_args)
.with(project)
.with(id: instance.values)
.with(:id)
.and_return([visible_wp.id])
expect(instance).not_to be_valid
end
end
context 'outside of a project' do
let(:project) { nil }
it 'is valid if only visible wps are values' do
instance.values = [visible_wp.id.to_s]
allow(WorkPackage)
.to receive_message_chain(:visible, :where, :pluck)
.with(no_args)
.with(id: instance.values)
.with(:id)
.and_return([visible_wp.id])
expect(instance).to be_valid
end
it 'is invalid if invisible wps are values' do
instance.values = [invisible_wp.id.to_s, visible_wp.id.to_s]
allow(WorkPackage)
.to receive_message_chain(:visible, :where, :pluck)
.with(no_args)
.with(id: instance.values)
.with(:id)
.and_return([visible_wp.id])
expect(instance).not_to be_valid
end
end
end
describe '#where and #includes' do
let(:parent) { FactoryGirl.create(:work_package) }
let(:visible_wp) { FactoryGirl.create(:work_package, parent: parent) }
before do
visible_wp
instance.values = [parent.id.to_s]
instance.operator = '='
end
it 'filters' do
scope = WorkPackage
.references(instance.includes)
.includes(instance.includes)
.where(instance.where)
expect(scope)
.to match_array [visible_wp]
end
end
end
end
Loading…
Cancel
Save