Commit 61a1602a authored by Jan Provaznik's avatar Jan Provaznik Committed by James Lopez

Allow fetching milestones of subgroups/subprojects

When includes_descendant parameter is passed, also milestones
from all subgroups and subprojects are returned but only
if visible to user.
parent f2b6fadd
...@@ -9,6 +9,10 @@ module Resolvers ...@@ -9,6 +9,10 @@ module Resolvers
required: false, required: false,
description: 'Filter milestones by state' description: 'Filter milestones by state'
argument :include_descendants, GraphQL::BOOLEAN_TYPE,
required: false,
description: 'Return also milestones in all subgroups and subprojects'
type Types::MilestoneType, null: true type Types::MilestoneType, null: true
def resolve(**args) def resolve(**args)
...@@ -26,16 +30,16 @@ module Resolvers ...@@ -26,16 +30,16 @@ module Resolvers
state: args[:state] || 'all', state: args[:state] || 'all',
start_date: args[:start_date], start_date: args[:start_date],
end_date: args[:end_date] end_date: args[:end_date]
}.merge(parent_id_parameter) }.merge(parent_id_parameter(args))
end end
def parent def parent
@parent ||= object.respond_to?(:sync) ? object.sync : object @parent ||= object.respond_to?(:sync) ? object.sync : object
end end
def parent_id_parameter def parent_id_parameter(args)
if parent.is_a?(Group) if parent.is_a?(Group)
{ group_ids: parent.id } group_parameters(args)
elsif parent.is_a?(Project) elsif parent.is_a?(Project)
{ project_ids: parent.id } { project_ids: parent.id }
end end
...@@ -46,5 +50,26 @@ module Resolvers ...@@ -46,5 +50,26 @@ module Resolvers
def authorize! def authorize!
Ability.allowed?(context[:current_user], :read_milestone, parent) || raise_resource_not_available_error! Ability.allowed?(context[:current_user], :read_milestone, parent) || raise_resource_not_available_error!
end end
def group_parameters(args)
return { group_ids: parent.id } unless include_descendants?(args)
{
group_ids: parent.self_and_descendants.public_or_visible_to_user(current_user).select(:id),
project_ids: group_projects.with_issues_or_mrs_available_for_user(current_user)
}
end
def include_descendants?(args)
args[:include_descendants].present? && Feature.enabled?(:group_milestone_descendants, parent)
end
def group_projects
GroupProjectsFinder.new(
group: parent,
current_user: current_user,
options: { include_subgroups: true }
).execute
end
end end
end end
...@@ -4105,6 +4105,11 @@ type Group { ...@@ -4105,6 +4105,11 @@ type Group {
""" """
first: Int first: Int
"""
Return also milestones in all subgroups and subprojects
"""
includeDescendants: Boolean
""" """
Returns the last _n_ elements from the list. Returns the last _n_ elements from the list.
""" """
......
...@@ -11425,6 +11425,16 @@ ...@@ -11425,6 +11425,16 @@
}, },
"defaultValue": null "defaultValue": null
}, },
{
"name": "includeDescendants",
"description": "Return also milestones in all subgroups and subprojects",
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"defaultValue": null
},
{ {
"name": "after", "name": "after",
"description": "Returns the elements in the list that come after the specified cursor.", "description": "Returns the elements in the list that come after the specified cursor.",
......
...@@ -8,14 +8,14 @@ describe Resolvers::MilestoneResolver do ...@@ -8,14 +8,14 @@ describe Resolvers::MilestoneResolver do
describe '#resolve' do describe '#resolve' do
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
def resolve_group_milestones(args = {}, context = { current_user: current_user })
resolve(described_class, obj: group, args: args, ctx: context)
end
context 'for group milestones' do context 'for group milestones' do
let_it_be(:now) { Time.now } let_it_be(:now) { Time.now }
let_it_be(:group) { create(:group, :private) } let_it_be(:group) { create(:group, :private) }
def resolve_group_milestones(args = {}, context = { current_user: current_user })
resolve(described_class, obj: group, args: args, ctx: context)
end
before do before do
group.add_developer(current_user) group.add_developer(current_user)
end end
...@@ -89,5 +89,25 @@ describe Resolvers::MilestoneResolver do ...@@ -89,5 +89,25 @@ describe Resolvers::MilestoneResolver do
end end
end end
end end
context 'when including descendant milestones in a public group' do
let_it_be(:group) { create(:group, :public) }
let(:args) { { include_descendants: true } }
it 'finds milestones only in accessible projects and groups' do
accessible_group = create(:group, :private, parent: group)
accessible_project = create(:project, group: accessible_group)
accessible_group.add_developer(current_user)
inaccessible_group = create(:group, :private, parent: group)
inaccessible_project = create(:project, :private, group: group)
milestone1 = create(:milestone, group: group)
milestone2 = create(:milestone, group: accessible_group)
milestone3 = create(:milestone, project: accessible_project)
create(:milestone, group: inaccessible_group)
create(:milestone, project: inaccessible_project)
expect(resolve_group_milestones(args)).to match_array([milestone1, milestone2, milestone3])
end
end
end end
end end
...@@ -7,7 +7,7 @@ describe 'Milestones through GroupQuery' do ...@@ -7,7 +7,7 @@ describe 'Milestones through GroupQuery' do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:now) { Time.now } let_it_be(:now) { Time.now }
let_it_be(:group) { create(:group, :private) } let_it_be(:group) { create(:group) }
let_it_be(:milestone_1) { create(:milestone, group: group) } let_it_be(:milestone_1) { create(:milestone, group: group) }
let_it_be(:milestone_2) { create(:milestone, group: group, state: :closed, start_date: now, due_date: now + 1.day) } let_it_be(:milestone_2) { create(:milestone, group: group, state: :closed, start_date: now, due_date: now + 1.day) }
let_it_be(:milestone_3) { create(:milestone, group: group, start_date: now, due_date: now + 2.days) } let_it_be(:milestone_3) { create(:milestone, group: group, start_date: now, due_date: now + 2.days) }
...@@ -17,10 +17,6 @@ describe 'Milestones through GroupQuery' do ...@@ -17,10 +17,6 @@ describe 'Milestones through GroupQuery' do
let(:milestone_data) { graphql_data['group']['milestones']['edges'] } let(:milestone_data) { graphql_data['group']['milestones']['edges'] }
describe 'Get list of milestones from a group' do describe 'Get list of milestones from a group' do
before do
group.add_developer(user)
end
context 'when the request is correct' do context 'when the request is correct' do
before do before do
fetch_milestones(user) fetch_milestones(user)
...@@ -51,6 +47,48 @@ describe 'Milestones through GroupQuery' do ...@@ -51,6 +47,48 @@ describe 'Milestones through GroupQuery' do
end end
end end
context 'when including milestones from decendants' do
let_it_be(:accessible_group) { create(:group, :private, parent: group) }
let_it_be(:accessible_project) { create(:project, group: accessible_group) }
let_it_be(:inaccessible_group) { create(:group, :private, parent: group) }
let_it_be(:inaccessible_project) { create(:project, :private, group: group) }
let_it_be(:submilestone_1) { create(:milestone, group: accessible_group) }
let_it_be(:submilestone_2) { create(:milestone, project: accessible_project) }
let_it_be(:submilestone_3) { create(:milestone, group: inaccessible_group) }
let_it_be(:submilestone_4) { create(:milestone, project: inaccessible_project) }
let(:args) { { include_descendants: true } }
before do
accessible_group.add_developer(user)
end
it 'returns milestones also from subgroups and subprojects visible to user' do
fetch_milestones(user, args)
expect_array_response(
milestone_1.to_global_id.to_s, milestone_2.to_global_id.to_s,
milestone_3.to_global_id.to_s, milestone_4.to_global_id.to_s,
submilestone_1.to_global_id.to_s, submilestone_2.to_global_id.to_s
)
end
context 'when group_milestone_descendants is disabled' do
before do
stub_feature_flags(group_milestone_descendants: false)
end
it 'ignores descendant milestones' do
fetch_milestones(user, args)
expect_array_response(
milestone_1.to_global_id.to_s, milestone_2.to_global_id.to_s,
milestone_3.to_global_id.to_s, milestone_4.to_global_id.to_s
)
end
end
end
def fetch_milestones(user = nil, args = {}) def fetch_milestones(user = nil, args = {})
post_graphql(milestones_query(args), current_user: user) post_graphql(milestones_query(args), current_user: user)
end end
......
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