rework hidden/starred of queries

The MenuItems::QueryMenuItems class used to map the `starred` state is replaced by a simple boolean flag. The hidden flag is removed (but still readable) as the concept of `View`s completely replaces it.
pull/9957/head
ulferts 3 years ago
parent 45704d1eba
commit 0fe90c21a7
No known key found for this signature in database
GPG Key ID: A205708DE1284017
  1. 10
      app/contracts/queries/base_contract.rb
  2. 2
      app/contracts/queries/update_contract.rb
  3. 4
      app/contracts/views/base_contract.rb
  4. 2
      app/models/permitted_params.rb
  5. 2
      app/models/queries/filters/not_existing_filter.rb
  6. 19
      app/models/queries/scopes/having_views.rb
  7. 2
      app/models/queries/scopes/visible.rb
  8. 20
      app/models/query.rb
  9. 6
      app/policies/query_policy.rb
  10. 14
      app/seeders/demo_data/query_builder.rb
  11. 15
      app/seeders/demo_data/work_package_board_seeder.rb
  12. 1
      app/services/base_type_service.rb
  13. 7
      app/services/projects/copy/queries_dependent_service.rb
  14. 19
      app/services/queries/copy/views_dependent_service.rb
  15. 2
      app/services/queries/copy_service.rb
  16. 10
      app/services/queries/set_attributes_service.rb
  17. 54
      app/services/queries/update_service.rb
  18. 2
      app/services/reports/assignee_report.rb
  19. 2
      app/services/reports/author_report.rb
  20. 2
      app/services/reports/responsible_report.rb
  21. 2
      app/workers/principals/delete_job.rb
  22. 1
      config/constants/ar_to_api_conversions.rb
  23. 5
      config/locales/en.yml
  24. 88
      db/migrate/20211209092519_query_views.rb
  25. 15
      docs/api/apiv3/components/examples/query_schema.yml
  26. 8
      docs/api/apiv3/components/schemas/query_model.yml
  27. 6
      docs/api/apiv3/components/schemas/schema_for_global_queries_model.yml
  28. 7
      docs/api/apiv3/tags/queries.yml
  29. 21
      frontend/src/app/core/apiv3/endpoints/queries/apiv3-queries-paths.ts
  30. 1
      frontend/src/app/features/boards/board/board-list/board-lists.service.ts
  31. 5
      frontend/src/app/shared/components/work-package-graphs/configuration/wp-graph-configuration.ts
  32. 7
      lib/api/decorators/property_schema_representer.rb
  33. 12
      lib/api/decorators/schema_representer.rb
  34. 51
      lib/api/v3/queries/queries_api.rb
  35. 2
      lib/api/v3/queries/query_helper.rb
  36. 18
      lib/api/v3/queries/query_representer.rb
  37. 21
      lib/api/v3/queries/schemas/query_schema_representer.rb
  38. 2
      lib/api/v3/views/view_representer.rb
  39. 2
      modules/bim/app/contracts/bim/views/contract_strategy.rb
  40. 2
      modules/bim/config/locales/en.seeders.bim.yml
  41. 5
      modules/bim/lib/open_project/bim/patches/work_package_board_seeder_patch.rb
  42. 6
      modules/boards/spec/factories/board_factory.rb
  43. 2
      modules/dashboards/spec/factories/grid_factory.rb
  44. 3
      modules/my_page/spec/models/grids/my_page_spec.rb
  45. 2
      modules/reporting/lib/widget/controls/save_as.rb
  46. 2
      modules/team_planner/spec/requests/api/v3/views/create_resource_spec.rb
  47. 2
      spec/contracts/queries/update_contract_spec.rb
  48. 2
      spec/contracts/views/shared_contract_examples.rb
  49. 9
      spec/factories/menu_item_factory.rb
  50. 6
      spec/factories/query_factory.rb
  51. 15
      spec/features/menu_items/query_menu_item_spec.rb
  52. 4
      spec/features/work_packages/select/select_query_spec.rb
  53. 2
      spec/features/work_packages/table/queries/query_menu_spec.rb
  54. 15
      spec/lib/api/v3/queries/query_representer_rendering_spec.rb
  55. 7
      spec/lib/api/v3/views/view_representer_rendering_spec.rb
  56. 53
      spec/models/menu_items/query_menu_item_spec.rb
  57. 4
      spec/models/query/scopes/visible_spec.rb
  58. 18
      spec/models/query_spec.rb
  59. 24
      spec/policies/query_policy_spec.rb
  60. 3
      spec/requests/api/v3/queries/query_resource_spec.rb
  61. 2
      spec/requests/api/v3/queries/update_form_api_spec.rb
  62. 4
      spec/requests/api/v3/queries/update_query_spec.rb
  63. 2
      spec/requests/api/v3/views/create_resource_spec.rb
  64. 8
      spec/requests/api/v3/views/index_resource_spec.rb
  65. 2
      spec/requests/api/v3/views/show_resource_spec.rb
  66. 3
      spec/seeders/demo_data_seeder_spec.rb
  67. 59
      spec/services/projects/copy_service_integration_spec.rb
  68. 15
      spec/services/projects/set_attributes_service_spec.rb
  69. 82
      spec/services/queries/update_service_spec.rb
  70. 18
      spec_legacy/fixtures/queries.yml

@ -35,8 +35,8 @@ module Queries
attribute :name
attribute :project_id
attribute :hidden
attribute :is_public # => public
attribute :starred
attribute :public # => public
attribute :display_sums # => sums
attribute :timeline_visible
attribute :timeline_zoom_level
@ -66,7 +66,7 @@ module Queries
end
def project_visible?
Project.visible(user).where(id: project_id).exists?
Project.visible(user).exists?(id: project_id)
end
def may_not_manage_queries?
@ -75,10 +75,10 @@ module Queries
def user_allowed_to_make_public
# Add error only when changing public flag
return unless model.is_public_changed?
return unless model.public_changed?
return if model.project_id.present? && model.project.nil?
if is_public && may_not_manage_queries?
if model.public && may_not_manage_queries?
errors.add :public, :error_unauthorized
end
end

@ -39,7 +39,7 @@ module Queries
def user_allowed_to_change
# Check user self-saving their own queries
# or user saving public queries
if model.is_public?
if model.public?
user_allowed_to_change_public
else
user_allowed_to_change_query

@ -80,8 +80,8 @@ module Views
def query_permissions?
# The visibility i.e. whether a private query belongs to the user is checked via the
# query_visible? method.
(model.query.is_public && user_allowed_on_query?(:manage_public_queries)) ||
(!model.query.is_public && user_allowed_on_query?(:save_queries))
(model.query.public && user_allowed_on_query?(:manage_public_queries)) ||
(!model.query.public && user_allowed_on_query?(:save_queries))
end
def user_allowed_on_query?(permission)

