diff --git a/frontend/src/app/shared/components/fields/display/display-field.initializer.ts b/frontend/src/app/shared/components/fields/display/display-field.initializer.ts index 7b38b7ad9285..a1a84fd82f9e 100644 --- a/frontend/src/app/shared/components/fields/display/display-field.initializer.ts +++ b/frontend/src/app/shared/components/fields/display/display-field.initializer.ts @@ -107,6 +107,7 @@ export function initializeCoreDisplayFields(displayFieldService:DisplayFieldServ 'TimeEntriesActivity', 'Version', 'Category', + 'BacklogBucket', 'Sprint', 'CustomField::Hierarchy::Item', 'CustomOption', diff --git a/frontend/src/app/shared/components/fields/edit/edit-field.initializer.ts b/frontend/src/app/shared/components/fields/edit/edit-field.initializer.ts index 118b58bdb885..a8c953547e3a 100644 --- a/frontend/src/app/shared/components/fields/edit/edit-field.initializer.ts +++ b/frontend/src/app/shared/components/fields/edit/edit-field.initializer.ts @@ -100,6 +100,7 @@ export function initializeCoreEditFields(editFieldService:EditFieldService, sele 'Version', 'TimeEntriesActivity', 'Category', + 'BacklogBucket', 'Sprint', 'CustomOption', 'CustomField::Hierarchy::Item', diff --git a/modules/backlogs/app/models/queries/backlog_buckets.rb b/modules/backlogs/app/models/queries/backlog_buckets.rb new file mode 100644 index 000000000000..62cf00316f20 --- /dev/null +++ b/modules/backlogs/app/models/queries/backlog_buckets.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 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. +# ++ + +module Queries::BacklogBuckets + ::Queries::Register.register(BacklogBucketQuery) do + order Orders::DefaultOrder + end +end diff --git a/modules/backlogs/app/models/queries/backlog_buckets/backlog_bucket_query.rb b/modules/backlogs/app/models/queries/backlog_buckets/backlog_bucket_query.rb new file mode 100644 index 000000000000..33e2f2289db3 --- /dev/null +++ b/modules/backlogs/app/models/queries/backlog_buckets/backlog_bucket_query.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 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. +# ++ + +module Queries + module BacklogBuckets + class BacklogBucketQuery + include ::Queries::BaseQuery + include ::Queries::UnpersistedQuery + + def self.model + ::BacklogBucket + end + + def default_scope + ::BacklogBucket.visible(User.current) + end + end + end +end diff --git a/modules/backlogs/app/models/queries/backlog_buckets/orders/default_order.rb b/modules/backlogs/app/models/queries/backlog_buckets/orders/default_order.rb new file mode 100644 index 000000000000..30311b27dd43 --- /dev/null +++ b/modules/backlogs/app/models/queries/backlog_buckets/orders/default_order.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 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. +# ++ + +class Queries::BacklogBuckets::Orders::DefaultOrder < Queries::Orders::Base + self.model = BacklogBucket + + def self.key + /\A(id|name)\z/ + end +end diff --git a/modules/backlogs/lib/api/v3/backlog_buckets/backlog_bucket_collection_representer.rb b/modules/backlogs/lib/api/v3/backlog_buckets/backlog_bucket_collection_representer.rb new file mode 100644 index 000000000000..23724dad31ab --- /dev/null +++ b/modules/backlogs/lib/api/v3/backlog_buckets/backlog_bucket_collection_representer.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 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. +# ++ + +module API + module V3 + module BacklogBuckets + class BacklogBucketCollectionRepresenter < ::API::Decorators::OffsetPaginatedCollection + end + end + end +end diff --git a/modules/backlogs/lib/api/v3/backlog_buckets/backlog_bucket_representer.rb b/modules/backlogs/lib/api/v3/backlog_buckets/backlog_bucket_representer.rb new file mode 100644 index 000000000000..ca29a4d75e02 --- /dev/null +++ b/modules/backlogs/lib/api/v3/backlog_buckets/backlog_bucket_representer.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 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 "roar/decorator" +require "roar/json/hal" + +module API + module V3 + module BacklogBuckets + class BacklogBucketRepresenter < ::API::Decorators::Single + include API::Decorators::LinkedResource + include API::V3::Workspaces::LinkedResource + include API::Decorators::DateProperty + + self_link + + associated_project as: :definingWorkspace + + property :id, + render_nil: true + + property :name, + render_nil: true + + date_time_property :created_at + date_time_property :updated_at + + def _type + "BacklogBucket" + end + end + end + end +end diff --git a/modules/backlogs/lib/api/v3/backlog_buckets/backlog_buckets_api.rb b/modules/backlogs/lib/api/v3/backlog_buckets/backlog_buckets_api.rb new file mode 100644 index 000000000000..c7621343206f --- /dev/null +++ b/modules/backlogs/lib/api/v3/backlog_buckets/backlog_buckets_api.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 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. +#++ + +module API + module V3 + module BacklogBuckets + class BacklogBucketsAPI < ::API::OpenProjectAPI + resources :backlog_buckets do + get &::API::V3::Utilities::Endpoints::Index + .new(model: BacklogBucket) + .mount + + route_param :id, type: Integer, desc: "Backlog Bucket ID" do + after_validation do + @backlog_bucket = BacklogBucket.visible(current_user).find(params[:id]) + end + + get &::API::V3::Utilities::Endpoints::Show.new(model: BacklogBucket).mount + end + end + end + end + end +end diff --git a/modules/backlogs/lib/api/v3/backlog_buckets/backlog_buckets_by_project_api.rb b/modules/backlogs/lib/api/v3/backlog_buckets/backlog_buckets_by_project_api.rb new file mode 100644 index 000000000000..a926b9aaccbb --- /dev/null +++ b/modules/backlogs/lib/api/v3/backlog_buckets/backlog_buckets_by_project_api.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 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. +# ++ + +module API + module V3 + module BacklogBuckets + class BacklogBucketsByProjectAPI < ::API::OpenProjectAPI + resources :backlog_buckets do + after_validation do + authorize_in_project(:view_sprints, project: @project) + end + + get &::API::V3::Utilities::Endpoints::Index + .new( + model: BacklogBucket, + scope: -> { BacklogBucket.where(project: @project).visible.order_alphabetically } + ) + .mount + end + end + end + end +end diff --git a/modules/backlogs/lib/open_project/backlogs/engine.rb b/modules/backlogs/lib/open_project/backlogs/engine.rb index 85f0baa0baae..db276976a9fe 100644 --- a/modules/backlogs/lib/open_project/backlogs/engine.rb +++ b/modules/backlogs/lib/open_project/backlogs/engine.rb @@ -173,12 +173,26 @@ def self.settings "#{root}/projects/#{id}/sprints" end + add_api_path :backlog_buckets do + "#{root}/backlog_buckets" + end + + add_api_path :backlog_bucket do |id| + "#{root}/backlog_buckets/#{id}" + end + + add_api_path :project_backlog_buckets do |id| + "#{root}/projects/#{id}/backlog_buckets" + end + add_api_endpoint "API::V3::Root" do mount ::API::V3::Sprints::SprintsAPI + mount ::API::V3::BacklogBuckets::BacklogBucketsAPI end add_api_endpoint "API::V3::Projects::ProjectsAPI", :id do mount ::API::V3::Sprints::SprintsByProjectAPI + mount ::API::V3::BacklogBuckets::BacklogBucketsByProjectAPI end initializer "openproject_backlogs.event_subscriptions" do @@ -220,13 +234,14 @@ def self.settings end config.to_prepare do - %i[position story_points sprint].each do |attribute| + %i[position story_points sprint backlog_bucket].each do |attribute| ::Type.add_constraint attribute, ->(_type, project: nil) { project.nil? || project.backlogs_enabled? } end ::Type.add_default_mapping(:estimates_and_progress, :story_points) ::Type.add_default_mapping(:other, :position) ::Type.add_default_mapping(:details, :sprint) + ::Type.add_default_mapping(:details, :backlog_bucket) ::Queries::Register.register(::Query) do filter Queries::WorkPackages::Filter::SprintFilter diff --git a/modules/backlogs/lib/open_project/backlogs/patches/api/work_package_representer.rb b/modules/backlogs/lib/open_project/backlogs/patches/api/work_package_representer.rb index 6b9783ce1f35..3dc41979d00d 100644 --- a/modules/backlogs/lib/open_project/backlogs/patches/api/work_package_representer.rb +++ b/modules/backlogs/lib/open_project/backlogs/patches/api/work_package_representer.rb @@ -48,8 +48,7 @@ def extension # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity resource :sprint, link_cache_if: ->(*) { - represented.project.present? && - current_user.allowed_in_project?(:view_sprints, represented.project) + current_user.allowed_in_project?(:view_sprints, represented.project) }, link: ->(*) { if represented.sprint.present? @@ -65,13 +64,37 @@ def extension # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity }, getter: ->(*) do if embed_links && - represented.project.present? && represented.sprint.present? && current_user.allowed_in_project?(:view_sprints, represented.project) ::API::V3::Sprints::SprintRepresenter.create(represented.sprint, current_user:) end end, setter: associated_resource_default_setter(:sprint, :sprint, :sprint) + + resource :backlog_bucket, + link_cache_if: ->(*) { + current_user.allowed_in_project?(:view_sprints, represented.project) + }, + link: ->(*) { + if represented.backlog_bucket.present? + { + href: api_v3_paths.backlog_bucket(represented.backlog_bucket_id), + title: represented.backlog_bucket.name + } + else + { + href: nil + } + end + }, + getter: ->(*) do + if embed_links && + represented.backlog_bucket.present? && + current_user.allowed_in_project?(:view_sprints, represented.project) + ::API::V3::BacklogBuckets::BacklogBucketRepresenter.create(represented.backlog_bucket, current_user:) + end + end, + setter: associated_resource_default_setter(:backlog_bucket, :backlog_bucket, :backlog_bucket) end end end diff --git a/modules/backlogs/lib/open_project/backlogs/patches/api/work_package_schema_representer.rb b/modules/backlogs/lib/open_project/backlogs/patches/api/work_package_schema_representer.rb index 813790bd4d78..6359c6c02bbc 100644 --- a/modules/backlogs/lib/open_project/backlogs/patches/api/work_package_schema_representer.rb +++ b/modules/backlogs/lib/open_project/backlogs/patches/api/work_package_schema_representer.rb @@ -63,8 +63,23 @@ def extension [{ status: { operator: "!", values: [Sprint.statuses["completed"]] } }] )) + query = "filters=#{filters}&pageSize=-1" - "#{api_v3_paths.project_sprints(represented.project_id)}?filters=#{filters}&pageSize=-1" + "#{api_v3_paths.project_sprints(represented.project_id)}?#{query}" + } + + schema_with_allowed_link :backlog_bucket, + has_default: false, + required: false, + show_if: ->(*) { + current_user.allowed_in_project?(:view_sprints, represented.project) && + backlogs_constraint_passed?(:backlog_bucket) + }, + href_callback: ->(*) { + filters = CGI.escape(JSON.dump([])) + query = "filters=#{filters}&pageSize=-1" + + "#{api_v3_paths.project_backlog_buckets(represented.project_id)}?#{query}" } define_method :backlogs_constraint_passed? do |attribute| diff --git a/modules/backlogs/spec/features/work_packages/backlog_buckets_on_wp_view_spec.rb b/modules/backlogs/spec/features/work_packages/backlog_buckets_on_wp_view_spec.rb new file mode 100644 index 000000000000..1f07519b0b31 --- /dev/null +++ b/modules/backlogs/spec/features/work_packages/backlog_buckets_on_wp_view_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 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" + +RSpec.describe "Backlog bucket displayed and selectable on work package view", :js do + include Components::Autocompleter::NgSelectAutocompleteHelpers + + shared_let(:project) { create(:project) } + shared_let(:other_project) { create(:project) } + shared_let(:backlog_bucket) { create(:backlog_bucket, project:, name: "Current bucket") } + shared_let(:another_bucket) { create(:backlog_bucket, project:, name: "Another bucket") } + shared_let(:other_project_bucket) { create(:backlog_bucket, project: other_project, name: "Other project bucket") } + shared_let(:work_package) { create(:work_package, project:, backlog_bucket:) } + + let(:permissions) { %i(view_work_packages view_sprints manage_sprint_items) } + let(:wp_page) { Pages::FullWorkPackage.new(work_package) } + + current_user { create(:user, member_with_permissions: { project => permissions }) } + + it "shows the backlog bucket and allows changing it" do + wp_page.visit! + + wp_page.expect_attributes backlog_bucket: backlog_bucket.name + + field = wp_page.work_package_field(:backlog_bucket) + field.activate! + + expect_no_ng_option(field, other_project_bucket.name) + + field.autocomplete(another_bucket.name, select: true) + + wp_page.expect_and_dismiss_toaster message: I18n.t(:notice_successful_update) + + # Ensure the change was persisted: + wp_page.visit! + wp_page.expect_attributes backlog_bucket: another_bucket.name + end + + context "when lacking the permission to see sprints" do + let(:permissions) { %i(view_work_packages) } + + it "does not show a backlog bucket property" do + wp_page.visit! + + wp_page.expect_no_attribute "Backlog bucket" + end + end +end diff --git a/modules/backlogs/spec/lib/api/v3/backlog_buckets/backlog_bucket_representer_rendering_spec.rb b/modules/backlogs/spec/lib/api/v3/backlog_buckets/backlog_bucket_representer_rendering_spec.rb new file mode 100644 index 000000000000..9389f2279505 --- /dev/null +++ b/modules/backlogs/spec/lib/api/v3/backlog_buckets/backlog_bucket_representer_rendering_spec.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 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" + +RSpec.describe API::V3::BacklogBuckets::BacklogBucketRepresenter, "rendering" do + include API::V3::Utilities::PathHelper + + let(:workspace) { build_stubbed(:project) } + let(:backlog_bucket) { build_stubbed(:backlog_bucket, name: "My Bucket", project: workspace) } + let(:current_user) { build_stubbed(:user) } + let(:embed_links) { true } + let(:representer) { described_class.create(backlog_bucket, current_user:, embed_links:) } + + subject(:generated) { representer.to_json } + + it { is_expected.to include_json("BacklogBucket".to_json).at_path("_type") } + + describe "links" do + it { is_expected.to have_json_type(Object).at_path("_links") } + + describe "self" do + it_behaves_like "has a titled link" do + let(:link) { "self" } + let(:href) { api_v3_paths.backlog_bucket(backlog_bucket.id) } + let(:title) { backlog_bucket.name } + end + end + + describe "definingWorkspace" do + it_behaves_like "has workspace linked" do + let(:link) { "definingWorkspace" } + end + end + end + + describe "properties" do + describe "_type" do + it_behaves_like "property", :_type do + let(:value) { "BacklogBucket" } + end + end + + describe "id" do + it_behaves_like "property", :id do + let(:value) { backlog_bucket.id } + end + end + + describe "name" do + it_behaves_like "property", :name do + let(:value) { backlog_bucket.name } + end + end + + describe "createdAt" do + it_behaves_like "has UTC ISO 8601 date and time" do + let(:date) { backlog_bucket.created_at } + let(:json_path) { "createdAt" } + end + end + + describe "updatedAt" do + it_behaves_like "has UTC ISO 8601 date and time" do + let(:date) { backlog_bucket.updated_at } + let(:json_path) { "updatedAt" } + end + end + end + + describe "embedded" do + it_behaves_like "has workspace embedded" do + let(:embedded_path) { "_embedded/definingWorkspace" } + end + end +end diff --git a/modules/backlogs/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_spec.rb b/modules/backlogs/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_spec.rb index fab1ec928092..4c6d445cda40 100644 --- a/modules/backlogs/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_spec.rb +++ b/modules/backlogs/spec/lib/api/v3/work_packages/schema/work_package_schema_representer_spec.rb @@ -44,7 +44,7 @@ allow(p).to receive(:backlogs_enabled?).and_return(backlogs_enabled) end end - let(:work_package_type) { build_stubbed(:type, attribute_groups: [["Agile", %w[position sprint story_points]]]) } + let(:work_package_type) { build_stubbed(:type, attribute_groups: [["Agile", %w[position sprint story_points backlog_bucket]]]) } let(:work_package) { build_stubbed(:work_package, type: work_package_type) } let(:current_user) { build_stubbed(:user) } @@ -135,10 +135,46 @@ end end + describe "backlogBucket" do + let(:path) { "backlogBucket" } + + it_behaves_like "has basic schema properties" do + let(:type) { "BacklogBucket" } + let(:name) { I18n.t("activerecord.attributes.work_package.backlog_bucket") } + let(:required) { false } + let(:writable) { true } + let(:location) { "_links" } + end + + it_behaves_like "links to allowed values via collection link" do + let(:href) { "#{api_v3_paths.project_backlog_buckets(project.id)}?filters=%5B%5D&pageSize=-1" } + end + + context "when lacking permission to see the sprints (or if backlogs is disabled)" do + let(:permissions) { %i(view_work_packages edit_work_packages) } + + it "has no reference to the backlog bucket" do + expect(subject).not_to have_json_path(path) + end + end + + context "when lacking permission to set the backlog bucket" do + let(:permissions) { %i(view_work_packages edit_work_packages view_sprints) } + + it_behaves_like "has basic schema properties" do + let(:type) { "BacklogBucket" } + let(:name) { I18n.t("activerecord.attributes.work_package.backlog_bucket") } + let(:required) { false } + let(:writable) { false } + let(:location) { "_links" } + end + end + end + describe "attribute_groups" do context "with backlogs enabled" do it "has backlogs properties listed in the right group" do - expect(subject).to be_json_eql(%w[position sprint storyPoints]) + expect(subject).to be_json_eql(%w[position sprint storyPoints backlogBucket]) .at_path("_attributeGroups/0/attributes") end end @@ -147,7 +183,7 @@ let(:permissions) { %i(view_work_packages edit_work_packages) } it "has backlogs properties listed in the right group" do - expect(subject).to be_json_eql(%w[position sprint storyPoints]) + expect(subject).to be_json_eql(%w[position sprint storyPoints backlogBucket]) .at_path("_attributeGroups/0/attributes") end end diff --git a/modules/backlogs/spec/lib/api/v3/work_packages/work_package_representer_rendering_spec.rb b/modules/backlogs/spec/lib/api/v3/work_packages/work_package_representer_rendering_spec.rb index b6af7bd445bf..93aa1c13b9f1 100644 --- a/modules/backlogs/spec/lib/api/v3/work_packages/work_package_representer_rendering_spec.rb +++ b/modules/backlogs/spec/lib/api/v3/work_packages/work_package_representer_rendering_spec.rb @@ -41,7 +41,8 @@ project:, story_points:, position:, - sprint:) + sprint:, + backlog_bucket:) end let(:type) { build_stubbed(:type) } let(:enabled_module_names) { %w[backlogs] } @@ -52,6 +53,7 @@ let(:story_points) { 23 } let(:position) { 123 } let(:sprint) { build_stubbed(:sprint) } + let(:backlog_bucket) { build_stubbed(:backlog_bucket) } let(:embed_links) { true } let(:representer) do described_class.create(work_package, current_user:, embed_links:) @@ -117,6 +119,42 @@ it_behaves_like "has no link" end + + context "when the work package has no backlog bucket assigned" do + let(:sprint) { nil } + + it_behaves_like "has an empty link" do + let(:link) { "sprint" } + end + end + end + + describe "backlogBucket" do + let(:link) { "backlogBucket" } + let(:href) { api_v3_paths.backlog_bucket(backlog_bucket.id) } + let(:title) { backlog_bucket.name } + + it_behaves_like "has a titled link" + + context "when lacking the permission" do + let(:permissions) { [] } + + it_behaves_like "has no link" + end + + context "when the project is empty (because the work package is not persisted yet)" do + let(:project) { nil } + + it_behaves_like "has no link" + end + + context "when the work package has no backlog bucket assigned" do + let(:backlog_bucket) { nil } + + it_behaves_like "has an empty link" do + let(:link) { "backlogBucket" } + end + end end describe "update links" do @@ -149,6 +187,32 @@ it_behaves_like "has the resource not embedded" end + + context "when the work package has no sprint assigned" do + let(:sprint) { nil } + + it_behaves_like "has the resource not embedded" + end + end + + describe "backlogBucket" do + let(:embedded_path) { "_embedded/backlogBucket" } + let(:embedded_resource) { backlog_bucket } + let(:embedded_resource_type) { "BacklogBucket" } + + it_behaves_like "has the resource embedded" + + context "when lacking the permission" do + let(:permissions) { [] } + + it_behaves_like "has the resource not embedded" + end + + context "when the work package has no backlog bucket assigned" do + let(:backlog_bucket) { nil } + + it_behaves_like "has the resource not embedded" + end end end end diff --git a/modules/backlogs/spec/requests/api/v3/backlog_buckets/index_resource_spec.rb b/modules/backlogs/spec/requests/api/v3/backlog_buckets/index_resource_spec.rb new file mode 100644 index 000000000000..a3ccc243e6fd --- /dev/null +++ b/modules/backlogs/spec/requests/api/v3/backlog_buckets/index_resource_spec.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 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" +require "rack/test" + +RSpec.describe "API v3 BacklogBucket resource", content_type: :json do + include Rack::Test::Methods + include API::V3::Utilities::PathHelper + + shared_let(:project) { create(:project, public: false) } + shared_let(:other_project) { create(:project, public: false) } + shared_let(:project_without_permission) { create(:project, public: false) } + + shared_let(:bucket) { create(:backlog_bucket, project:) } + shared_let(:other_bucket) { create(:backlog_bucket, project: other_project) } + shared_let(:bucket_without_permission) { create(:backlog_bucket, project: project_without_permission) } + + let(:permissions) { %i[view_sprints] } + + current_user do + create(:user, + member_with_permissions: { + project => permissions, + other_project => permissions + }) + end + + describe "GET /api/v3/backlog_buckets" do + let(:get_path) { api_v3_paths.path_for(:backlog_buckets, page_size:, offset:) } + let(:page_size) { nil } + let(:offset) { nil } + + before { get get_path } + + context "for a user with view_sprints permission" do + it_behaves_like "API V3 collection response", 2, 2, "BacklogBucket" do + let(:elements) { [other_bucket, bucket] } + end + end + + context "for a user without view_sprints permission" do + let(:permissions) { [] } + + it_behaves_like "API V3 collection response", 0, 0, "BacklogBucket" + end + + context "for an anonymous user" do + let(:current_user) { User.anonymous } + + it_behaves_like "unauthenticated access" + end + + context "with pagination" do + let(:page_size) { 1 } + let(:offset) { 2 } + + it_behaves_like "API V3 collection response", 2, 1, "BacklogBucket" do + let(:elements) { [bucket] } + end + end + end +end diff --git a/modules/backlogs/spec/requests/api/v3/backlog_buckets/project_index_resource_spec.rb b/modules/backlogs/spec/requests/api/v3/backlog_buckets/project_index_resource_spec.rb new file mode 100644 index 000000000000..60a2e3651b06 --- /dev/null +++ b/modules/backlogs/spec/requests/api/v3/backlog_buckets/project_index_resource_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 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" + +RSpec.describe "API v3 BacklogBucket resource on project", content_type: :json do + include Rack::Test::Methods + include API::V3::Utilities::PathHelper + + shared_let(:project) { create(:project, public: false) } + shared_let(:other_project) { create(:project, public: false) } + + shared_let(:bucket) { create(:backlog_bucket, project:) } + shared_let(:other_bucket) { create(:backlog_bucket, project: other_project) } + + let(:permissions) { %i[view_sprints] } + + current_user do + create(:user, member_with_permissions: { project => permissions }) + end + + describe "GET /api/v3/projects/:id/backlog_buckets" do + let(:get_path) { api_v3_paths.project_backlog_buckets(project.id) } + + before { get get_path } + + context "for a user with view_sprints permission" do + it_behaves_like "API V3 collection response", 1, 1, "BacklogBucket" do + let(:elements) { [bucket] } + end + end + + context "for a user without view_sprints permission" do + let(:permissions) { [] } + + it_behaves_like "unauthorized access" + end + + context "for a user being not a project member at all" do + let(:get_path) { api_v3_paths.project_backlog_buckets(other_project.id) } + + it_behaves_like "not found" + end + end +end diff --git a/modules/backlogs/spec/requests/api/v3/backlog_buckets/show_resource_spec.rb b/modules/backlogs/spec/requests/api/v3/backlog_buckets/show_resource_spec.rb new file mode 100644 index 000000000000..4b03f2c04b08 --- /dev/null +++ b/modules/backlogs/spec/requests/api/v3/backlog_buckets/show_resource_spec.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 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 "rack/test" +require "spec_helper" + +RSpec.describe "API v3 BacklogBucket resource", content_type: :json do + include Rack::Test::Methods + include API::V3::Utilities::PathHelper + + shared_let(:project) { create(:project, public: false) } + shared_let(:bucket) { create(:backlog_bucket, project:) } + + let(:permissions) { %i[view_sprints] } + + current_user do + create(:user, member_with_permissions: { project => permissions }) + end + + describe "GET /api/v3/backlog_buckets/:id" do + let(:get_path) { api_v3_paths.backlog_bucket(bucket.id) } + + before { get get_path } + + context "for a user with view_sprints permission" do + it_behaves_like "successful response", 200, "BacklogBucket" + end + + context "for a user without view_sprints permission" do + let(:permissions) { [] } + + it_behaves_like "not found" + end + + context "for an anonymous user" do + let(:current_user) { User.anonymous } + + it_behaves_like "unauthenticated access" + end + + context "for a bucket that does not exist" do + let(:get_path) { api_v3_paths.backlog_bucket(0) } + + it_behaves_like "not found" + end + end +end diff --git a/modules/backlogs/spec/requests/api/v3/sprints/project_index_resource_spec.rb b/modules/backlogs/spec/requests/api/v3/sprints/project_index_resource_spec.rb index 2c5d4223a3eb..67786a84acd5 100644 --- a/modules/backlogs/spec/requests/api/v3/sprints/project_index_resource_spec.rb +++ b/modules/backlogs/spec/requests/api/v3/sprints/project_index_resource_spec.rb @@ -71,5 +71,11 @@ it_behaves_like "unauthorized access" end + + context "for a user being not a project member at all" do + let(:get_path) { api_v3_paths.project_sprints(project_without_permission.id) } + + it_behaves_like "not found" + end end end diff --git a/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb b/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb index 3e57cfa4c908..17373fc2b530 100644 --- a/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb +++ b/spec/models/work_packages/pdf_export/work_package_to_pdf_spec.rb @@ -249,6 +249,7 @@ "Details", "Priority", "Normal", *(work_package.sprint.present? ? ["Sprint", work_package.sprint] : ["Sprint"]), + *(work_package.backlog_bucket.present? ? ["Backlog Bucket", work_package.backlog_bucket] : ["Backlog Bucket"]), "Version", work_package.version, "Category", work_package.category, "Project phase", diff --git a/spec/support/edit_fields/edit_field.rb b/spec/support/edit_fields/edit_field.rb index 0fd11b4d7a57..74b66ca2db8b 100644 --- a/spec/support/edit_fields/edit_field.rb +++ b/spec/support/edit_fields/edit_field.rb @@ -33,7 +33,7 @@ def initialize(context, @field_type = derive_field_type @create_form = create_form - @selector = selector || ".inline-edit--container.#{property_name}" + @selector = selector || ".inline-edit--container.#{@property_name.camelize(:lower)}" end def create_form? @@ -327,6 +327,8 @@ def derive_field_type "activity-autocompleter" when :sprint "sprint-autocompleter" + when :backlog_bucket + "backlog-bucket-autocompleter" else "input" end diff --git a/spec/support/mocked_permission_helper.rb b/spec/support/mocked_permission_helper.rb index 47bf733caf7d..b1a13adc8402 100644 --- a/spec/support/mocked_permission_helper.rb +++ b/spec/support/mocked_permission_helper.rb @@ -135,7 +135,7 @@ def mock_permissions_for(user) # rubocop:disable Metrics/PerceivedComplexity next true if permission_mock.admin_allowed_to?(permissions) permission_names = permissions.map(&:name) - projects.all? do |project| + projects.any? && projects.all? do |project| permission_mock.permitted_entities[project].intersect?(permission_names) end end diff --git a/spec/support_spec/mocked_permission_helper_spec.rb b/spec/support_spec/mocked_permission_helper_spec.rb index cd8f2b5a871a..12603e67f739 100644 --- a/spec/support_spec/mocked_permission_helper_spec.rb +++ b/spec/support_spec/mocked_permission_helper_spec.rb @@ -187,7 +187,7 @@ end it "allows the project permission when querying with controller and action hash" do - expect(user).to be_allowed_in_project({ controller: "work_packages", action: "index", project_id: project.id }, nil) + expect(user).to be_allowed_in_project({ controller: "work_packages", action: "index", project_id: project.id }, project) expect(user).to be_allowed_in_any_project({ controller: "work_packages", action: "index" }) end