Add hidden flag for project-context queries

Allows the API to create a hidden query that will not be rendered to the
user even if it is within a project context.
pull/7033/head
Oliver Günther 6 years ago
parent 23f96ff07f
commit e9cc09840d
No known key found for this signature in database
GPG Key ID: A3A8BDAD7C0C552C
  1. 1
      app/contracts/queries/base_contract.rb
  2. 7
      app/models/queries/operators/boolean_equals.rb
  3. 1
      app/models/queries/queries.rb
  4. 49
      app/models/queries/queries/filters/hidden_filter.rb
  5. 2
      app/models/query.rb
  6. 1
      app/services/base_type_service.rb
  7. 5
      db/migrate/20190207155607_add_hidden_to_queries.rb
  8. 1
      docs/api/apiv3/endpoints/queries.apib
  9. 9
      frontend/src/app/components/wp-query-select/wp-query-select-dropdown.component.ts
  10. 7
      frontend/src/app/modules/hal/dm-services/query-dm.service.ts
  11. 1
      frontend/src/app/modules/hal/resources/query-resource.ts
  12. 1
      lib/api/v3/queries/query_representer.rb
  13. 7
      lib/api/v3/queries/schemas/query_schema_representer.rb
  14. 26
      spec/features/work_packages/table/queries/hidden_query_spec.rb
  15. 11
      spec/lib/api/v3/queries/query_representer_generation_spec.rb
  16. 12
      spec/lib/api/v3/queries/schemas/query_schema_representer_spec.rb
  17. 117
      spec/models/queries/queries/filters/hidden_filter_spec.rb
  18. 8
      spec/models/query_spec.rb

@ -35,6 +35,7 @@ module Queries
attribute :name
attribute :project_id
attribute :hidden
attribute :is_public # => public
attribute :display_sums # => sums
attribute :timeline_visible

@ -36,13 +36,6 @@ module Queries::Operators
def self.sql_for_field(values, db_table, db_field)
sql = ''
# special case for old timeline where
# -1 is still used
# TODO: remove once old timeline is removed
if values.include?('-1')
return "#{db_table}.#{db_field} IS NULL"
end
if values.include?('f')
sql = "#{db_table}.#{db_field} IS NULL OR "
end

@ -33,4 +33,5 @@
module Queries::Queries
Queries::Register.filter Queries::Queries::QueryQuery, Queries::Queries::Filters::ProjectFilter
Queries::Register.filter Queries::Queries::QueryQuery, Queries::Queries::Filters::ProjectIdentifierFilter
Queries::Register.filter Queries::Queries::QueryQuery, Queries::Queries::Filters::HiddenFilter
end

@ -0,0 +1,49 @@
#-- 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.
#++
class Queries::Queries::Filters::HiddenFilter < Queries::Queries::Filters::QueryFilter
def self.key
:hidden
end
def allowed_values
[
[I18n.t(:general_text_yes), OpenProject::Database::DB_VALUE_TRUE],
[I18n.t(:general_text_no), OpenProject::Database::DB_VALUE_FALSE]
]
end
def type
:list
end
def type_strategy
@type_strategy ||= ::Queries::Filters::Strategies::BooleanList.new self
end
end

@ -64,6 +64,8 @@ class Query < ActiveRecord::Base
scope(:global, -> { where(project_id: nil) })
scope(:hidden, -> { where(hidden: true) })
def self.new_default(attributes = nil)
new(attributes).tap do |query|
query.add_default_filter

@ -113,6 +113,7 @@ class BaseTypeService
.call(attributes.with_indifferent_access)
query.show_hierarchies = false
query.hidden = true
groups[index][1] = [query]
end

@ -0,0 +1,5 @@
class AddHiddenToQueries < ActiveRecord::Migration[5.2]
def change
add_column :queries, :hidden, :boolean, default: false
end
end

@ -40,6 +40,7 @@ Please note, that all the properties listed above will also be embedded when ind
| timelineZoomLevel| Which zoom level should the timeline be rendered in? | String | days, weeks, months, quarters, years | READ |
| highlightingMode | Which highlighting mode should the table have? | String | none, inline, status, priority, type | READ |
| showHierarchies | Should the hierarchy mode be enabled? | Boolean | | READ |
| hidden | Should the query be hidden from the query list? | Boolean | | READ |
| public | Can users besides the owner see the query? | Boolean | | READ |
| starred | Should the query be highlighted to the user? | Boolean | | READ |