@ -546,7 +546,7 @@ class PermittedParams
query: %i(
name
display_sums
is_public
public
group_by
),
role: [

@ -58,7 +58,7 @@ module Queries
def to_hash
{
non_existent_filter: {
(name || :non_existent_filter) => {
operator: operator,
values: values
}

@ -1,5 +1,3 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2021 the OpenProject GmbH
@ -28,13 +26,16 @@
# See COPYRIGHT and LICENSE files for more details.
#++
module QueryMenuItemsHelper
def update_query_menu_item_path(project, query_menu_item)
if query_menu_item.persisted?
query_menu_item_path(project, query_menu_item.query,
query_menu_item)
else
query_menu_items_path(project, query_menu_item.query)
module Queries::Scopes
module HavingViews
extend ActiveSupport::Concern
class_methods do
# Return queries that have a view associated
def having_views
includes(:views)
.where.not(views: { id: nil })
end
end
end
end

@ -41,7 +41,7 @@ module Queries::Scopes
# and the query is public and the query is global (no project)
def visible(user)
scope = where(user_id: user.id)
.or(where(is_public: true))
.or(where(public: true))
.where(project: Project.allowed_to(user, :view_work_packages))
if user.allowed_to_globally?(:view_work_packages)

@ -36,9 +36,6 @@ class Query < ApplicationRecord
belongs_to :project
belongs_to :user
has_one :query_menu_item, -> { order('name') },
class_name: 'MenuItems::QueryMenuItem',
dependent: :delete, foreign_key: 'navigatable_id'
has_many :views,
dependent: :destroy
@ -56,14 +53,11 @@ class Query < ApplicationRecord
validate :validate_show_hierarchies
include Scopes::Scoped
scopes :visible
scopes :visible,
:having_views
scope(:global, -> { where(project_id: nil) })
scope(:hidden, -> { where(hidden: true) })
scope(:non_hidden, -> { where(hidden: false) })
def self.new_default(attributes = nil)
new(attributes).tap do |query|
query.add_default_filter
@ -144,6 +138,10 @@ class Query < ApplicationRecord
end
end
def hidden
views.empty?
end
# Try to fix an invalid query
#
# Fixes:
@ -365,12 +363,6 @@ class Query < ApplicationRecord
raise ::Query::StatementInvalid.new(e.message)
end
# Note: Convenience method to allow the angular front end to deal with query
# menu items in a non implementation-specific way
def starred
!!query_menu_item
end
def project_limiting_filter
return if subproject_filters_involved?

@ -58,7 +58,7 @@ class QueryPolicy < BasePolicy
def viewable?(query)
view_work_packages_allowed?(query) &&
(query.is_public? || query.user == user)
(query.public? || query.user == user)
end
def create_allowed?(query)
@ -70,7 +70,7 @@ class QueryPolicy < BasePolicy
end
def publicize_allowed?(query)
!query.is_public &&
!query.public &&
query.user_id == user.id &&
manage_public_queries_allowed?(query)
end
@ -80,7 +80,7 @@ class QueryPolicy < BasePolicy
end
def public_manageable_query?(query)
query.is_public &&
query.public &&
manage_public_queries_allowed?(query)
end

@ -51,8 +51,7 @@ module DemoData
{
name: config[:name],
user: User.admin.first,
is_public: config[:is_public] != false,
hidden: config[:hidden] == true,
public: config[:public] != false,
show_hierarchies: config[:hierarchy] == true,
timeline_visible: config[:timeline] == true
}
@ -70,16 +69,15 @@ module DemoData
query = Query.create! attr
create_menu_item query
create_view(query) unless config[:hidden]
query
end
def create_menu_item(query)
MenuItems::QueryMenuItem.create!(
navigatable_id: query.id,
name: SecureRandom.uuid,
title: query.name
def create_view(query)
View.create!(
type: 'work_packages_table',
query: query
)
end

@ -110,11 +110,8 @@ module DemoData
statuses.to_a.map do |status|
Query.new_default(project: project, user: admin).tap do |query|
# Hide the query in the main menu
query.hidden = true
# Make it public so that new members can see it too
query.is_public = true
query.public = true
query.name = status.name
# Set filter by this status
@ -163,11 +160,8 @@ module DemoData
lists.map do |list|
Query.new(project: project, user: admin).tap do |query|
# Hide the query in the main menu
query.hidden = true
# Make it public so that new members can see it too
query.is_public = true
query.public = true
query.name = list[:name]
@ -235,11 +229,8 @@ module DemoData
parents.map do |parent|
Query.new_default(project: project, user: admin).tap do |query|
# Hide the query in the main menu
query.hidden = true
# Make it public so that new members can see it too
query.is_public = true
query.public = true
query.name = parent.subject
# Set filter by this status

@ -137,7 +137,6 @@ class BaseTypeService
.call(props.with_indifferent_access)
query.show_hierarchies = false
query.hidden = true
[
name,

@ -41,7 +41,10 @@ module Projects::Copy
protected
# Copies queries from +project+
# Only includes the queries visible in the wp table view.
# Only includes the queries having a view so the ones that are e.g. in:
# * the work packages table
# * the team planner
# * the bcf module
def copy_dependency(params:)
mapping = queries_to_copy.map do |query|
copy = duplicate_query(query, params)
@ -56,7 +59,7 @@ module Projects::Copy
end
def queries_to_copy
source.queries.non_hidden.includes(:query_menu_item)
source.queries.having_views.includes(:views)
end
def duplicate_query(query, params)

@ -1,5 +1,3 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2021 the OpenProject GmbH
@ -29,19 +27,20 @@
#++
module Queries::Copy
class MenuItemDependentService < ::Copy::Dependency
class ViewsDependentService < ::Copy::Dependency
protected
def copy_dependency(params:)
duplicate_query_menu_item(source, target)
duplicate_views(source, target)
end
def duplicate_query_menu_item(query, new_query)
if query.query_menu_item && new_query.persisted?
::MenuItems::QueryMenuItem.create(
navigatable_id: new_query.id,
name: SecureRandom.uuid,
title: query.query_menu_item.title
def duplicate_views(query, new_query)
return if new_query.new_record?
query.views.each do |view|
View.create(
type: view.type,
query_id: new_query.id
)
end
end

@ -32,7 +32,7 @@ module Queries
class CopyService < ::BaseServices::Copy
def self.copy_dependencies
[
::Queries::Copy::MenuItemDependentService,
::Queries::Copy::ViewsDependentService,
::Queries::Copy::OrderedWorkPackagesDependentService
]
end

@ -1,5 +1,3 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2021 the OpenProject GmbH
@ -28,10 +26,4 @@
# See COPYRIGHT and LICENSE files for more details.
#++
class MenuItems::QueryMenuItem < MenuItem
belongs_to :query, foreign_key: 'navigatable_id'
def unique_name
"#{name}-#{id}".to_sym
end
end
class Queries::SetAttributesService < ::BaseServices::SetAttributes; end

@ -28,56 +28,4 @@
# See COPYRIGHT and LICENSE files for more details.
#++
class Queries::UpdateService < Queries::BaseService
def initialize(**args)
super(**args)
self.contract_class = Queries::UpdateContract
end
def call(query)
result, errors = update query
service_result result, errors, query
end
private
def update(query)
menu_item = prepare_menu_item query
result = nil
errors = nil
query.transaction do
result, errors = validate_and_save(query, user)
if !result
raise ActiveRecord::Rollback
elsif menu_item && !menu_item.save
result = false
merge_errors(errors, menu_item)
end
end
[result, errors]
end
def prepare_menu_item(query)
if query.changes.include?('name') &&
query.query_menu_item
menu_item = query.query_menu_item
menu_item.title = query.name
menu_item
end
end
def merge_errors(errors, menu_item)
menu_item.errors.each do |sym, message|
errors.add(sym, message)
end
end
end
class Queries::UpdateService < ::BaseServices::Update; end

@ -38,7 +38,7 @@ class Reports::AssigneeReport < Reports::Report
end
def rows
@rows ||= @project.members.map(&:user).sort
@rows ||= @project.members.map(&:principal).sort
end
def data

@ -38,7 +38,7 @@ class Reports::AuthorReport < Reports::Report
end
def rows
@rows ||= @project.members.map(&:user).sort
@rows ||= @project.members.map(&:principal).sort
end
def data

@ -38,7 +38,7 @@ class Reports::ResponsibleReport < Reports::Report
end
def rows
@rows ||= @project.members.map(&:user).sort
@rows ||= @project.members.map(&:principal).sort
end
def data

@ -63,7 +63,7 @@ class Principals::DeleteJob < ApplicationJob
end
def delete_private_queries(principal)
::Query.where(user_id: principal.id, is_public: false).delete_all
::Query.where(user_id: principal.id, public: false).delete_all
CostQuery.where(user_id: principal.id, is_public: false).delete_all
end

@ -42,7 +42,6 @@ module Constants
relation_type: 'type',
mail: 'email',
column_names: 'columns',
is_public: 'public',
sort_criteria: 'sortBy',
message: 'post',
firstname: 'firstName',

@ -915,7 +915,7 @@ en:
id: "ID"
is_default: "Default value"
is_for_all: "For all projects"
is_public: "Public"
public: "Public"
# kept for backwards compatibility
issue: "Work package"
lastname: "Last name"
@ -1795,7 +1795,6 @@ en:
label_public_projects: "Public projects"
label_query_new: "New query"
label_query_plural: "Custom queries"
label_query_menu_item: "Query menu item"
label_read: "Read..."
label_register: "Create a new account"
label_register_with_developer: "Register as developer"
@ -3002,8 +3001,6 @@ en:
To delete the wiki the wiki module can be deactivated by project administrators.
wiki_menu_item_delete_not_permitted: The wiki menu item of the only wiki page cannot be deleted.
query_menu_item_for: "Menu item for query \"%{title}\""
# TODO: merge with work_packages top level key
work_package:
updated_automatically_by_child_changes: |

@ -0,0 +1,88 @@
class QueryViews < ActiveRecord::Migration[6.1]
def up
add_column :queries,
:starred,
:boolean,
default: false
execute <<~SQL.squish
INSERT INTO
views (
type,
query_id,
created_at,
updated_at
)
SELECT
'work_packages_table',
id,
created_at,
updated_at
FROM queries
WHERE
hidden = false
SQL
execute <<~SQL.squish
UPDATE
queries
SET
starred = true
WHERE
id IN (SELECT navigatable_id FROM menu_items WHERE type = 'MenuItems::QueryMenuItem')
SQL
execute <<~SQL.squish
DELETE FROM
menu_items
WHERE
type = 'MenuItems::QueryMenuItem'
SQL
remove_column :queries,
:hidden
rename_column :queries,
:is_public,
:public
end
def down
rename_column :queries,
:public,
:is_public
add_column :queries,
:hidden,
:boolean,
default: false
# Consciously avoiding the use of a PostgreSQL 13.0 feature (gen_random_uuid())
Query.where(starred: true).find_each do |query|
::MenuItem.create(
type: 'MenuItems::QueryMenuItem',
navigatable_id: query.id,
name: SecureRandom.uuid,
title: query.name
)
end
execute <<~SQL.squish
UPDATE
queries
SET
hidden = true
WHERE
id NOT IN (SELECT query_id FROM views WHERE type = 'work_packages_table')
SQL
execute <<~SQL.squish
DELETE FROM
views
WHERE type = 'work_packages_table'
SQL
remove_column :queries,
:starred
end
end

@ -136,12 +136,14 @@ value:
required: false
type: "[]QueryColumn"
writable: true
deprecated: true
highlightingMode:
hasDefault: true
name: Highlighting mode
required: false
type: String
writable: true
deprecated: true
id:
hasDefault: false
name: ID
@ -170,15 +172,6 @@ value:
required: false
type: Boolean
writable: true
projections:
_links:
allowedValuesSchemas:
href: "/api/v3/queries/projections_schemas"
hasDefault: true
name: Projections
required: false
type: "[]QueryProjection"
writable: true
results:
hasDefault: false
name: Results
@ -191,6 +184,7 @@ value:
required: false
type: Boolean
writable: true
deprecated: true
sortBy:
_links: {}
hasDefault: true
@ -216,18 +210,21 @@ value:
required: false
type: QueryTimelineLabels
writable: true
deprecated: true
timelineVisible:
hasDefault: true
name: Timeline visible
required: false
type: Boolean
writable: true
deprecated: true
timelineZoomLevel:
hasDefault: true
name: Timeline zoom level
required: false
type: String
writable: true
deprecated: true
updatedAt:
hasDefault: false
name: Updated on

@ -28,26 +28,32 @@ properties:
type: boolean
description: Should the timeline mode be shown?
readOnly: true
deprecated: true
timelineLabels:
type: QueryTimelineLabels
description: Which labels are shown in the timeline, empty when default
readOnly: true
deprecated: true
timelineZoomLevel:
type: string
description: Which zoom level should the timeline be rendered in?
readOnly: true
deprecated: true
highlightingMode:
type: string
description: Which highlighting mode should the table have?
readOnly: true
deprecated: true
showHierarchies:
type: boolean
description: Should the hierarchy mode be enabled?
readOnly: true
deprecated: true
hidden:
type: boolean
description: Should the query be hidden from the query list?
readOnly: true
deprecated: true
public:
type: boolean
description: Can users besides the owner see the query?
@ -66,8 +72,6 @@ properties:
format: date-time
description: Time of the most recent change to the query
readOnly: true
projections:
type: "[]QueryProjection"
_links:
type: object
properties:

@ -62,24 +62,28 @@ example:
required: false
hasDefault: true
writable: true
deprecated: true
timelineZoomLevel:
type: String
name: Timeline zoom level
required: false
hasDefault: true
writable: true
deprecated: true
timelineLabels:
type: QueryTimelineLabels
name: Timeline labels
required: false
hasDefault: true
writable: true
deprecated: true
highlightingMode:
type: String
name: Highlighting mode
required: false
hasDefault: true
writable: true
deprecated: true
highlightedAttributes:
type: "[]QueryColumn"
name: Highlighted attributes
@ -87,12 +91,14 @@ example:
hasDefault: true
writable: true
location: _links
deprecated: true
showHierarchies:
type: Boolean
name: Show hierarchies
required: false
hasDefault: true
writable: true
deprecated: true
starred:
type: Boolean
name: Starred

@ -28,7 +28,7 @@ description: |
Please note, that all the properties listed above will also be embedded when individual queries are returned but will not be embedded when a list of queries is returned. Whether the properties are embedded or not may be subject to change in the future.
The `columns` and `highlightedAttributes` properties will be moved into the `QueryProjection::Table` so it is deprecated to have it listed within the Query directly.
The `columns` and `highlightedAttributes` properties will be moved into `Views::WorkPackagesTable` so it is deprecated to have it listed within the Query directly.
## Local Properties
@ -37,7 +37,6 @@ description: |
| id | Query id | Integer | x > 0 | READ |
| name | Query name | String | | READ/WRITE |
| filters | A set of QueryFilters which will be applied to the work packages to determine the resulting work packages | []QueryFilterInstance | | READ/WRITE |
| projections | A set of `QueryProjection` objects where each represents one projection (display option) defined for the query. | []QueryProjection | | READ/WRITE |
| sums | Should sums (of supported properties) be shown? | Boolean | | READ/WRITE |
| timelineVisible | Should the timeline mode be shown? | Boolean | | READ/WRITE |
| timelineLabels | Which labels are shown in the timeline, empty when default | QueryTimelineLabels | | READ/WRITE |
@ -52,9 +51,9 @@ description: |
A query that is not assigned to a project (`"project": null`) is called a global query. Global queries filter work packages regardless of the project they are assigned to. As such, a different set of filters exists for those queries.
The `hidden` property is deprecated as it will be replaced by `QueryProjection`.
The `hidden` property is deprecated as it is replaced by the `Views` concept. A query that isn't hidden will have a `View` while a query that is hidden won't.
The `timelineVisible`, `timelineLabels`, `timelineZoomLevel`, `highlightingMode` and `showHierarchies` properties will be moved into the more appropriate projections (`QueryProjection::Gantt` and `QueryProjection::Table`) so it is deprecated to have them within the Query directly.
The `timelineVisible`, `timelineLabels`, `timelineZoomLevel`, `highlightingMode` and `showHierarchies` properties will be moved into the more appropriate Views (probably `Views::WorkPackagesTable`) so it is deprecated to have them within the Query directly.
## Query Filter Instance

@ -132,25 +132,4 @@ export class APIv3QueriesPaths extends APIv3ResourceCollection<QueryResource, AP
}
return query.star();
}
/**
* Filter for non-hidden queries
*
* @param projectIdentifier
*/
public filterNonHidden(projectIdentifier:string|null):Observable<CollectionResource<QueryResource>> {
const listParams:Apiv3ListParameters = {
filters: [['hidden', '=', ['f']]],
};
if (projectIdentifier) {
// all queries with the provided projectIdentifier
listParams.filters!.push(['project_identifier', '=', [projectIdentifier]]);
} else {
// all queries having no project (i.e. being global)
listParams.filters!.push(['project', '!*', []]);
}
return this.list(listParams);
}
}

@ -96,7 +96,6 @@ export class BoardListsService {
private buildQueryRequest(params:Object) {
return {
hidden: true,
public: true,
_links: {
sortBy: [

@ -26,10 +26,9 @@ export class WpGraphConfiguration implements WpGraphConfiguration {
this.chartType = this.chartType || 'horizontalBar';
}
public static queryCreationParams(i18n:I18nService, is_public:boolean) {
public static queryCreationParams(i18n:I18nService, isPublic:boolean):unknown {
return {
hidden: true,
public: is_public,
public: isPublic,
name: i18n.t('js.grid.widgets.work_packages_graph.title'),
showHierarchies: false,
_links: {

@ -38,7 +38,7 @@ module API
def initialize(
type:, name:, location: nil, required: true, has_default: false, writable: true,
attribute_group: nil, description: nil, current_user: nil
attribute_group: nil, description: nil, current_user: nil, deprecated: nil
)
@type = type
@name = name
@ -48,6 +48,7 @@ module API
@attribute_group = attribute_group
@location = derive_location(location)
@description = description
@deprecated = deprecated
super(nil, current_user: current_user)
end
@ -63,7 +64,8 @@ module API
:regular_expression,
:options,
:location,
:description
:description,
:deprecated
property :type, exec_context: :decorator
property :name, exec_context: :decorator
@ -74,6 +76,7 @@ module API
property :min_length, exec_context: :decorator
property :max_length, exec_context: :decorator
property :regular_expression, exec_context: :decorator
property :deprecated, exec_context: :decorator, render_nil: false
property :options, exec_context: :decorator
property :location, exec_context: :decorator, render_nil: false

@ -66,7 +66,8 @@ module API
regular_expression: nil,
options: {},
show_if: true,
description: nil)
description: nil,
deprecated: nil)
getter = ->(*) do
schema_property_getter(type,
name_source,
@ -79,7 +80,8 @@ module API
regular_expression,
options,
location,
description)
description,
deprecated)
end
schema_property(property,
@ -293,7 +295,8 @@ module API
regular_expression,
options,
location,
description)
description,
deprecated)
name = call_or_translate(name_source)
schema = ::API::Decorators::PropertySchemaRepresenter
.new(type: call_or_use(type),
@ -303,7 +306,8 @@ module API
required: call_or_use(required),
has_default: call_or_use(has_default),
writable: call_or_use(writable),
attribute_group: call_or_use(attribute_group))
attribute_group: call_or_use(attribute_group),
deprecated: deprecated)
schema.min_length = min_length
schema.max_length = max_length
schema.regular_expression = regular_expression

@ -111,9 +111,9 @@ module API
mount API::V3::Queries::UpdateFormAPI
patch do
update_query @query, request_body, current_user
end
patch &::API::V3::Utilities::Endpoints::Update
.new(model: Query)
.mount
params do
optional :valid_subset, type: Boolean
@ -129,7 +129,7 @@ module API
@query.valid_subset!
# We do not ignore invalid params provided by the client
# unless explicily required by valid_subset
# unless explicitly required by valid_subset
query_representer_response(@query, params, params.delete(:valid_subset))
end
@ -141,37 +141,22 @@ module API
status 204
end
patch :star do
authorize_by_policy(:star)
# Query name is not user-visible, but apparently used as CSS class. WTF.
# Normalizing the query name can result in conflicts and empty names in case all
# characters are filtered out. A random name doesn't have these problems.
query_menu_item = MenuItems::QueryMenuItem
.find_or_initialize_by(navigatable_id: @query.id) do |item|
item.name = SecureRandom.uuid
item.title = @query.name
end
query_menu_item.save!
@query.valid_subset!
query_representer_response(@query, {})
namespace :star do
patch &::API::V3::Utilities::Endpoints::Update
.new(model: Query,
params_modifier: ->(_params) {
{ starred: true }
})
.mount
end
patch :unstar do
authorize_by_policy(:unstar)
@query.valid_subset!
representer = query_representer_response(@query, {})
query_menu_item = @query.query_menu_item
return representer if @query.query_menu_item.nil?
query_menu_item.destroy
@query.reload
representer
namespace :unstar do
patch &::API::V3::Utilities::Endpoints::Update
.new(model: Query,
params_modifier: ->(_params) {
{ starred: false }
})
.mount
end
mount API::V3::Queries::Order::QueryOrderAPI

@ -77,7 +77,7 @@ module API
def update_query(query, request_body, current_user)
rep = representer.new query, current_user: current_user
query = rep.from_hash request_body
call = ::Queries::UpdateService.new(user: current_user).call query
call = ::Queries::UpdateService.new(model: query, user: current_user).call query
if call.success?
representer.new call.result, current_user: current_user, embed_links: true

@ -288,8 +288,12 @@ module API
exec_context: :decorator
property :display_sums, as: :sums
property :is_public, as: :public
property :hidden
property :public
# The property is deprecated and should be removed
# in the next major version.
property :hidden,
setter: ->(*) {} # ignored
# Timeline properties
property :timeline_visible
@ -322,8 +326,8 @@ module API
super(model, current_user: current_user, embed_links: embed_links)
end
self.to_eager_load = [:query_menu_item,
:user,
self.to_eager_load = [:user,
:views,
{ project: :work_package_custom_fields }]
def _type
@ -343,8 +347,10 @@ module API
filters_hash.each do |filter_attributes|
name = get_filter_name filter_attributes
if name && (filter = represented.filter_for name)
filter_representer = ::API::V3::Queries::Filters::QueryFilterInstanceRepresenter.new(filter)
if name
filter_class = Query.find_registered_filter(name) || ::Queries::Filters::NotExistingFilter
filter_representer = ::API::V3::Queries::Filters::QueryFilterInstanceRepresenter
.new(filter_class.create!(name: name))
filter = filter_representer.from_hash filter_attributes
represented.filters << filter

@ -107,37 +107,43 @@ module API
type: 'Boolean',
required: false,
writable: true,
has_default: true
has_default: true,
deprecated: true
schema :timeline_zoom_level,
type: 'String',
required: false,
writable: true,
has_default: true
has_default: true,
deprecated: true
schema :timeline_labels,
type: 'QueryTimelineLabels',
required: false,
writable: true,
has_default: true
has_default: true,
deprecated: true
schema :highlighting_mode,
type: 'String',
required: false,
writable: true,
has_default: true
has_default: true,
deprecated: true
schema :display_representation,
type: 'String',
required: false,
writable: true,
has_default: true
has_default: true,
deprecated: true
schema :show_hierarchies,
type: 'Boolean',
required: false,
writable: true,
has_default: true
has_default: true,
deprecated: true
schema :starred,
type: 'Boolean',
@ -149,7 +155,8 @@ module API
type: 'Boolean',
required: true,
writable: true,
has_default: true
has_default: true,
deprecated: true
schema :ordered_work_packages,
type: 'QueryOrder',

@ -70,7 +70,7 @@ module API
property :public,
getter: ->(*) {
query.is_public
query.public
}
property :starred,

@ -14,7 +14,7 @@ module ::Bim
def view_permissions?
return false unless user_allowed_on_view?(:view_ifc_models)
return false unless user_allowed_on_view?(:save_bcf_queries)
if model.query.is_public && !user_allowed_on_view?(:manage_public_bcf_queries)
if model.query.public && !user_allowed_on_view?(:manage_public_bcf_queries)
return false
end

@ -41,7 +41,7 @@ en:
timeline: false
sort_by: id
hidden: true
is_public: false
public: false
columns:
- type
- id

@ -47,11 +47,8 @@ module OpenProject::Bim::Patches::WorkPackageBoardSeederPatch
statuses.to_a.map do |status|
Query.new_default(project: project, user: admin).tap do |query|
# Hide the query in the main menu
query.hidden = true
# Make it public so that new members can see it too
query.is_public = true
query.public = true
query.name = status.name
# Set filter by this status

@ -18,7 +18,7 @@ FactoryBot.define do
callback(:after_build) do |board, evaluator| # this is also done after :create
query = evaluator.query || begin
Query.new_default(name: 'List 1', is_public: true, project: board.project).tap do |q|
Query.new_default(name: 'List 1', public: true, project: board.project).tap do |q|
q.sort_criteria = [[:manual_sorting, 'asc']]
q.add_filter(:manual_sort, 'ow', [])
q.save!
@ -48,7 +48,7 @@ FactoryBot.define do
callback(:after_build) do |board, evaluator| # this is also done after :create
evaluator.num_queries.times do |i|
query = Query.new_default(name: "List #{i + 1}", is_public: true, project: board.project).tap do |q|
query = Query.new_default(name: "List #{i + 1}", public: true, project: board.project).tap do |q|
q.sort_criteria = [[:manual_sorting, 'asc']]
q.add_filter(:manual_sort, 'ow', [])
q.save!
@ -79,7 +79,7 @@ FactoryBot.define do
callback(:after_build) do |board, evaluator| # this is also done after :create
evaluator.projects_columns.each do |project|
query = Query.new_default(name: project.name, project: board.project, is_public: true).tap do |q|
query = Query.new_default(name: project.name, project: board.project, public: true).tap do |q|
q.sort_criteria = [[:manual_sorting, 'asc']]
q.add_filter('only_subproject_id', '=', [project.id.to_s])
q.save!

@ -22,7 +22,7 @@ FactoryBot.define do
column_count { 4 }
callback(:after_build) do |dashboard|
query = FactoryBot.create(:query, project: dashboard.project, hidden: true, is_public: true)
query = FactoryBot.create(:query, project: dashboard.project, public: true)
widget = FactoryBot.build(:grid_widget,
identifier: 'work_packages_table',

@ -49,8 +49,7 @@ describe Grids::MyPage, type: :model do
let(:user) { FactoryBot.create(:user) }
let(:query) do
FactoryBot.create(:query,
user: user,
hidden: true)
user: user)
end
before do

@ -63,7 +63,7 @@ class Widget::Controls::SaveAs < Widget::Controls
if @options[:can_save_as_public]
box = content_tag :p, class: 'form--field -wide-label' do
label_tag(:query_is_public,
Query.human_attribute_name(:is_public),
Query.human_attribute_name(:public),
class: 'form--label -transparent') +
content_tag(:span,
class: 'form--field-container') do

@ -48,7 +48,7 @@ describe ::API::V3::Views::ViewsAPI,
shared_let(:public_query) do
FactoryBot.create(:query,
project: project,
is_public: true)
public: true)
end
let(:additional_setup) do

@ -36,7 +36,7 @@ describe Queries::UpdateContract do
let(:project) { FactoryBot.build_stubbed :project }
let(:query) do
FactoryBot.build_stubbed(:query, project: project, is_public: public, user: user)
FactoryBot.build_stubbed(:query, project: project, public: public, user: user)
end
let(:current_user) do

@ -43,7 +43,7 @@ shared_examples_for 'view contract' do |disabled_permission_checks|
let(:view_query) do
FactoryBot.build_stubbed(:query,
user: query_user,
is_public: query_public,
public: query_public,
project: query_project)
end
let(:permissions) { %i[view_work_packages save_queries] }

@ -49,14 +49,5 @@ FactoryBot.define do
end
end
end
factory :query_menu_item, class: 'MenuItems::QueryMenuItem' do
query
name { query.normalized_name }
title { query.name }
navigatable_id { query.id }
end
end
end

@ -33,18 +33,18 @@ FactoryBot.define do
sequence(:name) { |n| "Query #{n}" }
factory :public_query do
is_public { true }
public { true }
sequence(:name) { |n| "Public query #{n}" }
end
factory :private_query do
is_public { false }
public { false }
sequence(:name) { |n| "Private query #{n}" }
end
factory :global_query do
project { nil }
is_public { true }
public { true }
sequence(:name) { |n| "Global query #{n}" }
end

@ -53,20 +53,17 @@ RSpec.feature 'Query menu items', js: true do
context 'with identical names' do
let(:query_a) do
FactoryBot.create :query_with_view_work_packages_table,
is_public: true,
public: true,
name: 'some query.',
project: project
end
let(:query_b) do
FactoryBot.create :query_with_view_work_packages_table,
is_public: true,
public: true,
name: query_a.name,
project: project
end
let!(:menu_item_a) { FactoryBot.create :query_menu_item, query: query_a }
let!(:menu_item_b) { FactoryBot.create :query_menu_item, query: query_b }
it 'can be shown' do
visit_index_page(query_a)
@ -78,7 +75,7 @@ RSpec.feature 'Query menu items', js: true do
context 'with dots in their name' do
let(:query) do
FactoryBot.create :query_with_view_work_packages_table,
is_public: true,
public: true,
name: 'OP 3.0',
project: project
end
@ -103,21 +100,19 @@ RSpec.feature 'Query menu items', js: true do
describe 'renaming a menu item' do
let(:query_a) do
FactoryBot.create :query_with_view_work_packages_table,
is_public: true,
public: true,
name: 'bbbb',
project: project,
user: user
end
let(:query_b) do
FactoryBot.create :query_with_view_work_packages_table,
is_public: true,
public: true,
name: 'zzzz',
project: project,
user: user
end
let!(:menu_item_a) { FactoryBot.create :query_menu_item, query: query_a }
let!(:menu_item_b) { FactoryBot.create :query_menu_item, query: query_b }
let(:new_name) { 'aaaaa' }
before do

@ -42,7 +42,7 @@ describe 'Query selection', type: :feature do
let(:filters) { ::Components::WorkPackages::Filters.new }
let(:query) do
FactoryBot.build(:query, project: project, is_public: true).tap do |query|
FactoryBot.build(:query, project: project, public: true).tap do |query|
query.filters.clear
query.add_filter('assigned_to_id', '=', ['me'])
query.add_filter('done_ratio', '>=', [10])
@ -96,7 +96,7 @@ describe 'Query selection', type: :feature do
let(:query2) do
FactoryBot.create(:query_with_view_work_packages_table,
project: project,
is_public: true)
public: true)
end
before do

@ -73,7 +73,7 @@ describe 'Query menu item', js: true do
expect(page).to have_selector('.op-sidemenu--item-action', text: 'Some query name', wait: 20)
last_query = Query.last
expect(last_query.is_public).to be_truthy
expect(last_query.public).to be_truthy
end
it 'only saves a single query when saving through the title input (Regression #31095)' do

@ -31,8 +31,9 @@ require 'spec_helper'
describe ::API::V3::Queries::QueryRepresenter do
include ::API::V3::Utilities::PathHelper
let(:query) { FactoryBot.build_stubbed(:query, project: project) }
let(:unpersisted_query) { FactoryBot.build(:query, project: project, user: other_user) }
let(:query) { FactoryBot.build_stubbed(:query, project: project, views: views) }
let(:unpersisted_query) { FactoryBot.build(:query, project: project, user: other_user, views: views) }
let(:views) { [FactoryBot.build_stubbed(:view)] }
let(:project) { FactoryBot.build_stubbed(:project) }
let(:user) { instance_double('User', allowed_to_globally?: true, allowed_to?: true, admin: true, admin?: true, active?: true) }
let(:other_user) { FactoryBot.build_stubbed(:user) }
@ -572,20 +573,18 @@ describe ::API::V3::Queries::QueryRepresenter do
end
it_behaves_like 'property', :public do
let(:value) { query.is_public }
let(:value) { query.public }
end
describe 'hidden' do
context 'with the query being non hidden' do
context 'with the query having a view' do
it_behaves_like 'property', :hidden do
let(:value) { false }
end
end
context 'with the query being hidden' do
before do
query.hidden = true
end
context 'without the query having a view' do
let(:views) { [] }
it_behaves_like 'property', :hidden do
let(:value) { true }

@ -33,12 +33,11 @@ describe ::API::V3::Views::ViewRepresenter, 'rendering' do
subject(:generated) { representer.to_json }
let(:query) { FactoryBot.build_stubbed(:query, is_public: query_public, query_menu_item: query_menu_item) }
let(:query) { FactoryBot.build_stubbed(:query, public: query_public, starred: query_starred) }
let(:view) { FactoryBot.build_stubbed(:view_work_packages_table, query: query) }
let(:current_user) { FactoryBot.build_stubbed(:user) }
let(:query_public) { true }
# aka starred
let(:query_menu_item) { FactoryBot.build_stubbed(:query_menu_item) }
let(:query_starred) { true }
let(:embed_links) { false }
@ -85,7 +84,7 @@ describe ::API::V3::Views::ViewRepresenter, 'rendering' do
end
context 'without the query being starred' do
let(:query_menu_item) { nil }
let(:query_starred) { false }
it_behaves_like 'property', :starred do
let(:value) { false }

@ -1,53 +0,0 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2021 the OpenProject GmbH
#
# 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 COPYRIGHT and LICENSE files for more details.
#++
require 'spec_helper'
describe MenuItems::QueryMenuItem, type: :model do
let(:project) { FactoryBot.create :project, enabled_module_names: %w[activity] }
let(:query) { FactoryBot.create :query, project: project }
let(:another_query) { FactoryBot.create :query, project: project }
describe 'it should destroy all items when destroying' do
before(:each) do
query_item = FactoryBot.create(:query_menu_item,
query: query,
name: 'Query Item',
title: 'Query Item')
another_query_item = FactoryBot.create(:query_menu_item,
query: another_query,
name: 'Another Query Item',
title: 'Another Query Item')
end
it 'the associated query' do
query.destroy
expect(MenuItems::QueryMenuItem.where(navigatable_id: query.id)).to be_empty
end
end
end

@ -56,13 +56,13 @@ describe Queries::Scopes::Visible, type: :model do
let!(:public_query) do
FactoryBot.create(:query,
project: project,
is_public: true)
public: true)
end
let!(:public_query_lacking_permissions) do
FactoryBot.create(:query,
project: FactoryBot.create(:project,
members: { user => FactoryBot.create(:role, permissions: []) }),
is_public: true)
public: true)
end
let!(:global_user_query) do
FactoryBot.create(:query,

@ -63,10 +63,20 @@ describe Query, type: :model do
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)
context 'with a view' do
before do
FactoryBot.create(:view_work_packages_table, query: query)
end
it 'is false' do
expect(query.hidden).to eq(false)
end
end
context 'without a view' do
it 'is true' do
expect(query.hidden).to eq(true)
end
end
end

@ -50,7 +50,7 @@ describe QueryPolicy, type: :controller do
end
it 'is true if the query is public and another user views it' do
query.is_public = true
query.public = true
query.user = other_user
expect(subject.allowed?(query, :show)).to be_truthy
end
@ -60,7 +60,7 @@ describe QueryPolicy, type: :controller do
FactoryBot.build_stubbed(:query,
project: project,
user: user,
is_public: false)
public: false)
end
it 'is true if the query is private and the owner views it' do
@ -146,7 +146,7 @@ describe QueryPolicy, type: :controller do
global: project.nil?)
.and_return true
query.user = FactoryBot.build_stubbed(:user)
query.is_public = true
query.public = true
expect(subject.allowed?(query, action)).to be_truthy
end
@ -159,7 +159,7 @@ describe QueryPolicy, type: :controller do
global: project.nil?)
.and_return false
query.user = FactoryBot.build_stubbed(:user)
query.is_public = true
query.public = true
expect(subject.allowed?(query, action)).to be_falsy
end
@ -172,7 +172,7 @@ describe QueryPolicy, type: :controller do
global: project.nil?)
.and_return true
query.user = FactoryBot.build_stubbed(:user)
query.is_public = false
query.public = false
expect(subject.allowed?(query, action)).to be_falsy
end
@ -249,7 +249,7 @@ describe QueryPolicy, type: :controller do
global: project.nil?)
.and_return true
query.user = FactoryBot.build_stubbed(:user)
query.is_public = false
query.public = false
expect(subject.allowed?(query, :publicize)).to be_falsy
end
@ -277,7 +277,7 @@ describe QueryPolicy, type: :controller do
.and_return true
query.user = FactoryBot.build_stubbed(:user)
query.is_public = true
query.public = true
expect(subject.allowed?(query, :depublicize)).to be_truthy
end
@ -288,7 +288,7 @@ describe QueryPolicy, type: :controller do
project,
global: project.nil?)
.and_return true
query.is_public = false
query.public = false
expect(subject.allowed?(query, :depublicize)).to be_falsy
end
@ -327,7 +327,7 @@ describe QueryPolicy, type: :controller do
global: project.nil?)
.and_return true
query.is_public = true
query.public = true
expect(subject.allowed?(query, :reorder_work_packages)).to be_truthy
end
@ -338,7 +338,7 @@ describe QueryPolicy, type: :controller do
.and_return true
query.user = FactoryBot.build_stubbed(:user)
query.is_public = false
query.public = false
expect(subject.allowed?(query, :reorder_work_packages)).to be_falsey
end
@ -358,7 +358,7 @@ describe QueryPolicy, type: :controller do
global: project.nil?)
.and_return true
query.is_public = true
query.public = true
expect(subject.allowed?(query, :reorder_work_packages)).to be_truthy
end
@ -370,7 +370,7 @@ describe QueryPolicy, type: :controller do
global: project.nil?)
.and_return true
query.user = FactoryBot.build_stubbed(:user)
query.is_public = false
query.public = false
expect(subject.allowed?(query, :reorder_work_packages)).to be_falsey
end

@ -383,8 +383,9 @@ describe 'API v3 Query resource', type: :request, content_type: :json do
let(:permissions) { %i[view_work_packages manage_public_queries] }
context 'when unstarring a starred query' do
let(:query) { FactoryBot.create(:public_query, project: project, starred: true) }
before(:each) do
FactoryBot.create(:query_menu_item, query: query)
patch unstar_path
end

@ -44,7 +44,7 @@ describe "POST /api/v3/queries/form", type: :request do
FactoryBot.create(
:query,
name: "Existing Query",
is_public: false,
public: false,
project: project,
user: user
)

@ -42,7 +42,7 @@ describe "PATCH /api/v3/queries/:id", type: :request do
:global_query,
name: "A Query",
user: user,
is_public: false,
public: false,
show_hierarchies: false,
display_sums: false
)
@ -136,7 +136,7 @@ describe "PATCH /api/v3/queries/:id", type: :request do
expect(query.sort_criteria).to eq [["id", "desc"], ["assigned_to", "asc"]]
expect(query.columns.map(&:name)).to eq %i[id subject status assigned_to]
expect(query.project).to eq project
expect(query.is_public).to eq true
expect(query.public).to eq true
expect(query.display_sums).to eq false
expect(query.filters.size).to eq 1

@ -42,7 +42,7 @@ describe ::API::V3::Views::ViewsAPI,
shared_let(:private_user_query) do
FactoryBot.create(:query,
project: project,
is_public: false,
public: false,
user: permitted_user)
end

@ -46,7 +46,7 @@ describe ::API::V3::Views::ViewsAPI,
FactoryBot.create(:query,
user: permitted_user,
project: project,
is_public: false)
public: false)
end
shared_let(:user_private_project_view) do
FactoryBot.create(:view_work_packages_table,
@ -55,7 +55,7 @@ describe ::API::V3::Views::ViewsAPI,
shared_let(:other_user_private_project_query) do
FactoryBot.create(:query,
project: project,
is_public: false)
public: false)
end
shared_let(:other_user_private_project_view) do
FactoryBot.create(:view_work_packages_table,
@ -64,7 +64,7 @@ describe ::API::V3::Views::ViewsAPI,
shared_let(:user_public_project_query) do
FactoryBot.create(:query,
project: project,
is_public: true)
public: true)
end
shared_let(:user_public_project_view) do
FactoryBot.create(:view_work_packages_table,
@ -146,7 +146,7 @@ describe ::API::V3::Views::ViewsAPI,
FactoryBot.create(:query,
user: permitted_user,
project: project,
is_public: false)
public: false)
end
let(:user_private_project_team_planner_view) do

@ -44,7 +44,7 @@ describe ::API::V3::Views::ViewsAPI,
shared_let(:private_user_query) do
FactoryBot.create(:query,
project: project,
is_public: false,
public: false,
user: permitted_user)
end
shared_let(:view) do

@ -41,7 +41,8 @@ describe RootSeeder,
expect(Project.count).to eq 2
expect(WorkPackage.count).to eq 33
expect(Wiki.count).to eq 2
expect(Query.where.not(hidden: true).count).to eq 7
expect(Query.having_views.count).to eq 7
expect(View.where(type: 'work_packages_table').count).to eq 7
expect(Query.count).to eq 25
expect(Projects::Status.count).to eq 2
expect(Role.where(type: 'Role').count).to eq 5

@ -38,6 +38,7 @@ describe Projects::CopyService, 'integration', type: :model do
FactoryBot.create :work_package, project: source, subject: 'source wp locked', status: status_locked
end
shared_let(:source_query) { FactoryBot.create :query, project: source, name: 'My query' }
shared_let(:source_view) { FactoryBot.create :view_work_packages_table, query: source_query }
shared_let(:source_category) { FactoryBot.create :category, project: source, name: 'Stock management' }
shared_let(:source_version) { FactoryBot.create :version, project: source, name: 'Version A' }
shared_let(:source_wiki_page) { FactoryBot.create(:wiki_page_with_content, wiki: source.wiki) }
@ -103,6 +104,7 @@ describe Projects::CopyService, 'integration', type: :model do
expect(project_copy.wiki).to be_present
expect(project_copy.wiki.pages.count).to eq 2
expect(project_copy.queries.count).to eq 1
expect(project_copy.queries[0].views.count).to eq 1
expect(project_copy.versions.count).to eq 1
expect(project_copy.wiki.pages.root.content.text).to eq source_wiki_page.content.text
expect(project_copy.wiki.pages.leaves.first.content.text).to eq source_child_wiki_page.content.text
@ -275,9 +277,12 @@ describe Projects::CopyService, 'integration', type: :model do
describe 'valid queries' do
context 'with a filter' do
let!(:query) do
query = FactoryBot.build(:query, project: source)
query.add_filter('subject', '~', ['bogus'])
query.save!
FactoryBot.build(:query, project: source).tap do |q|
q.add_filter('subject', '~', ['bogus'])
q.save!
FactoryBot.create(:view_work_packages_table, query: q)
end
end
it 'produces a valid query in the new project' do
@ -289,11 +294,13 @@ describe Projects::CopyService, 'integration', type: :model do
context 'with a filter to be mapped' do
let!(:query) do
query = FactoryBot.build(:query, project: source)
query.add_filter('parent', '=', [source_wp.id.to_s])
# Not valid due to wp not visible
query.save!(validate: false)
query
FactoryBot.build(:query, project: source).tap do |q|
q.add_filter('parent', '=', [source_wp.id.to_s])
# Not valid due to wp not visible
q.save!(validate: false)
FactoryBot.create(:view_work_packages_table, query: q)
end
end
it 'produces a valid query that is mapped in the new project' do
@ -305,26 +312,34 @@ describe Projects::CopyService, 'integration', type: :model do
end
end
describe 'query menu items' do
let!(:query) do
query = FactoryBot.build(:query, project: source, name: 'Query with item')
describe 'views' do
let!(:query_with_view) do
query = FactoryBot.build(:query, project: source, name: 'Query with view')
query.add_filter('subject', '~', ['bogus'])
query.save!
MenuItems::QueryMenuItem.create(
navigatable_id: query.id,
name: 'some-uuid',
title: 'My query title'
)
FactoryBot.create(:view_work_packages_table, query: query)
query
end
it 'copies the menu item' do
let!(:query_without_view) do
query = FactoryBot.build(:query, project: source, name: 'Query without view')
query.add_filter('subject', '~', ['bogus'])
query.save!
query
end
it 'copies only the query with a view (non viewed queries will have to implement specific copy service)' do
expect(subject).to be_success
query = project_copy.queries.find_by(name: 'Query with item')
expect(query).to be_present
expect(query.query_menu_item.title).to eq('My query title')
copied_query_with_view = project_copy.queries.find_by(name: 'Query with view')
expect(copied_query_with_view).to be_present
expect(copied_query_with_view.views.length).to eq 1
expect(copied_query_with_view.views[0].type).to eq 'work_packages_table'
expect(project_copy.queries)
.not_to exist(name: 'Query without view')
end
end
@ -366,6 +381,8 @@ describe Projects::CopyService, 'integration', type: :model do
FactoryBot.create(:query, name: 'Manual query', user: current_user, project: source, show_hierarchies: false).tap do |q|
q.sort_criteria = [[:manual_sorting, 'asc']]
q.save!
FactoryBot.create(:view_work_packages_table, query: q)
end
end
@ -496,7 +513,7 @@ describe Projects::CopyService, 'integration', type: :model do
wp.add_watcher user
wp.save
user.lock!
user.locked!
source.work_packages << wp
end

