add a sprint GET end point to v3

This commit is contained in:
ulferts
2026-02-26 17:57:43 +01:00
parent 338bb59e6c
commit 993b3e53f0
11 changed files with 525 additions and 1 deletions
+1
View File
@@ -5706,6 +5706,7 @@ en:
project: Undisclosed - The project is invisible because of lacking permissions.
ancestor: Undisclosed - The ancestor is invisible because of lacking permissions.
definingProject: Undisclosed - The project is invisible because of lacking permissions.
definingWorkspace: Undisclosed - The workspace is invisible because of lacking permissions.
doorkeeper:
pre_authorization:
+1 -1
View File
@@ -70,7 +70,7 @@ module API
representer: ::API::V3::Projects::ProjectRepresenter,
skip_render:,
link: ::API::V3::Workspaces::WorkspaceRepresenterFactory
.create_link_lambda(name),
.create_link_lambda(name, property_name: as),
setter: ::API::V3::Workspaces::WorkspaceRepresenterFactory
.create_setter_lambda(name)
}
@@ -76,6 +76,10 @@ module Agile
validate :validate_only_one_active_sprint_per_project
include ::Scopes::Scoped
scopes :visible
# TODO: validate sharing is set to an allowed value, e.g. only admins may share systemwide (#71374, #71253)
# TODO: implement sharing logic once it has been defined (#71374)
@@ -0,0 +1,45 @@
# 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 Agile::Sprints::Scopes
module Visible
extend ActiveSupport::Concern
class_methods do
# Returns all sprints the user is allowed to see.
# A sprint is visible if the user has the :view_sprints permission
# in the project the sprint belongs to.
def visible(user = User.current)
joins(:project)
.merge(Project.allowed_to(user, :view_sprints))
end
end
end
end
+4
View File
@@ -40,6 +40,10 @@ en:
goal: "Sprint goal"
name: "Sprint name"
sharing: "Sharing"
statuses:
in_planning: "In planning"
active: "Active"
completed: "Completed"
sprint:
duration: "Sprint duration"
work_package:
@@ -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 "roar/decorator"
require "roar/json/hal"
module API
module V3
module Sprints
class SprintRepresenter < ::API::Decorators::Single
include API::Decorators::LinkedResource
include API::V3::Workspaces::LinkedResource
include API::Decorators::DateProperty
self_link
link :status do
{
href: "#{::API::V3::URN_PREFIX}sprints:status:#{represented.status}",
title: I18n.t("activerecord.attributes.agile/sprint.statuses.#{represented.status}")
}
end
associated_project as: :definingWorkspace
property :id,
render_nil: true
property :name,
render_nil: true
date_property :start_date
date_property :finish_date
date_time_property :created_at
date_time_property :updated_at
def _type
"Sprint"
end
end
end
end
end
@@ -0,0 +1,49 @@
# 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 Sprints
class SprintsAPI < ::API::OpenProjectAPI
resources :sprints do
route_param :id, type: Integer, desc: "Sprint ID" do
after_validation do
guard_feature_flag(:scrum_projects)
@sprint = Agile::Sprint.visible(current_user).find(params[:id])
end
get &::API::V3::Utilities::Endpoints::Show.new(model: Agile::Sprint).mount
end
end
end
end
end
end
@@ -180,6 +180,14 @@ module OpenProject::Backlogs
"#{root}/backlogs_types/#{id}"
end
add_api_path :sprint do |id|
"#{root}/sprints/#{id}"
end
add_api_endpoint "API::V3::Root" do
mount ::API::V3::Sprints::SprintsAPI
end
config.to_prepare do
OpenProject::Backlogs::Hooks::LayoutHook
OpenProject::Backlogs::Hooks::UserSettingsHook
@@ -0,0 +1,156 @@
# 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::Sprints::SprintRepresenter, "rendering" do
include API::V3::Utilities::PathHelper
let(:workspace) { build_stubbed(:project) }
let(:start_date) { Date.new(2024, 1, 1) }
let(:finish_date) { Date.new(2024, 1, 10) }
let(:status) { "in_planning" }
let(:sprint) do
build_stubbed(:agile_sprint,
project: workspace,
status:,
name: "Sprint 1",
start_date:,
finish_date:)
end
let(:current_user) { build_stubbed(:user) }
let(:embed_links) { true }
let(:representer) { described_class.create(sprint, current_user:, embed_links:) }
subject(:generated) { representer.to_json }
it { is_expected.to include_json("Sprint".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.sprint(sprint.id) }
let(:title) { sprint.name }
end
end
describe "definingWorkspace" do
it_behaves_like "has workspace linked" do
let(:link) { "definingWorkspace" }
end
end
describe "status" do
let(:link) { "status" }
context "with in_planning value" do
it_behaves_like "has a titled link" do
let(:href) { "urn:openproject-org:api:v3:sprints:status:in_planning" }
let(:title) { I18n.t("activerecord.attributes.agile/sprint.statuses.in_planning") }
end
end
context "with active value" do
let(:status) { "active" }
it_behaves_like "has a titled link" do
let(:href) { "urn:openproject-org:api:v3:sprints:status:active" }
let(:title) { I18n.t("activerecord.attributes.agile/sprint.statuses.active") }
end
end
context "with completed value" do
let(:status) { "completed" }
it_behaves_like "has a titled link" do
let(:href) { "urn:openproject-org:api:v3:sprints:status:completed" }
let(:title) { I18n.t("activerecord.attributes.agile/sprint.statuses.completed") }
end
end
end
end
describe "properties" do
describe "_type" do
it_behaves_like "property", :_type do
let(:value) { "Sprint" }
end
end
describe "id" do
it_behaves_like "property", :id do
let(:value) { sprint.id }
end
end
describe "name" do
it_behaves_like "property", :name do
let(:value) { sprint.name }
end
end
describe "startDate" do
it_behaves_like "has ISO 8601 date only" do
let(:date) { start_date }
let(:json_path) { "startDate" }
end
end
describe "finishDate" do
it_behaves_like "has ISO 8601 date only" do
let(:date) { finish_date }
let(:json_path) { "finishDate" }
end
end
describe "createdAt" do
it_behaves_like "has UTC ISO 8601 date and time" do
let(:date) { sprint.created_at }
let(:json_path) { "createdAt" }
end
end
describe "updatedAt" do
it_behaves_like "has UTC ISO 8601 date and time" do
let(:date) { sprint.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
@@ -0,0 +1,105 @@
# 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 Agile::Sprints::Scopes::Visible do
shared_let(:project) { create(:project) }
shared_let(:other_project) { create(:project) }
shared_let(:sprint) { create(:agile_sprint, project:) }
shared_let(:sprint_in_other_project) { create(:agile_sprint, project: other_project) }
shared_let(:role) { create(:project_role, permissions: [:view_sprints]) }
shared_let(:user_with_permission) do
create(:user).tap do |u|
create(:member, project:, user: u, roles: [role])
end
end
shared_let(:user_with_permission_in_both) do
create(:user).tap do |u|
create(:member, project:, user: u, roles: [role])
create(:member, project: other_project, user: u, roles: [role])
end
end
shared_let(:user_without_permission) do
create(:user).tap do |u|
create(:member,
project:,
user: u,
roles: [create(:project_role, permissions: [:view_work_packages])])
end
end
shared_let(:user_without_membership) { create(:user) }
subject { Agile::Sprint.visible(current_user) }
context "for a user with view_sprints in one project" do
current_user { user_with_permission }
it "returns the sprint in that project" do
expect(subject).to contain_exactly(sprint)
end
it "does not return sprints from projects the user has no permission in" do
expect(subject).not_to include(sprint_in_other_project)
end
end
context "for a user with view_sprnts in both projects" do
current_user { user_with_permission_in_both }
it "returns sprints from both projects" do
expect(subject).to contain_exactly(sprint, sprint_in_other_project)
end
end
context "for a user with a different permission but not view_sprints" do
current_user { user_without_permission }
it "returns no sprints" do
expect(subject).to be_empty
end
end
context "for a user without any membership" do
current_user { user_without_membership }
it "returns no sprints" do
expect(subject).to be_empty
end
end
context "when called without a user argument" do
current_user { user_with_permission }
it "uses User.current" do
expect(Agile::Sprint.visible).to contain_exactly(sprint)
end
end
end
@@ -0,0 +1,80 @@
# 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 Sprint resource", content_type: :json do
include Rack::Test::Methods
include API::V3::Utilities::PathHelper
shared_let(:project) { create(:project, public: false) }
shared_let(:sprint) { create(:agile_sprint, project:) }
let(:permissions) { %i[view_sprints] }
current_user do
create(:user, member_with_permissions: { project => permissions })
end
describe "GET /api/v3/sprints/:id", with_flag: :scrum_projects do
let(:get_path) { api_v3_paths.sprint(sprint.id) }
before do
get get_path
end
context "for a user with view_sprints permission" do
it_behaves_like "successful response", 200, "Sprint"
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 sprint that does not exist" do
let(:get_path) { api_v3_paths.sprint(0) }
it_behaves_like "not found"
end
context "when the feature flag is turned off", with_flag: { scrum_projects: false } do
it_behaves_like "not found"
end
end
end