diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index a8dc50834d46698362258e8746fa1c252a3c049a..3bdf10db0e4f1653529080d60107ad1d1c5950f4 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -19732,6 +19732,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| `iid` | [`ID`](#id) | Internal ID of the Iteration to look up. |
| `in` | [`[IterationSearchableField!]`](#iterationsearchablefield) | Fields in which the fuzzy-search should be performed with the query given in the argument `search`. Defaults to `[title]`. |
| `includeAncestors` | [`Boolean`](#boolean) | Whether to include ancestor iterations. Defaults to true. |
+| `includeDescendants` | [`Boolean`](#boolean) | Whether to include descendant iterations. |
| `iterationCadenceIds` | [`[IterationsCadenceID!]`](#iterationscadenceid) | Global iteration cadence IDs by which to look up the iterations. |
| `search` | [`String`](#string) | Query used for fuzzy-searching in the fields selected in the argument `in`. Returns all iterations if empty. |
| `sort` | [`IterationSort`](#iterationsort) | List iterations by sort order. If unspecified, an arbitrary order (subject to change) is used. |
@@ -24810,6 +24811,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| `iid` | [`ID`](#id) | Internal ID of the Iteration to look up. |
| `in` | [`[IterationSearchableField!]`](#iterationsearchablefield) | Fields in which the fuzzy-search should be performed with the query given in the argument `search`. Defaults to `[title]`. |
| `includeAncestors` | [`Boolean`](#boolean) | Whether to include ancestor iterations. Defaults to true. |
+| `includeDescendants` | [`Boolean`](#boolean) | Whether to include descendant iterations. |
| `iterationCadenceIds` | [`[IterationsCadenceID!]`](#iterationscadenceid) | Global iteration cadence IDs by which to look up the iterations. |
| `search` | [`String`](#string) | Query used for fuzzy-searching in the fields selected in the argument `in`. Returns all iterations if empty. |
| `sort` | [`IterationSort`](#iterationsort) | List iterations by sort order. If unspecified, an arbitrary order (subject to change) is used. |
diff --git a/doc/api/group_iterations.md b/doc/api/group_iterations.md
index b8cb1b7e053f306904ed052b80b5cb2d61f52bbb..c522992fdea722085028dcdabeb2c64d8f8b6bcf 100644
--- a/doc/api/group_iterations.md
+++ b/doc/api/group_iterations.md
@@ -26,18 +26,20 @@ GET /groups/:id/iterations?state=opened
GET /groups/:id/iterations?state=closed
GET /groups/:id/iterations?search=version
GET /groups/:id/iterations?include_ancestors=false
+GET /groups/:id/iterations?include_descendants=true
GET /groups/:id/iterations?updated_before=2013-10-02T09%3A24%3A18Z
GET /groups/:id/iterations?updated_after=2013-10-02T09%3A24%3A18Z
```
-| Attribute | Type | Required | Description |
-| ------------------- | ------- | -------- | ----------- |
-| `state` | string | no | 'Return `opened`, `upcoming`, `current`, `closed`, or `all` iterations.' |
-| `search` | string | no | Return only iterations with a title matching the provided string. |
-| `in` | array of strings | no | Fields in which fuzzy search should be performed with the query given in the argument `search`. The available options are `title` and `cadence_title`. Default is `[title]`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/350991) in GitLab 16.2. |
-| `include_ancestors` | boolean | no | Include iterations from parent group and its ancestors. Defaults to `true`. |
-| `updated_before` | datetime | no | Return only iterations updated before the given datetime. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/378662) in GitLab 15.10. |
-| `updated_after` | datetime | no | Return only iterations updated after the given datetime. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/378662) in GitLab 15.10. |
+| Attribute | Type | Required | Description |
+| --------------------- | -------- | -------- | ----------- |
+| `state` | string | no | 'Return `opened`, `upcoming`, `current`, `closed`, or `all` iterations.' |
+| `search` | string | no | Return only iterations with a title matching the provided string. |
+| `in` | array of strings | no | Fields in which fuzzy search should be performed with the query given in the argument `search`. The available options are `title` and `cadence_title`. Default is `[title]`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/350991) in GitLab 16.2. |
+| `include_ancestors` | boolean | no | Include iterations for group and its ancestors. Defaults to `true`. |
+| `include_descendants` | boolean | no | Include iterations for group and its descendants. Defaults to `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/135764) in GitLab 16.7. |
+| `updated_before` | datetime | no | Return only iterations updated before the given datetime. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/378662) in GitLab 15.10. |
+| `updated_after` | datetime | no | Return only iterations updated after the given datetime. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/378662) in GitLab 15.10. |
Example request:
diff --git a/doc/api/iterations.md b/doc/api/iterations.md
index ef718fffe0a5cc0a5af884e2635cd521b2ccce72..98d596895be5820d71f68d06e0141fca579c6063 100644
--- a/doc/api/iterations.md
+++ b/doc/api/iterations.md
@@ -28,18 +28,20 @@ GET /projects/:id/iterations?state=opened
GET /projects/:id/iterations?state=closed
GET /projects/:id/iterations?search=version
GET /projects/:id/iterations?include_ancestors=false
+GET /projects/:id/iterations?include_descendants=true
GET /projects/:id/iterations?updated_before=2013-10-02T09%3A24%3A18Z
GET /projects/:id/iterations?updated_after=2013-10-02T09%3A24%3A18Z
```
-| Attribute | Type | Required | Description |
-| ------------------- | ------- | -------- | ----------- |
-| `state` | string | no | 'Return `opened`, `upcoming`, `current`, `closed`, or `all` iterations.' |
-| `search` | string | no | Return only iterations with a title matching the provided string. |
-| `in` | array of strings | no | Fields in which fuzzy search should be performed with the query given in the argument `search`. The available options are `title` and `cadence_title`. Default is `[title]`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/350991) in GitLab 16.2. |
-| `include_ancestors` | boolean | no | Include iterations from parent group and its ancestors. Defaults to `true`. |
-| `updated_before` | datetime | no | Return only iterations updated before the given datetime. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/378662) in GitLab 15.10. |
-| `updated_after` | datetime | no | Return only iterations updated after the given datetime. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/378662) in GitLab 15.10. |
+| Attribute | Type | Required | Description |
+| --------------------- | -------- | -------- | ----------- |
+| `state` | string | no | 'Return `opened`, `upcoming`, `current`, `closed`, or `all` iterations.' |
+| `search` | string | no | Return only iterations with a title matching the provided string. |
+| `in` | array of strings | no | Fields in which fuzzy search should be performed with the query given in the argument `search`. The available options are `title` and `cadence_title`. Default is `[title]`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/350991) in GitLab 16.2. |
+| `include_ancestors` | boolean | no | Include iterations for parent group and its ancestors. Defaults to `true`. |
+| `include_descendants` | boolean | no | Include iterations for parent group and its descendants. Defaults to `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/135764) in GitLab 16.7. |
+| `updated_before` | datetime | no | Return only iterations updated before the given datetime. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/378662) in GitLab 15.10. |
+| `updated_after` | datetime | no | Return only iterations updated after the given datetime. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/378662) in GitLab 15.10. |
Example request:
diff --git a/ee/app/finders/iterations_finder.rb b/ee/app/finders/iterations_finder.rb
index d6f2082f6ec307e222dfda56d2ee22d57da43675..ddf28d9b8b3101b7681bd378c8465f5b1734de14 100644
--- a/ee/app/finders/iterations_finder.rb
+++ b/ee/app/finders/iterations_finder.rb
@@ -5,6 +5,7 @@
# params - Hash
# parent - The group in which to look-up iterations.
# include_ancestors - whether to look-up iterations in group ancestors.
+# include_descendants - whether to look-up iterations in group descendants.
# title - Filter by title.
# search - Filter by fuzzy searching the given query in the selected fields.
# in - Array of searchable fields used with search param.
@@ -124,6 +125,8 @@ def order(items)
def groups
parent = params[:parent]
+ include_ancestors = params[:include_ancestors].present?
+ include_descendants = params[:include_descendants].present?
group = case parent
when Group
@@ -134,6 +137,14 @@ def groups
raise ArgumentError, 'Invalid parent class. Only Project and Group are supported.'
end
- params[:include_ancestors] ? group.self_and_ancestors : group
+ return group unless include_ancestors || include_descendants
+
+ if include_ancestors && include_descendants
+ group.self_and_hierarchy
+ elsif include_ancestors
+ group.self_and_ancestors
+ else
+ group.self_and_descendants
+ end
end
end
diff --git a/ee/app/graphql/resolvers/iterations_resolver.rb b/ee/app/graphql/resolvers/iterations_resolver.rb
index 4f8a4d8cf46e080a1afe0b3e9f901323be6ffe0c..384be6e3070366708f36fbfcf33f24c26975b8ea 100644
--- a/ee/app/graphql/resolvers/iterations_resolver.rb
+++ b/ee/app/graphql/resolvers/iterations_resolver.rb
@@ -35,6 +35,9 @@ class IterationsResolver < BaseResolver
argument :include_ancestors, GraphQL::Types::Boolean,
required: false,
description: 'Whether to include ancestor iterations. Defaults to true.'
+ argument :include_descendants, GraphQL::Types::Boolean,
+ required: false,
+ description: 'Whether to include descendant iterations.'
argument :iteration_cadence_ids, [::Types::GlobalIDType[::Iterations::Cadence]],
required: false,
@@ -84,6 +87,7 @@ def iterations_finder_params(args)
{
parent: parent,
include_ancestors: args[:include_ancestors],
+ include_descendants: args[:include_descendants],
id: args[:id],
iid: args[:iid],
iteration_cadence_ids: args[:iteration_cadence_ids],
diff --git a/ee/lib/api/iterations.rb b/ee/lib/api/iterations.rb
index a22c04140ebaaf71a50a0634694c2a69cc675812..3fb09e6ee5b46e4e243acae96729a0d4cad4092c 100644
--- a/ee/lib/api/iterations.rb
+++ b/ee/lib/api/iterations.rb
@@ -37,6 +37,11 @@ class Iterations < ::API::Base
default: true,
desc: 'Include iterations from parent and its ancestors',
documentation: { example: false }
+ optional :include_descendants,
+ type: Grape::API::Boolean,
+ default: false,
+ desc: 'Include iterations from parent and its descendants',
+ documentation: { example: true }
optional :updated_before,
type: DateTime,
desc: 'Return milestones updated before the specified datetime. Format: ISO 8601 YYYY-MM-DDTHH:MM:SSZ',
@@ -58,6 +63,7 @@ def iterations_finder_params(parent)
finder_params = {
parent: parent,
include_ancestors: params[:include_ancestors],
+ include_descendants: params[:include_descendants],
state: params[:state],
search: nil,
in: nil,
diff --git a/ee/spec/finders/iterations_finder_spec.rb b/ee/spec/finders/iterations_finder_spec.rb
index 40bd0d707d1852f9dba4de8e9f335bafe0e8eed3..5397f0cc3a54910545d20def7427066e9b2c8a8e 100644
--- a/ee/spec/finders/iterations_finder_spec.rb
+++ b/ee/spec/finders/iterations_finder_spec.rb
@@ -4,18 +4,24 @@
RSpec.describe IterationsFinder, feature_category: :team_planning do
let_it_be(:root) { create(:group, :private) }
- let_it_be(:group) { create(:group, :private, parent: root) }
+ let_it_be(:ancestor_group) { create(:group, :private, parent: root) }
+ let_it_be(:group) { create(:group, :private, parent: ancestor_group) }
+ let_it_be(:subgroup) { create(:group, :private, parent: group) }
let_it_be(:project_1) { create(:project, namespace: group) }
let_it_be(:user) { create(:user) }
let_it_be(:iteration_cadence1) { create(:iterations_cadence, group: group, active: true, duration_in_weeks: 1, title: 'one week iterations') }
let_it_be(:iteration_cadence2) { create(:iterations_cadence, group: group, active: true, duration_in_weeks: 2, title: 'two week iterations') }
let_it_be(:iteration_cadence3) { create(:iterations_cadence, group: root, active: true, duration_in_weeks: 3, title: 'three week iterations') }
+ let_it_be(:iteration_cadence4) { create(:iterations_cadence, group: subgroup, active: true, duration_in_weeks: 4, title: 'four week iterations') }
+ let_it_be(:iteration_cadence5) { create(:iterations_cadence, group: ancestor_group, active: true, duration_in_weeks: 4, title: 'ancestor iterations') }
- let_it_be(:closed_iteration) { create(:closed_iteration, :skip_future_date_validation, iterations_cadence: iteration_cadence2, start_date: 7.days.ago, due_date: 2.days.ago, updated_at: 10.days.ago) }
- let_it_be(:started_group_iteration) { create(:current_iteration, :skip_future_date_validation, iterations_cadence: iteration_cadence2, title: 'one test', start_date: 1.day.ago, due_date: Date.today, updated_at: 5.days.ago) }
+ let_it_be(:closed_iteration) { create(:closed_iteration, :skip_future_date_validation, iterations_cadence: iteration_cadence2, start_date: 7.days.ago, due_date: 3.days.ago, updated_at: 10.days.ago) }
+ let_it_be(:started_group_iteration) { create(:current_iteration, :skip_future_date_validation, iterations_cadence: iteration_cadence2, title: 'one test', start_date: 2.days.ago, due_date: Date.today, updated_at: 5.days.ago) }
let_it_be(:upcoming_group_iteration) { create(:iteration, iterations_cadence: iteration_cadence1, title: 'Iteration 1', start_date: 1.day.from_now, due_date: 3.days.from_now) }
let_it_be(:root_group_iteration) { create(:current_iteration, iterations_cadence: iteration_cadence3, start_date: 1.day.ago, due_date: 2.days.from_now) }
- let_it_be(:root_closed_iteration) { create(:closed_iteration, iterations_cadence: iteration_cadence3, start_date: 1.week.ago, due_date: 2.days.ago) }
+ let_it_be(:ancestor_group_iteration) { create(:current_iteration, iterations_cadence: iteration_cadence5, start_date: 1.day.ago, due_date: 2.days.from_now) }
+ let_it_be(:ancestor_closed_iteration) { create(:closed_iteration, iterations_cadence: iteration_cadence5, start_date: 1.week.ago, due_date: 2.days.ago) }
+ let_it_be(:subgroup_iteration) { create(:current_iteration, :skip_future_date_validation, iterations_cadence: iteration_cadence4, title: 'subgroup test', start_date: 2.days.ago, due_date: Date.today, updated_at: 5.days.ago) }
let(:parent) { project_1 }
let(:params) { { parent: parent, include_ancestors: true } }
@@ -40,17 +46,22 @@
end
context 'when skipping authorization' do
- let(:params) { { parent: parent } }
+ let(:params) { { parent: parent, include_ancestors: true } }
it 'returns iterations' do
- expect(described_class.new(user, params).execute(skip_authorization: true)).not_to be_empty
+ iterations = described_class.new(user, params).execute(skip_authorization: true)
+
+ expect(iterations).to include(root_group_iteration)
+ expect(iterations).not_to include(subgroup_iteration)
end
end
end
context 'with permissions' do
before do
+ ancestor_group.add_reporter(user)
group.add_reporter(user)
+ subgroup.add_reporter(user)
project_1.add_reporter(user)
end
@@ -71,7 +82,7 @@
context 'with filters' do
context 'by iteration_wildcard_id' do
- let_it_be(:started_group_iteration2) { create(:current_iteration, :skip_future_date_validation, iterations_cadence: iteration_cadence1, group: iteration_cadence1.group, title: 'one test', start_date: 1.day.ago, due_date: Date.today) }
+ let_it_be(:started_group_iteration2) { create(:current_iteration, :skip_future_date_validation, iterations_cadence: iteration_cadence1, group: iteration_cadence1.group, title: 'one test', start_date: 2.days.ago, due_date: Date.today) }
before do
params[:iteration_wildcard_id] = 'CURRENT'
@@ -93,12 +104,22 @@
end
context 'iterations for project with ancestors' do
- it 'returns iterations for project and ancestor groups' do
- expect(subject).to contain_exactly(root_closed_iteration, root_group_iteration, closed_iteration, started_group_iteration, upcoming_group_iteration)
+ it 'orders iterations by due date and title' do
+ expect(subject.to_a).to eq([root_group_iteration, closed_iteration, ancestor_closed_iteration, started_group_iteration, ancestor_group_iteration, upcoming_group_iteration].sort_by { |a| [a.due_date, a.title, a.id] })
end
+ end
- it 'orders iterations by due date and title' do
- expect(subject.to_a).to eq([closed_iteration, root_closed_iteration, started_group_iteration, root_group_iteration, upcoming_group_iteration].sort_by { |a| [a.due_date, a.title, a.id] })
+ context 'iterations for group with descendants' do
+ let(:params) { { parent: group, include_ancestors: false, include_descendants: true } }
+
+ it 'returns iterations for descendent groups and projects' do
+ expect(subject.to_a).to eq([closed_iteration, started_group_iteration, upcoming_group_iteration, subgroup_iteration].sort_by { |a| [a.due_date, a.title, a.id] })
+ end
+
+ it 'returns iterations for ancestor/descendent groups and projects' do
+ params[:include_ancestors] = true
+
+ expect(subject.to_a).to eq([root_group_iteration, ancestor_closed_iteration, ancestor_group_iteration, closed_iteration, started_group_iteration, upcoming_group_iteration, subgroup_iteration].sort_by { |a| [a.due_date, a.title, a.id] })
end
end
@@ -106,13 +127,13 @@
it 'filters by all states' do
params[:state] = 'all'
- expect(subject).to contain_exactly(root_closed_iteration, root_group_iteration, closed_iteration, started_group_iteration, upcoming_group_iteration)
+ expect(subject).to contain_exactly(root_group_iteration, ancestor_closed_iteration, ancestor_group_iteration, closed_iteration, started_group_iteration, upcoming_group_iteration)
end
it 'filters by current state' do
params[:state] = 'current'
- expect(subject).to contain_exactly(root_group_iteration, started_group_iteration)
+ expect(subject).to contain_exactly(root_group_iteration, ancestor_group_iteration, started_group_iteration)
end
it 'filters by invalid state' do
@@ -124,13 +145,13 @@
it 'filters by opened state' do
params[:state] = 'opened'
- expect(subject).to contain_exactly(upcoming_group_iteration, root_group_iteration, started_group_iteration)
+ expect(subject).to contain_exactly(root_group_iteration, upcoming_group_iteration, ancestor_group_iteration, started_group_iteration)
end
it 'filters by closed state' do
params[:state] = 'closed'
- expect(subject).to contain_exactly(root_closed_iteration, closed_iteration)
+ expect(subject).to contain_exactly(ancestor_closed_iteration, closed_iteration)
end
it 'filters by title' do
@@ -151,7 +172,7 @@
end
context 'filters by title' do
- let(:all_iterations) { [closed_iteration, started_group_iteration, upcoming_group_iteration, root_group_iteration, root_closed_iteration] }
+ let(:all_iterations) { [closed_iteration, started_group_iteration, upcoming_group_iteration, root_group_iteration, ancestor_group_iteration, ancestor_closed_iteration] }
where(:search, :fields_to_search, :expected_iterations) do
'' | [] | lazy { all_iterations }
@@ -214,8 +235,9 @@
expect(subject).to contain_exactly(
started_group_iteration,
upcoming_group_iteration,
+ ancestor_group_iteration,
root_group_iteration,
- root_closed_iteration
+ ancestor_closed_iteration
)
end
@@ -232,7 +254,7 @@
end
it 'returns CURRENT iterations' do
- expect(subject).to contain_exactly(root_group_iteration, started_group_iteration)
+ expect(subject).to contain_exactly(ancestor_group_iteration, root_group_iteration, started_group_iteration)
end
it 'returns CURRENT iteration for the specified cadence' do
@@ -246,13 +268,13 @@
it 'returns iterations with start_date and due_date between timeframe' do
params.merge!(start_date: 1.day.ago, end_date: 3.days.from_now)
- expect(subject).to match_array([started_group_iteration, upcoming_group_iteration, root_group_iteration])
+ expect(subject).to match_array([started_group_iteration, upcoming_group_iteration, ancestor_group_iteration, root_group_iteration])
end
it 'returns iterations which start before the timeframe' do
- params.merge!(start_date: 3.days.ago, end_date: 2.days.ago)
+ params.merge!(start_date: 4.days.ago, end_date: 3.days.ago)
- expect(subject).to match_array([closed_iteration, root_closed_iteration])
+ expect(subject).to match_array([closed_iteration, ancestor_closed_iteration])
end
it 'returns iterations which end after the timeframe' do
@@ -279,11 +301,12 @@
context 'sorting' do
let(:cadence1_iterations) { [upcoming_group_iteration] }
let(:cadence2_iterations) { [closed_iteration, started_group_iteration] }
- let(:cadence3_iterations) { [root_closed_iteration, root_group_iteration] }
+ let(:cadence3_iterations) { [root_group_iteration] }
+ let(:cadence5_iterations) { [ancestor_closed_iteration, ancestor_group_iteration] }
shared_examples 'sorted by the default order' do
it 'sorts by the default order (due_date, title, id asc)' do
- expect(subject).to eq([closed_iteration, root_closed_iteration, started_group_iteration, root_group_iteration, upcoming_group_iteration])
+ expect(subject).to eq([closed_iteration, ancestor_closed_iteration, started_group_iteration, root_group_iteration, ancestor_group_iteration, upcoming_group_iteration])
end
end
@@ -300,13 +323,13 @@
it 'sorts correctly by cadence_and_due_date_asc' do
params[:sort] = :cadence_and_due_date_asc
- expect(subject).to eq([*cadence1_iterations, *cadence2_iterations, *cadence3_iterations])
+ expect(subject).to eq([*cadence1_iterations, *cadence2_iterations, *cadence3_iterations, *cadence5_iterations])
end
it 'sorts correctly by cadence_and_due_date_desc' do
params[:sort] = :cadence_and_due_date_desc
- expect(subject).to eq([*cadence1_iterations.reverse, *cadence2_iterations.reverse, *cadence3_iterations.reverse])
+ expect(subject).to eq([*cadence1_iterations.reverse, *cadence2_iterations.reverse, *cadence3_iterations.reverse, *cadence5_iterations.reverse])
end
end
end
diff --git a/ee/spec/graphql/resolvers/iterations_resolver_spec.rb b/ee/spec/graphql/resolvers/iterations_resolver_spec.rb
index 8b62cfcc802d9b1e119fedb0c2ea15c9dc479b68..5bb1d0f203f0e9eed63ae23f59f2da54a937fc94 100644
--- a/ee/spec/graphql/resolvers/iterations_resolver_spec.rb
+++ b/ee/spec/graphql/resolvers/iterations_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Resolvers::IterationsResolver do
+RSpec.describe Resolvers::IterationsResolver, feature_category: :team_planning do
include GraphqlHelpers
describe '#resolve' do
@@ -13,6 +13,7 @@
id: nil,
iid: nil,
iteration_cadence_ids: nil,
+ include_descendants: nil,
parent: nil,
state: nil,
search: nil,
@@ -114,7 +115,7 @@ def resolve_group_iterations(args = {}, obj = group, context = { current_user: c
iid = '2'
iteration_cadence_ids = ['5']
- params = params_list.merge(id: id, iid: iid, iteration_cadence_ids: iteration_cadence_ids, parent: group, include_ancestors: nil, state: 'closed', start_date: start_date, end_date: end_date, search: search, in: [:title])
+ params = params_list.merge(id: id, iid: iid, iteration_cadence_ids: iteration_cadence_ids, parent: group, include_ancestors: nil, include_descendants: nil, state: 'closed', start_date: start_date, end_date: end_date, search: search, in: [:title])
expect(IterationsFinder).to receive(:new).with(current_user, params).and_call_original
@@ -124,7 +125,7 @@ def resolve_group_iterations(args = {}, obj = group, context = { current_user: c
it 'accepts a raw model id for backward compatibility' do
id = '1'
iid = '2'
- params = params_list.merge(id: id, iid: iid, parent: group, include_ancestors: nil, state: 'all')
+ params = params_list.merge(id: id, iid: iid, parent: group, include_ancestors: nil, include_descendants: nil, state: 'all')
expect(IterationsFinder).to receive(:new).with(current_user, params).and_call_original
@@ -158,6 +159,22 @@ def resolve_group_iterations(args = {}, obj = group, context = { current_user: c
resolve_group_iterations({ include_ancestors: false }, subgroup)
end
+
+ it 'accepts include_descendants true' do
+ params = params_list.merge(parent: subgroup, include_ancestors: true, include_descendants: true, state: 'all')
+
+ expect(IterationsFinder).to receive(:new).with(current_user, params).and_call_original
+
+ resolve_group_iterations({ include_descendants: true }, subgroup)
+ end
+
+ it 'accepts include_descendants false' do
+ params = params_list.merge(parent: subgroup, include_ancestors: true, include_descendants: false, state: 'all')
+
+ expect(IterationsFinder).to receive(:new).with(current_user, params).and_call_original
+
+ resolve_group_iterations({ include_descendants: false }, subgroup)
+ end
end
context 'by timeframe' do
diff --git a/ee/spec/requests/api/iterations_spec.rb b/ee/spec/requests/api/iterations_spec.rb
index 7832b975c0304badf66ffc65bca65c7dd6119a45..3d9af47c8c7d3768e5246c463b1387adeb7d19f0 100644
--- a/ee/spec/requests/api/iterations_spec.rb
+++ b/ee/spec/requests/api/iterations_spec.rb
@@ -6,6 +6,7 @@
let_it_be(:user) { create(:user) }
let_it_be(:parent_group) { create(:group, :private) }
let_it_be(:group) { create(:group, :private, parent: parent_group) }
+ let_it_be(:subgroup) { create(:group, :private, parent: group) }
let_it_be(:current_iteration) do
create(
@@ -26,6 +27,10 @@
create(:iteration, :with_due_date, group: parent_group, start_date: 2.weeks.from_now, updated_at: 10.days.ago)
end
+ let_it_be(:descendant_iteration) do
+ create(:iteration, :with_due_date, group: subgroup, start_date: 2.weeks.from_now, updated_at: 10.days.ago)
+ end
+
before_all do
current_iteration.iterations_cadence.update!(title: "abc")
parent_group.add_guest(user)
@@ -161,6 +166,14 @@
expect(json_response.size).to eq(2)
expect(json_response.map { |i| i['id'] }).to contain_exactly(current_iteration.id, closed_iteration.id)
end
+
+ it 'includes descendant iterations when include_descendants is set to true' do
+ get api(api_path, user), params: { include_ancestors: false, include_descendants: true }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.size).to eq(3)
+ expect(json_response.map { |i| i['id'] }).to contain_exactly(current_iteration.id, closed_iteration.id, descendant_iteration.id)
+ end
end
describe 'GET /projects/:id/iterations' do
@@ -170,11 +183,18 @@
it_behaves_like 'iterations list'
- it 'return direct parent group iterations when include_ancestors is set to false' do
+ it 'excludes ancestor iterations of direct parent group when include_ancestors is set to false' do
get api(api_path, user), params: { include_ancestors: false }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.map { |i| i['id'] }).to contain_exactly(current_iteration.id, closed_iteration.id)
end
+
+ it 'includes descendant iterations of direct parent group when include_descendants is set to true' do
+ get api(api_path, user), params: { include_ancestors: false, include_descendants: true }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response.map { |i| i['id'] }).to contain_exactly(current_iteration.id, closed_iteration.id, descendant_iteration.id)
+ end
end
end