@ -30,6 +30,7 @@
require 'spec_helper'
# rubocop:disable RSpec/NestedGroups
describe Projects::SetAttributesService, type: :model do
let(:user) { FactoryBot.build_stubbed(:user) }
let(:contract_class) do
@ -101,7 +102,7 @@ describe Projects::SetAttributesService, type: :model do
Project.new
end
context 'identifier default value' do
describe 'identifier default value' do
context 'with an identifier provided' do
let(:call_attributes) do
{
@ -127,7 +128,7 @@ describe Projects::SetAttributesService, type: :model do
end
end
context 'public default value', with_settings: { default_projects_public: true } do
describe 'public default value', with_settings: { default_projects_public: true } do
context 'with a value for is_public provided' do
let(:call_attributes) do
{
@ -149,7 +150,7 @@ describe Projects::SetAttributesService, type: :model do
end
end
context 'enabled_module_names default value', with_settings: { default_projects_modules: ['lorem', 'ipsum'] } do
describe 'enabled_module_names default value', with_settings: { default_projects_modules: ['lorem', 'ipsum'] } do
context 'with a value for enabled_module_names provided' do
let(:call_attributes) do
{
@ -182,13 +183,14 @@ describe Projects::SetAttributesService, type: :model do
end
end
context 'types default value' do
describe 'types default value' do
let(:other_types) do
[FactoryBot.build_stubbed(:type)]
end
let(:default_types) do
[FactoryBot.build_stubbed(:type)]
end
before do
allow(Type)
.to receive(:default)
@ -227,7 +229,7 @@ describe Projects::SetAttributesService, type: :model do
end
end
context 'project status' do
describe 'project status' do
context 'with a value provided' do
let(:call_attributes) do
{
@ -257,7 +259,7 @@ describe Projects::SetAttributesService, type: :model do
end
context 'for an existing project' do
context 'project status' do
describe 'project status' do
context 'with the project not having a status before' do
context 'with a value provided' do
let(:call_attributes) do
@ -350,3 +352,4 @@ describe Projects::SetAttributesService, type: :model do
end
end
end
# rubocop:enable RSpec/NestedGroups

@ -29,86 +29,8 @@
#++
require 'spec_helper'
require 'services/base_services/behaves_like_update_service'
describe Queries::UpdateService do
let(:query) { FactoryBot.create(:query, user: user) }
let(:menu_item) do
FactoryBot.create(:query_menu_item,
query: query)
end
let(:user) { FactoryBot.create(:admin) }
let(:instance) { described_class.new(user: user) }
describe "a query's menu item" do
before do
query
menu_item
end
context 'successful saving' do
before do
query.name = 'blubs'
end
it 'is renamed along with the query' do
instance.call(query)
expect(menu_item.reload.title).to eql 'blubs'
end
it 'is successful' do
expect(instance.call(query)).to be_success
end
end
context 'unsuccessful saving of the menu item' do
before do
# violating the validations
violating_menu_item = FactoryBot.build(:query_menu_item,
name: menu_item.name,
navigatable_id: menu_item.navigatable_id)
violating_menu_item.save(validate: false)
query.name = 'blubs'
end
it 'does not rename the menu item' do
instance.call(query)
expect(menu_item.reload.title).not_to eql 'blubs'
end
it 'is unsuccessful' do
expect(instance.call(query)).not_to be_success
end
it 'explains the error' do
expect(instance.call(query).errors['name']).to be_present
end
end
context 'unsuccessful saving of the query' do
before do
query.name = 'blubs'
# violating the validations
query.group_by = 'some bogus'
end
it 'does not rename the menu item' do
instance.call(query)
expect(menu_item.reload.title).not_to eql 'blubs'
end
it 'is unsuccessful' do
expect(instance.call(query)).not_to be_success
end
it 'explains the error' do
expect(instance.call(query).errors['group_by']).to be_present
end
end
end
it_behaves_like 'BaseServices update service'
end

@ -30,7 +30,7 @@
queries_001:
id: 1
project_id: 1
is_public: true
public: true
name: Multiple custom fields query
filters: |
---
@ -52,7 +52,7 @@ queries_001:
queries_002:
id: 2
project_id: 1
is_public: false
public: false
name: Private query for cookbook
filters: |
---
@ -70,7 +70,7 @@ queries_002:
queries_003:
id: 3
project_id:
is_public: false
public: false
name: Private query for all projects
filters: |
---
@ -84,7 +84,7 @@ queries_003:
queries_004:
id: 4
project_id:
is_public: true
public: true
name: Public query for all projects
filters: |
---
@ -98,7 +98,7 @@ queries_004:
queries_005:
id: 5
project_id:
is_public: true
public: true
name: Open issues by priority and type
filters: |
---
@ -118,7 +118,7 @@ queries_005:
queries_006:
id: 6
project_id:
is_public: true
public: true
name: Open issues grouped by type
filters: |
---
@ -137,7 +137,7 @@ queries_006:
queries_007:
id: 7
project_id: 2
is_public: true
public: true
name: Public query for project 2
filters: |
---
@ -151,7 +151,7 @@ queries_007:
queries_008:
id: 8
project_id: 2
is_public: false
public: false
name: Private query for project 2
filters: |
---
@ -165,7 +165,7 @@ queries_008:
queries_009:
id: 9
project_id:
is_public: true
public: true
name: Open issues grouped by list custom field
filters: |
---

Loading…
Cancel
Save