Commit 0d29d658 authored by Heinrich Lee Yu's avatar Heinrich Lee Yu

Merge branch '250479-show-iteration-lists' into 'master'

Handle listing of issues inside iteration lists and moving of issues to / from iteration lists

See merge request gitlab-org/gitlab!49946
parents 40d9ac1f edfe463a
...@@ -2029,6 +2029,11 @@ type BoardList { ...@@ -2029,6 +2029,11 @@ type BoardList {
""" """
issuesCount: Int issuesCount: Int
"""
Iteration of the list
"""
iteration: Iteration
""" """
Label of the list Label of the list
""" """
......
...@@ -5351,6 +5351,20 @@ ...@@ -5351,6 +5351,20 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "iteration",
"description": "Iteration of the list",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Iteration",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "label", "name": "label",
"description": "Label of the list", "description": "Label of the list",
...@@ -325,6 +325,7 @@ Represents a list for an issue board. ...@@ -325,6 +325,7 @@ Represents a list for an issue board.
| `id` | ID! | ID (global ID) of the list | | `id` | ID! | ID (global ID) of the list |
| `issues` | IssueConnection | Board issues | | `issues` | IssueConnection | Board issues |
| `issuesCount` | Int | Count of issues in the list | | `issuesCount` | Int | Count of issues in the list |
| `iteration` | Iteration | Iteration of the list |
| `label` | Label | Label of the list | | `label` | Label | Label of the list |
| `limitMetric` | ListLimitMetric | The current limit metric for the list | | `limitMetric` | ListLimitMetric | The current limit metric for the list |
| `listType` | String! | Type of the list | | `listType` | String! | Type of the list |
......
...@@ -8,6 +8,8 @@ module EE ...@@ -8,6 +8,8 @@ module EE
prepended do prepended do
field :milestone, ::Types::MilestoneType, null: true, field :milestone, ::Types::MilestoneType, null: true,
description: 'Milestone of the list' description: 'Milestone of the list'
field :iteration, ::Types::IterationType, null: true,
description: 'Iteration of the list'
field :max_issue_count, GraphQL::INT_TYPE, null: true, field :max_issue_count, GraphQL::INT_TYPE, null: true,
description: 'Maximum number of issues in the list' description: 'Maximum number of issues in the list'
field :max_issue_weight, GraphQL::INT_TYPE, null: true, field :max_issue_weight, GraphQL::INT_TYPE, null: true,
...@@ -23,6 +25,10 @@ module EE ...@@ -23,6 +25,10 @@ module EE
::Gitlab::Graphql::Loaders::BatchModelLoader.new(::Milestone, object.milestone_id).find ::Gitlab::Graphql::Loaders::BatchModelLoader.new(::Milestone, object.milestone_id).find
end end
def iteration
::Gitlab::Graphql::Loaders::BatchModelLoader.new(::Iteration, object.iteration_id).find
end
def assignee def assignee
object.assignee? ? object.user : nil object.assignee? ? object.user : nil
end end
......
...@@ -8,6 +8,10 @@ module EE ...@@ -8,6 +8,10 @@ module EE
LIMIT_METRIC_TYPES = %w[all_metrics issue_count issue_weights].freeze LIMIT_METRIC_TYPES = %w[all_metrics issue_count issue_weights].freeze
# When adding a new licensed type, make sure to also add
# it on license.rb with the pattern "board_<list_type>_lists"
LICENSED_LIST_TYPES = %i[assignee milestone iteration].freeze
# ActiveSupport::Concern does not prepend the ClassMethods, # ActiveSupport::Concern does not prepend the ClassMethods,
# so we cannot call `super` if we use it. # so we cannot call `super` if we use it.
def self.prepended(base) def self.prepended(base)
...@@ -42,7 +46,7 @@ module EE ...@@ -42,7 +46,7 @@ module EE
unless: -> { board&.resource_parent&.feature_available?(:board_milestone_lists) } unless: -> { board&.resource_parent&.feature_available?(:board_milestone_lists) }
base.validates :list_type, base.validates :list_type,
exclusion: { in: %w[iteration], message: -> (_object, _data) { _('Iteration lists not available with your current license') } }, exclusion: { in: %w[iteration], message: -> (_object, _data) { _('Iteration lists not available with your current license') } },
unless: -> { board&.resource_parent&.feature_available?(:iterations) } unless: -> { board&.resource_parent&.feature_available?(:board_iteration_lists) }
base.scope :without_types, ->(list_types) { where.not(list_type: list_types) } base.scope :without_types, ->(list_types) { where.not(list_type: list_types) }
end end
......
...@@ -14,6 +14,7 @@ class License < ApplicationRecord ...@@ -14,6 +14,7 @@ class License < ApplicationRecord
EES_FEATURES = %i[ EES_FEATURES = %i[
audit_events audit_events
blocked_issues blocked_issues
board_iteration_lists
code_owners code_owners
code_review_analytics code_review_analytics
contribution_analytics contribution_analytics
......
...@@ -13,6 +13,7 @@ module EE ...@@ -13,6 +13,7 @@ module EE
unless list&.movable? || list&.closed? unless list&.movable? || list&.closed?
issues = without_assignees_from_lists(issues) issues = without_assignees_from_lists(issues)
issues = without_milestones_from_lists(issues) issues = without_milestones_from_lists(issues)
issues = without_iterations_from_lists(issues)
end end
case list&.list_type case list&.list_type
...@@ -20,6 +21,8 @@ module EE ...@@ -20,6 +21,8 @@ module EE
with_assignee(super) with_assignee(super)
when 'milestone' when 'milestone'
with_milestone(super) with_milestone(super)
when 'iteration'
with_iteration(super)
else else
super super
end end
...@@ -58,6 +61,18 @@ module EE ...@@ -58,6 +61,18 @@ module EE
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def all_iteration_lists
# Note that the names are very similar but these are different.
# One is a license name and the other is a feature flag
if parent.feature_available?(:board_iteration_lists) && ::Feature.enabled?(:iteration_board_lists, parent)
board.lists.iteration.where.not(iteration_id: nil)
else
::List.none
end
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def without_assignees_from_lists(issues) def without_assignees_from_lists(issues)
return issues if all_assignee_lists.empty? return issues if all_assignee_lists.empty?
...@@ -85,6 +100,14 @@ module EE ...@@ -85,6 +100,14 @@ module EE
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def without_iterations_from_lists(issues)
return issues if all_iteration_lists.empty?
issues.not_in_iterations(all_iteration_lists.select(:iteration_id))
end
# rubocop: enable CodeReuse/ActiveRecord
def with_assignee(issues) def with_assignee(issues)
issues.assigned_to(list.user) issues.assigned_to(list.user)
end end
...@@ -95,6 +118,10 @@ module EE ...@@ -95,6 +118,10 @@ module EE
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
def with_iteration(issues)
issues.in_iterations(list.iteration_id)
end
# Prevent filtering by milestone stubs # Prevent filtering by milestone stubs
# like Milestone::Upcoming, Milestone::Started etc # like Milestone::Upcoming, Milestone::Started etc
def has_valid_milestone? def has_valid_milestone?
......
...@@ -34,19 +34,28 @@ module EE ...@@ -34,19 +34,28 @@ module EE
assignee_ids = assignee_ids(issue) assignee_ids = assignee_ids(issue)
milestone_id = milestone_id(issue) milestone_id = milestone_id(issue)
{ movement_args = {
assignee_ids: assignee_ids, assignee_ids: assignee_ids,
milestone_id: milestone_id milestone_id: milestone_id
} }
movement_args[:sprint_id] = iteration_id(issue) if ::Feature.enabled?(:iteration_board_lists, parent)
movement_args
end end
def milestone_id(issue) def milestone_id(issue)
# We want to nullify the issue milestone.
return if moving_to_list.backlog? && moving_from_list.milestone? return if moving_to_list.backlog? && moving_from_list.milestone?
return moving_to_list.milestone_id if moving_to_list.milestone?
issue.milestone_id
end
def iteration_id(issue)
return if moving_to_list.backlog? && moving_from_list.iteration?
return moving_to_list.iteration_id if moving_to_list.iteration?
# Moving to a list which is not a 'milestone list' will keep issue.sprint_id
# the already existent milestone.
[issue.milestone_id, moving_to_list.milestone_id].compact.last
end end
def assignee_ids(issue) def assignee_ids(issue)
......
...@@ -19,16 +19,7 @@ module EE ...@@ -19,16 +19,7 @@ module EE
private private
def valid_license?(parent) def valid_license?(parent)
license_name = case type List::LICENSED_LIST_TYPES.exclude?(type) || parent.feature_available?(:"board_#{type}_lists")
when :assignee
:board_assignee_lists
when :milestone
:board_milestone_lists
when :iteration
:iterations
end
license_name.nil? || parent.feature_available?(license_name)
end end
def license_validation_error def license_validation_error
......
...@@ -6,10 +6,6 @@ module EE ...@@ -6,10 +6,6 @@ module EE
module ListService module ListService
extend ::Gitlab::Utils::Override extend ::Gitlab::Utils::Override
# When adding a new licensed type, make sure to also add
# it on license.rb with the pattern "board_<list_type>_lists"
LICENSED_LIST_TYPES = %i[assignee milestone].freeze
override :execute override :execute
def execute(board, create_default_lists: true) def execute(board, create_default_lists: true)
list_types = unavailable_list_types_for(board) list_types = unavailable_list_types_for(board)
...@@ -20,7 +16,10 @@ module EE ...@@ -20,7 +16,10 @@ module EE
private private
def unavailable_list_types_for(board) def unavailable_list_types_for(board)
(hidden_lists_for(board) + unlicensed_lists_for(board)).uniq list_types = hidden_lists_for(board) + unlicensed_lists_for(board)
list_types << ::List.list_types[:iteration] if ::Feature.disabled?(:iteration_board_lists, board.resource_parent)
list_types.uniq
end end
def hidden_lists_for(board) def hidden_lists_for(board)
...@@ -35,7 +34,7 @@ module EE ...@@ -35,7 +34,7 @@ module EE
def unlicensed_lists_for(board) def unlicensed_lists_for(board)
parent = board.resource_parent parent = board.resource_parent
LICENSED_LIST_TYPES.each_with_object([]) do |list_type, lists| List::LICENSED_LIST_TYPES.each_with_object([]) do |list_type, lists|
list_type_key = ::List.list_types[list_type] list_type_key = ::List.list_types[list_type]
lists << list_type_key unless parent&.feature_available?(:"board_#{list_type}_lists") lists << list_type_key unless parent&.feature_available?(:"board_#{list_type}_lists")
end end
......
...@@ -79,7 +79,7 @@ RSpec.describe Boards::ListsController do ...@@ -79,7 +79,7 @@ RSpec.describe Boards::ListsController do
context 'when license is available' do context 'when license is available' do
before do before do
stub_licensed_features(iterations: true) stub_licensed_features(board_iteration_lists: true)
end end
it 'returns a successful 200 response' do it 'returns a successful 200 response' do
...@@ -92,7 +92,7 @@ RSpec.describe Boards::ListsController do ...@@ -92,7 +92,7 @@ RSpec.describe Boards::ListsController do
context 'when license is unavailable' do context 'when license is unavailable' do
before do before do
stub_licensed_features(iterations: false) stub_licensed_features(board_iteration_lists: false)
end end
it 'returns an error' do it 'returns an error' do
......
...@@ -10,7 +10,12 @@ FactoryBot.define do ...@@ -10,7 +10,12 @@ FactoryBot.define do
factory :milestone_list, parent: :list do factory :milestone_list, parent: :list do
list_type { :milestone } list_type { :milestone }
label { nil } label { nil }
user { nil }
milestone milestone
end end
factory :iteration_list, parent: :list do
list_type { :iteration }
label { nil }
iteration
end
end end
...@@ -21,7 +21,7 @@ RSpec.describe Mutations::Boards::Lists::Create do ...@@ -21,7 +21,7 @@ RSpec.describe Mutations::Boards::Lists::Create do
end end
before do before do
stub_licensed_features(board_assignee_lists: true, board_milestone_lists: true, iterations: true) stub_licensed_features(board_assignee_lists: true, board_milestone_lists: true, board_iteration_lists: true)
end end
subject { mutation.resolve(board_id: board.to_global_id.to_s, **list_create_params) } subject { mutation.resolve(board_id: board.to_global_id.to_s, **list_create_params) }
...@@ -108,7 +108,7 @@ RSpec.describe Mutations::Boards::Lists::Create do ...@@ -108,7 +108,7 @@ RSpec.describe Mutations::Boards::Lists::Create do
context 'when feature unavailable' do context 'when feature unavailable' do
it 'returns an error' do it 'returns an error' do
stub_licensed_features(iterations: false) stub_licensed_features(board_iteration_lists: false)
expect(subject[:errors]).to include 'Iteration lists not available with your current license' expect(subject[:errors]).to include 'Iteration lists not available with your current license'
end end
......
...@@ -4,7 +4,7 @@ require 'spec_helper' ...@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe GitlabSchema.types['BoardList'] do RSpec.describe GitlabSchema.types['BoardList'] do
it 'has specific fields' do it 'has specific fields' do
expected_fields = %w[milestone max_issue_count max_issue_weight assignee total_weight] expected_fields = %w[milestone iteration max_issue_count max_issue_weight assignee total_weight]
expect(described_class).to include_graphql_fields(*expected_fields) expect(described_class).to include_graphql_fields(*expected_fields)
end end
......
...@@ -88,7 +88,7 @@ RSpec.describe List do ...@@ -88,7 +88,7 @@ RSpec.describe List do
it { is_expected.to validate_presence_of(:iteration) } it { is_expected.to validate_presence_of(:iteration) }
it 'is invalid when feature is not available' do it 'is invalid when feature is not available' do
stub_licensed_features(iterations: false) stub_licensed_features(board_iteration_lists: false)
expect(subject).to be_invalid expect(subject).to be_invalid
expect(subject.errors[:list_type]) expect(subject.errors[:list_type])
......
...@@ -22,6 +22,7 @@ RSpec.describe Boards::Issues::ListService, services: true do ...@@ -22,6 +22,7 @@ RSpec.describe Boards::Issues::ListService, services: true do
let_it_be(:p3) { create(:group_label, title: 'P3', group: group) } let_it_be(:p3) { create(:group_label, title: 'P3', group: group) }
let_it_be(:milestone) { create(:milestone, group: group) } let_it_be(:milestone) { create(:milestone, group: group) }
let_it_be(:iteration) { create(:iteration, group: group) }
let_it_be(:opened_issue1) { create(:labeled_issue, project: project, milestone: m1, weight: 9, title: 'Issue 1', labels: [bug]) } let_it_be(:opened_issue1) { create(:labeled_issue, project: project, milestone: m1, weight: 9, title: 'Issue 1', labels: [bug]) }
let_it_be(:opened_issue2) { create(:labeled_issue, project: project, milestone: m2, weight: 1, title: 'Issue 2', labels: [p2]) } let_it_be(:opened_issue2) { create(:labeled_issue, project: project, milestone: m2, weight: 1, title: 'Issue 2', labels: [p2]) }
...@@ -42,15 +43,16 @@ RSpec.describe Boards::Issues::ListService, services: true do ...@@ -42,15 +43,16 @@ RSpec.describe Boards::Issues::ListService, services: true do
let(:parent) { group } let(:parent) { group }
before do before do
stub_licensed_features(board_assignee_lists: true, board_milestone_lists: true) stub_licensed_features(board_assignee_lists: true, board_milestone_lists: true, board_iteration_lists: true)
parent.add_developer(user) parent.add_developer(user)
opened_issue3.assignees.push(user_list.user) opened_issue3.assignees.push(user_list.user)
end end
context 'with assignee, milestone and label lists present' do context 'with assignee, milestone, iteration and label lists present' do
let!(:user_list) { create(:user_list, board: board, position: 2) } let!(:user_list) { create(:user_list, board: board, position: 2) }
let!(:milestone_list) { create(:milestone_list, board: board, position: 3, milestone: milestone) } let!(:milestone_list) { create(:milestone_list, board: board, position: 3, milestone: milestone) }
let!(:iteration_list) { create(:iteration_list, board: board, position: 4, iteration: iteration) }
let!(:backlog) { create(:backlog_list, board: board) } let!(:backlog) { create(:backlog_list, board: board) }
let!(:list1) { create(:list, board: board, label: development, position: 0) } let!(:list1) { create(:list, board: board, label: development, position: 0) }
let!(:list2) { create(:list, board: board, label: testing, position: 1) } let!(:list2) { create(:list, board: board, label: testing, position: 1) }
...@@ -80,6 +82,46 @@ RSpec.describe Boards::Issues::ListService, services: true do ...@@ -80,6 +82,46 @@ RSpec.describe Boards::Issues::ListService, services: true do
end end
end end
context 'iteration lists' do
let!(:iteration_issue) { create(:labeled_issue, project: project, iteration: iteration, labels: [p3]) }
let(:params) { { board_id: board.id, id: iteration_list.id } }
subject(:issues) { described_class.new(parent.class.find(parent.id), user, params).execute }
it 'returns issues from iteration persisted in the list' do
expect(issues).to contain_exactly(iteration_issue)
end
context 'backlog list' do
let(:params) { { board_id: board.id, id: backlog.id } }
it 'excludes issues in the iteration list' do
expect(issues).not_to include(iteration_issue)
end
context 'when feature is disabled' do
before do
stub_licensed_features(board_iteration_lists: false)
end
it 'includes issues in the iteration list' do
expect(issues).to include(iteration_issue)
end
end
context 'when feature flag is disabled' do
before do
stub_feature_flags(iteration_board_lists: false)
end
it 'includes issues in the iteration list' do
expect(issues).to include(iteration_issue)
end
end
end
end
describe '#metadata' do describe '#metadata' do
it 'returns issues count and weight for list' do it 'returns issues count and weight for list' do
params = { board_id: board.id, id: backlog.id } params = { board_id: board.id, id: backlog.id }
......
...@@ -103,6 +103,87 @@ RSpec.describe Boards::Issues::MoveService, services: true do ...@@ -103,6 +103,87 @@ RSpec.describe Boards::Issues::MoveService, services: true do
end end
end end
shared_examples 'moving an issue to/from iteration lists' do
context 'from backlog to iteration list' do
let!(:issue) { create(:issue, project: project) }
let(:params) { { board_id: board1.id, from_list_id: backlog.id, to_list_id: iteration_list1.id } }
it 'assigns the iteration' do
expect { described_class.new(parent, user, params).execute(issue) }
.to change { issue.reload.iteration }
.from(nil)
.to(iteration_list1.iteration)
end
context 'when feature flag is disabled' do
before do
stub_feature_flags(iteration_board_lists: false)
end
it 'does not assign the iteration' do
expect { described_class.new(parent, user, params).execute(issue) }
.not_to change { issue.reload.iteration }
end
end
end
context 'from iteration to backlog list' do
let!(:issue) { create(:issue, project: project, iteration: iteration_list1.iteration) }
it 'removes the iteration' do
params = { board_id: board1.id, from_list_id: iteration_list1.id, to_list_id: backlog.id }
expect { described_class.new(parent, user, params).execute(issue) } .to change { issue.reload.iteration }
.from(iteration_list1.iteration)
.to(nil)
end
end
context 'from label to iteration list' do
let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) }
it 'assigns the iteration and keeps labels' do
params = { board_id: board1.id, from_list_id: label_list1.id, to_list_id: iteration_list1.id }
expect { described_class.new(parent, user, params).execute(issue) }
.to change { issue.reload.iteration }
.from(nil)
.to(iteration_list1.iteration)
expect(issue.labels).to contain_exactly(bug, development)
end
end
context 'from iteration to label list' do
let!(:issue) do
create(:labeled_issue, project: project,
iteration: iteration_list1.iteration,
labels: [bug, development])
end
it 'adds labels and keeps iteration' do
params = { board_id: board1.id, from_list_id: iteration_list1.id, to_list_id: label_list2.id }
expect { described_class.new(parent, user, params).execute(issue) }
.not_to change { issue.reload.iteration }
expect(issue.labels).to contain_exactly(bug, development, testing)
end
end
context 'between iteration lists' do
let!(:issue) { create(:issue, project: project, iteration: iteration_list1.iteration) }
it 'replaces previous list iteration to targeting list iteration' do
params = { board_id: board1.id, from_list_id: iteration_list1.id, to_list_id: iteration_list2.id }
expect { described_class.new(parent, user, params).execute(issue) }
.to change { issue.reload.iteration }
.from(iteration_list1.iteration)
.to(iteration_list2.iteration)
end
end
end
shared_examples 'moving an issue to/from assignee lists' do shared_examples 'moving an issue to/from assignee lists' do
let(:issue) { create(:labeled_issue, project: project, labels: [bug, development], milestone: milestone1) } let(:issue) { create(:labeled_issue, project: project, labels: [bug, development], milestone: milestone1) }
let(:params) { { board_id: board1.id, from_list_id: label_list1.id, to_list_id: label_list2.id } } let(:params) { { board_id: board1.id, from_list_id: label_list1.id, to_list_id: label_list2.id } }
...@@ -193,15 +274,20 @@ RSpec.describe Boards::Issues::MoveService, services: true do ...@@ -193,15 +274,20 @@ RSpec.describe Boards::Issues::MoveService, services: true do
let(:user_list2) { create(:user_list, board: board1, user: user, position: 3) } let(:user_list2) { create(:user_list, board: board1, user: user, position: 3) }
let(:milestone_list1) { create(:milestone_list, board: board1, milestone: milestone1, position: 4) } let(:milestone_list1) { create(:milestone_list, board: board1, milestone: milestone1, position: 4) }
let(:milestone_list2) { create(:milestone_list, board: board1, milestone: milestone2, position: 5) } let(:milestone_list2) { create(:milestone_list, board: board1, milestone: milestone2, position: 5) }
let(:iteration_list1) { create(:iteration_list, board: board1, iteration: iteration1, position: 6) }
let(:iteration_list2) { create(:iteration_list, board: board1, iteration: iteration2, position: 7) }
let(:closed) { create(:closed_list, board: board1) } let(:closed) { create(:closed_list, board: board1) }
let(:backlog) { create(:backlog_list, board: board1) } let(:backlog) { create(:backlog_list, board: board1) }
context 'when parent is a project' do context 'when parent is a project' do
let(:project) { create(:project) } let(:group) { create(:group) }
let(:project) { create(:project, namespace: group) }
let(:parent_attr) { { project: project } } let(:parent_attr) { { project: project } }
let(:parent) { project } let(:parent) { project }
let(:milestone1) { create(:milestone, project: project) } let(:milestone1) { create(:milestone, project: project) }
let(:milestone2) { create(:milestone, project: project) } let(:milestone2) { create(:milestone, project: project) }
let(:iteration1) { create(:iteration, group: group) }
let(:iteration2) { create(:iteration, group: group) }
let(:bug) { create(:label, project: project, name: 'Bug') } let(:bug) { create(:label, project: project, name: 'Bug') }
let(:development) { create(:label, project: project, name: 'Development') } let(:development) { create(:label, project: project, name: 'Development') }
...@@ -209,13 +295,14 @@ RSpec.describe Boards::Issues::MoveService, services: true do ...@@ -209,13 +295,14 @@ RSpec.describe Boards::Issues::MoveService, services: true do
let(:regression) { create(:label, project: project, name: 'Regression') } let(:regression) { create(:label, project: project, name: 'Regression') }
before do before do
stub_licensed_features(board_assignee_lists: true, board_milestone_lists: true) stub_licensed_features(board_assignee_lists: true, board_milestone_lists: true, board_iteration_lists: true)
parent.add_developer(user) parent.add_developer(user)
parent.add_developer(user_list1.user) parent.add_developer(user_list1.user)
end end
it_behaves_like 'moving an issue to/from assignee lists' it_behaves_like 'moving an issue to/from assignee lists'
it_behaves_like 'moving an issue to/from milestone lists' it_behaves_like 'moving an issue to/from milestone lists'
it_behaves_like 'moving an issue to/from iteration lists'
end end
context 'when parent is a group' do context 'when parent is a group' do
...@@ -225,6 +312,8 @@ RSpec.describe Boards::Issues::MoveService, services: true do ...@@ -225,6 +312,8 @@ RSpec.describe Boards::Issues::MoveService, services: true do
let(:parent) { group } let(:parent) { group }
let(:milestone1) { create(:milestone, group: group) } let(:milestone1) { create(:milestone, group: group) }
let(:milestone2) { create(:milestone, group: group) } let(:milestone2) { create(:milestone, group: group) }
let(:iteration1) { create(:iteration, group: group) }
let(:iteration2) { create(:iteration, group: group) }
let(:bug) { create(:group_label, group: group, name: 'Bug') } let(:bug) { create(:group_label, group: group, name: 'Bug') }
let(:development) { create(:group_label, group: group, name: 'Development') } let(:development) { create(:group_label, group: group, name: 'Development') }
...@@ -239,6 +328,7 @@ RSpec.describe Boards::Issues::MoveService, services: true do ...@@ -239,6 +328,7 @@ RSpec.describe Boards::Issues::MoveService, services: true do
it_behaves_like 'moving an issue to/from assignee lists' it_behaves_like 'moving an issue to/from assignee lists'
it_behaves_like 'moving an issue to/from milestone lists' it_behaves_like 'moving an issue to/from milestone lists'
it_behaves_like 'moving an issue to/from iteration lists'
context 'when moving to same list' do context 'when moving to same list' do
let(:subgroup) { create(:group, parent: group) } let(:subgroup) { create(:group, parent: group) }
......
...@@ -62,7 +62,7 @@ RSpec.describe Boards::Lists::CreateService do ...@@ -62,7 +62,7 @@ RSpec.describe Boards::Lists::CreateService do
subject(:service) { described_class.new(project, user, 'iteration_id' => iteration.id) } subject(:service) { described_class.new(project, user, 'iteration_id' => iteration.id) }
before do before do
stub_licensed_features(iterations: true) stub_licensed_features(board_iteration_lists: true)
end end
it 'creates an iteration list when param is valid' do it 'creates an iteration list when param is valid' do
...@@ -93,7 +93,7 @@ RSpec.describe Boards::Lists::CreateService do ...@@ -93,7 +93,7 @@ RSpec.describe Boards::Lists::CreateService do
end end
it 'returns an error when license is unavailable' do it 'returns an error when license is unavailable' do
stub_licensed_features(iterations: false) stub_licensed_features(board_iteration_lists: false)
response = service.execute(board) response = service.execute(board)
......
...@@ -4,6 +4,14 @@ require 'spec_helper' ...@@ -4,6 +4,14 @@ require 'spec_helper'
RSpec.describe Boards::Lists::ListService do RSpec.describe Boards::Lists::ListService do
describe '#execute' do describe '#execute' do
before do
stub_licensed_features(board_assignee_lists: false, board_milestone_lists: false, board_iteration_lists: false)
end
def execute_service
service.execute(Board.find(board.id))
end
shared_examples 'list service for board with assignee lists' do shared_examples 'list service for board with assignee lists' do
let!(:assignee_list) { build(:user_list, board: board).tap { |l| l.save(validate: false) } } let!(:assignee_list) { build(:user_list, board: board).tap { |l| l.save(validate: false) } }
let!(:backlog_list) { create(:backlog_list, board: board) } let!(:backlog_list) { create(:backlog_list, board: board) }
...@@ -11,18 +19,17 @@ RSpec.describe Boards::Lists::ListService do ...@@ -11,18 +19,17 @@ RSpec.describe Boards::Lists::ListService do
context 'when the feature is enabled' do context 'when the feature is enabled' do
before do before do
allow(board.resource_parent).to receive(:feature_available?).with(:board_assignee_lists).and_return(true) stub_licensed_features(board_assignee_lists: true)
allow(board.resource_parent).to receive(:feature_available?).with(:board_milestone_lists).and_return(false)
end end
it 'returns all lists' do it 'returns all lists' do
expect(service.execute(board)).to match_array [backlog_list, list, assignee_list, board.closed_list] expect(execute_service).to match_array [backlog_list, list, assignee_list, board.closed_list]
end end
end end
context 'when the feature is disabled' do context 'when the feature is disabled' do
it 'filters out assignee lists that might have been created while subscribed' do it 'filters out assignee lists that might have been created while subscribed' do
expect(service.execute(board)).to match_array [backlog_list, list, board.closed_list] expect(execute_service).to match_array [backlog_list, list, board.closed_list]
end end
end end
end end
...@@ -34,19 +41,51 @@ RSpec.describe Boards::Lists::ListService do ...@@ -34,19 +41,51 @@ RSpec.describe Boards::Lists::ListService do
context 'when the feature is enabled' do context 'when the feature is enabled' do
before do before do
allow(board.resource_parent).to receive(:feature_available?).with(:board_assignee_lists).and_return(false) stub_licensed_features(board_milestone_lists: true)
allow(board.resource_parent).to receive(:feature_available?).with(:board_milestone_lists).and_return(true)
end end
it 'returns all lists' do it 'returns all lists' do
expect(service.execute(board)) expect(execute_service)
.to match_array([backlog_list, list, milestone_list, board.closed_list]) .to match_array([backlog_list, list, milestone_list, board.closed_list])
end end
end end
context 'when the feature is disabled' do context 'when the feature is disabled' do
it 'filters out assignee lists that might have been created while subscribed' do it 'filters out assignee lists that might have been created while subscribed' do
expect(service.execute(board)).to match_array [backlog_list, list, board.closed_list] expect(execute_service).to match_array [backlog_list, list, board.closed_list]
end
end
end
shared_examples 'list service for board with iteration lists' do
let!(:iteration_list) { build(:iteration_list, board: board).tap { |l| l.save(validate: false) } }
let!(:backlog_list) { create(:backlog_list, board: board) }
let!(:list) { create(:list, board: board, label: label) }
context 'when the feature is enabled' do
before do
stub_licensed_features(board_iteration_lists: true)
end
it 'returns all lists' do
expect(execute_service)
.to match_array([backlog_list, list, iteration_list, board.closed_list])
end
context 'when the feature flag is disabled' do
before do
stub_feature_flags(iteration_board_lists: false)
end
it 'filters out iteration lists that might have been created while subscribed' do
expect(execute_service).to match_array [backlog_list, list, board.closed_list]
end
end
end
context 'when feature is disabled' do
it 'filters out iteration lists that might have been created while subscribed' do
expect(execute_service).to match_array [backlog_list, list, board.closed_list]
end end
end end
end end
...@@ -58,7 +97,7 @@ RSpec.describe Boards::Lists::ListService do ...@@ -58,7 +97,7 @@ RSpec.describe Boards::Lists::ListService do
it 'hides backlog list' do it 'hides backlog list' do
board.update(hide_backlog_list: true) board.update(hide_backlog_list: true)
expect(service.execute(board)).to match_array([board.closed_list, list]) expect(execute_service).to match_array([board.closed_list, list])
end end
end end
...@@ -66,7 +105,7 @@ RSpec.describe Boards::Lists::ListService do ...@@ -66,7 +105,7 @@ RSpec.describe Boards::Lists::ListService do
it 'hides closed list' do it 'hides closed list' do
board.update(hide_closed_list: true) board.update(hide_closed_list: true)
expect(service.execute(board)).to match_array([board.backlog_list, list]) expect(execute_service).to match_array([board.backlog_list, list])
end end
end end
end end
...@@ -80,6 +119,7 @@ RSpec.describe Boards::Lists::ListService do ...@@ -80,6 +119,7 @@ RSpec.describe Boards::Lists::ListService do
it_behaves_like 'list service for board with assignee lists' it_behaves_like 'list service for board with assignee lists'
it_behaves_like 'list service for board with milestone lists' it_behaves_like 'list service for board with milestone lists'
it_behaves_like 'list service for board with iteration lists'
it_behaves_like 'hidden lists' it_behaves_like 'hidden lists'
end end
...@@ -92,6 +132,7 @@ RSpec.describe Boards::Lists::ListService do ...@@ -92,6 +132,7 @@ RSpec.describe Boards::Lists::ListService do
it_behaves_like 'list service for board with assignee lists' it_behaves_like 'list service for board with assignee lists'
it_behaves_like 'list service for board with milestone lists' it_behaves_like 'list service for board with milestone lists'
it_behaves_like 'list service for board with iteration lists'
it_behaves_like 'hidden lists' it_behaves_like 'hidden lists'
end end
end end
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
RSpec.shared_examples 'iteration board list' do RSpec.shared_examples 'iteration board list' do
before do before do
stub_licensed_features(iterations: true) stub_licensed_features(board_iteration_lists: true)
end end
context 'when iteration_id is sent' do context 'when iteration_id is sent' do
...@@ -24,7 +24,7 @@ RSpec.shared_examples 'iteration board list' do ...@@ -24,7 +24,7 @@ RSpec.shared_examples 'iteration board list' do
end end
it 'returns 400 if not licensed' do it 'returns 400 if not licensed' do
stub_licensed_features(iterations: false) stub_licensed_features(board_iteration_lists: false)
post api(url, user), params: { iteration_id: iteration.id } post api(url, user), params: { iteration_id: iteration.id }
......
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