mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
Merge branch 'release/17.4' into release/17.5
This commit is contained in:
@@ -6270,6 +6270,7 @@ en:
|
||||
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.
|
||||
workPackage: Undisclosed - The work package is invisible because of lacking permissions.
|
||||
|
||||
doorkeeper:
|
||||
pre_authorization:
|
||||
|
||||
@@ -193,6 +193,47 @@ module API
|
||||
skip_render:)
|
||||
end
|
||||
|
||||
# Like associated_resource, but skips rendering and shows an undisclosed
|
||||
# link when the associated record exists but is not visible to the current user.
|
||||
# Requires the associated model to implement +visible?(user)+.
|
||||
def associated_visible_resource(name,
|
||||
as: nil,
|
||||
representer: nil,
|
||||
v3_path: name,
|
||||
link_title_attribute: :name,
|
||||
undisclosed_title: :"api_v3.undisclosed.#{name.to_s.camelize(:lower)}")
|
||||
associated_resource(
|
||||
name,
|
||||
as:,
|
||||
representer:,
|
||||
v3_path:,
|
||||
skip_render: ->(*) {
|
||||
represented.public_send(:"#{name}_id").nil? ||
|
||||
!represented.public_send(name)&.visible?(current_user)
|
||||
},
|
||||
link: associated_visible_resource_link_lambda(name,
|
||||
v3_path:,
|
||||
link_title_attribute:,
|
||||
undisclosed_title:)
|
||||
)
|
||||
end
|
||||
|
||||
def associated_visible_resource_link_lambda(name, v3_path:, link_title_attribute:, undisclosed_title:)
|
||||
->(*) do
|
||||
id = represented.public_send(:"#{name}_id")
|
||||
next if id.nil?
|
||||
|
||||
resource = represented.public_send(name)
|
||||
if resource&.visible?(current_user)
|
||||
{ href: api_v3_paths.public_send(v3_path, id),
|
||||
title: resource.public_send(link_title_attribute) }
|
||||
else
|
||||
{ href: ::API::V3::URN_UNDISCLOSED,
|
||||
title: I18n.t(undisclosed_title) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def link_attr(name, uncacheable, link_cache_if)
|
||||
links_attr = { rel: name.to_s.camelize(:lower) }
|
||||
links_attr[:uncacheable] = true if uncacheable
|
||||
|
||||
@@ -77,8 +77,7 @@ module API
|
||||
representer: ::API::V3::Users::UserRepresenter,
|
||||
skip_render: ->(*) { represented.presenter_id.nil? }
|
||||
|
||||
associated_resource :work_package,
|
||||
skip_render: ->(*) { represented.work_package_id.nil? }
|
||||
associated_visible_resource :work_package
|
||||
|
||||
associated_resource :meeting_section,
|
||||
as: :section,
|
||||
|
||||
+24
@@ -138,6 +138,30 @@ RSpec.describe "API v3 Meeting Agenda Items sub-resource", content_type: :json d
|
||||
expect(last_response).to have_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
context "when the agenda item is linked to a work package in an inaccessible project" do
|
||||
let(:private_project) { create(:project, public: false) }
|
||||
let(:private_work_package) { create(:work_package, project: private_project) }
|
||||
let!(:wp_agenda_item) do
|
||||
create(:wp_meeting_agenda_item, meeting:, meeting_section: section, work_package: private_work_package,
|
||||
author: current_user)
|
||||
end
|
||||
let(:path) { api_v3_paths.meeting_agenda_item(meeting.id, wp_agenda_item.id) }
|
||||
|
||||
it "returns 200" do
|
||||
expect(last_response).to have_http_status(:ok)
|
||||
end
|
||||
|
||||
it "does not embed the inaccessible work package" do
|
||||
expect(last_response.body).not_to have_json_path("_embedded/workPackage")
|
||||
end
|
||||
|
||||
it "renders the work package link as undisclosed" do
|
||||
expect(last_response.body)
|
||||
.to be_json_eql(::API::V3::URN_UNDISCLOSED.to_json)
|
||||
.at_path("_links/workPackage/href")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "PATCH /api/v3/meetings/:meeting_id/agenda_items/:id" do
|
||||
|
||||
@@ -43,6 +43,76 @@ RSpec.describe API::Decorators::LinkedResource do
|
||||
let(:represented) { {} }
|
||||
let(:current_user) { create(:user) }
|
||||
|
||||
describe ".associated_visible_resource" do
|
||||
include API::V3::Utilities::PathHelper
|
||||
|
||||
let(:thing_representer_class) do
|
||||
Class.new(API::Decorators::Single) do
|
||||
property :id
|
||||
def _type = "Thing"
|
||||
end
|
||||
end
|
||||
|
||||
let(:representer_class) do
|
||||
klass = thing_representer_class
|
||||
Class.new(API::Decorators::Single) do
|
||||
include API::Decorators::LinkedResource
|
||||
associated_visible_resource :thing, v3_path: :thing, representer: klass,
|
||||
undisclosed_title: :"api_v3.undisclosed.parent"
|
||||
end
|
||||
end
|
||||
|
||||
let(:thing_id) { 42 }
|
||||
let(:thing) { double("thing", id: thing_id, name: "Thing Name") }
|
||||
let(:model) { Struct.new(:thing_id, :thing).new(thing_id, thing) }
|
||||
|
||||
before do
|
||||
without_partial_double_verification do
|
||||
allow(api_v3_paths).to receive(:thing).with(thing_id).and_return("/api/v3/things/#{thing_id}")
|
||||
end
|
||||
end
|
||||
|
||||
subject(:json) { representer_class.new(model, current_user:, embed_links: true).to_json }
|
||||
|
||||
context "when the resource is visible" do
|
||||
before { allow(thing).to receive(:visible?).with(current_user).and_return(true) }
|
||||
|
||||
it "renders the link href and title" do
|
||||
expect(json).to be_json_eql("/api/v3/things/42".to_json).at_path("_links/thing/href")
|
||||
expect(json).to be_json_eql("Thing Name".to_json).at_path("_links/thing/title")
|
||||
end
|
||||
|
||||
it "embeds the resource" do
|
||||
expect(json).to have_json_path("_embedded/thing")
|
||||
end
|
||||
end
|
||||
|
||||
context "when the resource is not visible" do
|
||||
before { allow(thing).to receive(:visible?).with(current_user).and_return(false) }
|
||||
|
||||
it "renders the link href as undisclosed" do
|
||||
expect(json).to be_json_eql(::API::V3::URN_UNDISCLOSED.to_json).at_path("_links/thing/href")
|
||||
end
|
||||
|
||||
it "does not embed the resource" do
|
||||
expect(json).not_to have_json_path("_embedded/thing")
|
||||
end
|
||||
end
|
||||
|
||||
context "when the resource id is nil" do
|
||||
let(:thing_id) { nil }
|
||||
let(:thing) { nil }
|
||||
|
||||
it "renders no link" do
|
||||
expect(json).not_to have_json_path("_links/thing")
|
||||
end
|
||||
|
||||
it "does not embed the resource" do
|
||||
expect(json).not_to have_json_path("_embedded/thing")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#from_hash" do
|
||||
subject { representer.new(represented, current_user:).from_hash(input_hash) }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user