Commit 19640b98 authored by Eugenia Grieff's avatar Eugenia Grieff

Use group ancestors instead of descendants

- Update project milestones spec to use ancestors
groups
parent 389fe963
...@@ -28,11 +28,11 @@ Parameters: ...@@ -28,11 +28,11 @@ Parameters:
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| ---------------------------- | ------ | -------- | ----------- | | ---------------------------- | ------ | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `iids[]` | integer array | optional | Return only the milestones having the given `iid` | | `iids[]` | integer array | optional | Return only the milestones having the given `iid`. Will be ignored if `include_parent_milestones` is set to `true` |
| `state` | string | optional | Return only `active` or `closed` milestones | | `state` | string | optional | Return only `active` or `closed` milestones |
| `title` | string | optional | Return only the milestones having the given `title` | | `title` | string | optional | Return only the milestones having the given `title` |
| `search` | string | optional | Return only milestones with a title or description matching the provided string | | `search` | string | optional | Return only milestones with a title or description matching the provided string |
| `include_parent_milestones` | boolean | optional | Include milestones from parent group and descendants | | `include_parent_milestones` | boolean | optional | Include milestones from parent group and ancestors. Introduced in [GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36944) |
```shell ```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/milestones" curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/milestones"
......
...@@ -100,31 +100,22 @@ module API ...@@ -100,31 +100,22 @@ module API
end end
def milestones_finder_params(parent) def milestones_finder_params(parent)
finder_params = {}
if parent.is_a?(Group) if parent.is_a?(Group)
finder_params[:group_ids] = parent.id { group_ids: parent.id }
else else
finder_params[:project_ids] = parent.id {
finder_params[:group_ids] = parent_group_ids(parent) project_ids: parent.id,
group_ids: parent_group_ids(parent)
}
end end
finder_params
end end
def parent_group_ids(parent) def parent_group_ids(parent)
return unless params[:include_parent_milestones].present? return unless params[:include_parent_milestones].present?
return unless can_read_parent_group?(parent)
parent.group.self_and_descendants
.public_or_visible_to_user(current_user)
.select(:id)
end
def can_read_parent_group?(parent)
return unless parent&.group&.present?
Ability.allowed?(current_user, :read_group, parent.group) parent.group.self_and_ancestors
.public_or_visible_to_user(current_user)
.select(:id)
end end
end end
end end
......
...@@ -17,7 +17,7 @@ module API ...@@ -17,7 +17,7 @@ module API
params do params do
use :list_params use :list_params
optional :include_parent_milestones, type: Boolean, default: false, optional :include_parent_milestones, type: Boolean, default: false,
desc: 'Include milestones from parent group and descendants' desc: 'Include milestones from parent group and ancestors'
end end
get ":id/milestones" do get ":id/milestones" do
authorize! :read_milestone, user_project authorize! :read_milestone, user_project
......
...@@ -3,10 +3,10 @@ ...@@ -3,10 +3,10 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe API::ProjectMilestones do RSpec.describe API::ProjectMilestones do
let(:user) { create(:user) } let_it_be(:user) { create(:user) }
let!(:project) { create(:project, namespace: user.namespace ) } let_it_be(:project) { create(:project, namespace: user.namespace ) }
let!(:closed_milestone) { create(:closed_milestone, project: project, title: 'version1', description: 'closed milestone') } let_it_be(:closed_milestone) { create(:closed_milestone, project: project, title: 'version1', description: 'closed milestone') }
let!(:milestone) { create(:milestone, project: project, title: 'version2', description: 'open milestone') } let_it_be(:milestone) { create(:milestone, project: project, title: 'version2', description: 'open milestone') }
before do before do
project.add_developer(user) project.add_developer(user)
...@@ -18,45 +18,52 @@ RSpec.describe API::ProjectMilestones do ...@@ -18,45 +18,52 @@ RSpec.describe API::ProjectMilestones do
describe 'GET /projects/:id/milestones' do describe 'GET /projects/:id/milestones' do
context 'when include_parent_milestones is true' do context 'when include_parent_milestones is true' do
let(:group) { create(:group, :public) } let_it_be(:group) { create(:group, :public) }
let(:project) { create(:project, group: group) } let_it_be(:child_group) { create(:group, :public, parent: group) }
let!(:group_milestone) { create(:milestone, group: group) } let_it_be(:child_project) { create(:project, group: child_group) }
let_it_be(:project_milestone) { create(:milestone, project: child_project) }
let_it_be(:group_milestone) { create(:milestone, group: group) }
let_it_be(:child_group_milestone) { create(:milestone, group: child_group) }
context 'when user has access to group parent' do before do
let(:nested_group) { create(:group, :public, parent: group) } child_project.add_developer(user)
let!(:nested_group_milestone) { create(:milestone, group: nested_group) } end
it 'result includes parent group and subgroup milestones' do it 'includes parent groups milestones' do
milestones = [nested_group_milestone, group_milestone, milestone, closed_milestone] milestones = [child_group_milestone, group_milestone, project_milestone]
get api("/projects/#{project.id}/milestones", user), get api("/projects/#{child_project.id}/milestones", user),
params: { include_parent_milestones: true } params: { include_parent_milestones: true }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response.size).to eq(4) expect(json_response.size).to eq(3)
expect(json_response.map { |entry| entry["id"] }).to eq(milestones.map(&:id))
end
expect(json_response.map { |entry| entry["id"] }).to eq(milestones.map(&:id)) context 'when user has no access to an ancestor group' do
before do
[child_group, group].each do |group|
group.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
end
end end
end
context 'when user has no access to group parent' do it 'does not show ancestor group milestones' do
it 'does not show parent group milestones' do milestones = [child_group_milestone, project_milestone]
allow(Ability).to receive(:allowed?).and_call_original
allow(Ability).to receive(:allowed?).with(user, :read_group, group).and_return(false)
get api("/projects/#{project.id}/milestones", user), get api("/projects/#{child_project.id}/milestones", user),
params: { include_parent_milestones: true } params: { include_parent_milestones: true }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response.size).to eq(2) expect(json_response.size).to eq(2)
expect(json_response.map { |entry| entry["id"] }).to eq(milestones.map(&:id))
end end
end end
context 'when filtering by iids' do context 'when filtering by iids' do
it 'does not filer by iids' do it 'does not filter by iids' do
milestones = [group_milestone, milestone, closed_milestone] milestones = [child_group_milestone, group_milestone, project_milestone]
get api("/projects/#{project.id}/milestones", user), get api("/projects/#{child_project.id}/milestones", user),
params: { include_parent_milestones: true, iids: [group_milestone.iid] } params: { include_parent_milestones: true, iids: [group_milestone.iid] }
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment