From fafd94ff4634a3218f73bcf2b56db762f6758535 Mon Sep 17 00:00:00 2001 From: Brett Walker Date: Wed, 1 Nov 2023 15:45:54 -0500 Subject: [PATCH] Add include_descendants to iteration list API for both REST and GraphQL APIs Changelog: changed EE: true --- doc/api/graphql/reference/index.md | 2 + doc/api/group_iterations.md | 18 +++-- doc/api/iterations.md | 18 +++-- ee/app/finders/iterations_finder.rb | 13 +++- .../graphql/resolvers/iterations_resolver.rb | 4 + ee/lib/api/iterations.rb | 6 ++ ee/spec/finders/iterations_finder_spec.rb | 73 ++++++++++++------- .../resolvers/iterations_resolver_spec.rb | 23 +++++- ee/spec/requests/api/iterations_spec.rb | 22 +++++- 9 files changed, 133 insertions(+), 46 deletions(-) diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index a8dc50834d4669..3bdf10db0e4f16 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 b8cb1b7e053f30..c522992fdea722 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 ef718fffe0a5cc..98d596895be582 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 d6f2082f6ec307..ddf28d9b8b3101 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 4f8a4d8cf46e08..384be6e3070366 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 a22c04140ebaaf..3fb09e6ee5b46e 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 40bd0d707d1852..5397f0cc3a5491 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 8b62cfcc802d9b..5bb1d0f203f0e9 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 7832b975c0304b..3d9af47c8c7d37 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 -- GitLab