@ -170,10 +170,11 @@ export class WorkPackageQuerySelectDropdownComponent implements OnInit, OnDestro
this.updateMenuOnChanges(this.searchInput);
}
private transformQueries(collection:CollectionResource) {
let loadedQueries:IAutocompleteItem[] = _.map(collection.elements, (query:any) => {
return {label: query.name, query: query, query_props: null};
});
private transformQueries(collection:CollectionResource<QueryResource>) {
let loadedQueries:IAutocompleteItem[] = collection.elements
.map(query => {
return {label: query.name, query: query, query_props: null};
});
// Add to the loaded set of queries the fixed set of queries for the current project context
const combinedQueries = loadedQueries.concat(this.wpStaticQueries.all);

@ -157,7 +157,7 @@ export class QueryDmService {
}
}
public all(projectIdentifier:string|null|undefined):Promise<CollectionResource> {
public all(projectIdentifier:string|null|undefined):Promise<CollectionResource<QueryResource>> {
let filters = new ApiV3FilterBuilder();
if (projectIdentifier) {
@ -168,10 +168,13 @@ export class QueryDmService {
filters.add('project', '!*', []);
}
// Exclude hidden queries
filters.add('hidden', '=', ['f']);
let urlQuery = { filters: filters.toJson() };
return this.halResourceService
.get<CollectionResource>(this.pathHelper.api.v3.queries.toString(), urlQuery)
.get<CollectionResource<QueryResource>>(this.pathHelper.api.v3.queries.toString(), urlQuery)
.toPromise();
}

@ -70,6 +70,7 @@ export class QueryResource extends HalResource {
public timelineLabels:TimelineLabels;
public showHierarchies:boolean;
public public:boolean;
public hidden:boolean;
public project:ProjectResource;
public $initialize(source:any) {

@ -260,6 +260,7 @@ module API
property :display_sums, as: :sums
property :is_public, as: :public
property :hidden
# Timeline properties
property :timeline_visible

@ -144,6 +144,13 @@ module API
has_default: true,
visibility: false
schema :hidden,
type: 'Boolean',
required: true,
writable: true,
has_default: true,
visibility: false
schema_with_allowed_collection :columns,
type: '[]QueryColumn',
required: false,

@ -0,0 +1,26 @@
require 'spec_helper'
describe 'Visiting a hidden query', js: true do
let(:user) { FactoryBot.create :admin }
let(:project) { FactoryBot.create(:project) }
let(:wp_table) { Pages::WorkPackagesTable.new(project) }
let(:highlighting) { ::Components::WorkPackages::Highlighting.new }
let!(:work_package) { FactoryBot.create :work_package, project: project }
let!(:query) { FactoryBot.create(:query, name: 'my hidden query', user: user, project: project, hidden: true) }
before do
login_as(user)
wp_table.visit!
end
it 'does not render the hidden query' do
expect(page).to have_selector('.wp-query-menu--search-ul')
expect(page).to have_no_selector('.ui-menu-item', text: 'my hidden query')
query.update(name: 'my visible query', hidden: false)
wp_table.visit!
expect(page).to have_selector('.ui-menu-item', text: 'my visible query')
end
end

@ -507,6 +507,17 @@ describe ::API::V3::Queries::QueryRepresenter do
is_expected.to be_json_eql(query.is_public.to_json).at_path('public')
end
describe 'hidden' do
it 'renders when the value is not set' do
is_expected.to be_json_eql(false.to_json).at_path('hidden')
end
it 'renders when the value is set' do
query.hidden = true
is_expected.to be_json_eql(true.to_json).at_path('hidden')
end
end
describe 'highlighting' do
context 'with EE', with_ee: %i[conditional_highlighting] do
it 'renders when the value is set' do

@ -200,6 +200,18 @@ describe ::API::V3::Queries::Schemas::QuerySchemaRepresenter do
end
end
describe 'hidden' do
let(:path) { 'hidden' }
it_behaves_like 'has basic schema properties' do
let(:type) { 'Boolean' }
let(:name) { Query.human_attribute_name('hidden') }
let(:required) { true }
let(:writable) { true }
let(:has_default) { true }
end
end
describe 'sums' do
let(:path) { 'sums' }

@ -0,0 +1,117 @@
#-- 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::Queries::Filters::HiddenFilter, type: :model do
let(:instance) do
described_class.create!(name: 'hidden', context: nil, operator: operator, values: values)
end
it_behaves_like 'basic query filter' do
let(:class_key) { :hidden }
let(:type) { :list }
end
include_context 'filter tests'
let(:type) { :list }
describe '#scope' do
context 'for "= t"' do
let(:operator) { '=' }
let(:values) { ['t'] }
it 'is the same as handwriting the query' do
expected = Query.where(["queries.hidden IN (?)", values])
expect(instance.scope.to_sql).to eql expected.to_sql
end
end
context 'for "= f"' do
let(:operator) { '=' }
let(:values) { ['f'] }
it 'is the same as handwriting the query' do
sql = "queries.hidden IS NULL OR queries.hidden IN (?)"
expected = Query.where([sql, values])
expect(instance.scope.to_sql).to eql expected.to_sql
end
end
context 'for "!"' do
let(:operator) { '!' }
let(:values) { ['f'] }
it 'is the same as handwriting the query' do
expected = Query.where(["queries.hidden IN ('t')", values])
expect(instance.scope.to_sql).to eql expected.to_sql
end
end
end
describe '#valid?' do
let(:operator) { '=' }
context 'for true value' do
let(:values) { ['t'] }
it 'is valid' do
expect(instance).to be_valid
end
end
context 'for false value' do
let(:values) { ['f'] }
it 'is valid' do
expect(instance).to be_valid
end
end
context 'for an invalid operator' do
let(:operator) { '*' }
it 'is invalid' do
expect(instance).to be_invalid
end
end
context 'for an invalid value' do
let(:values) { ['inexistent'] }
it 'is invalid' do
expect(instance).to be_invalid
end
end
end
end

@ -62,6 +62,14 @@ describe Query, type: :model do
end
end
describe 'hidden' do
it 'sets the hidden property' do
expect(query.hidden).to eq(false)
query.hidden = true
expect(query.hidden).to eq(true)
end
end
describe 'timeline' do
it 'has a property for timeline visible' do
expect(query.timeline_visible).to be_falsey

Loading…
Cancel
Save