Add project milestone link to dashboard milestones

One of the steps to deprecate dashboard milestones.
Links do dashboard milestone are replaced with links for each
project milestone
This commit is contained in:
Fatih Acet 2018-12-20 12:14:33 +01:00
parent f7ac8041f7
commit e9ef02096b
No known key found for this signature in database
GPG key ID: E994FE39E29B7E11
20 changed files with 246 additions and 175 deletions

View file

@ -45,9 +45,4 @@
&.status-box-upcoming { &.status-box-upcoming {
background: $gl-text-color-secondary; background: $gl-text-color-secondary;
} }
&.status-box-milestone {
color: $gl-text-color;
background: $gray-darker;
}
} }

View file

@ -1,3 +1,5 @@
$status-box-line-height: 26px;
.issues-sortable-list .str-truncated { .issues-sortable-list .str-truncated {
max-width: 90%; max-width: 90%;
} }
@ -38,6 +40,7 @@
font-size: $tooltip-font-size; font-size: $tooltip-font-size;
margin-top: 0; margin-top: 0;
margin-right: $gl-padding-4; margin-right: $gl-padding-4;
line-height: $status-box-line-height;
@include media-breakpoint-down(xs) { @include media-breakpoint-down(xs) {
line-height: unset; line-height: unset;

View file

@ -43,14 +43,7 @@ class Groups::MilestonesController < Groups::ApplicationController
def update def update
# Keep this compatible with legacy group milestones where we have to update # Keep this compatible with legacy group milestones where we have to update
# all projects milestones states at once. # all projects milestones states at once.
if @milestone.legacy_group_milestone? milestones, update_params = get_milestones_for_update
update_params = milestone_params.select { |key| key == "state_event" }
milestones = @milestone.milestones
else
update_params = milestone_params
milestones = [@milestone]
end
milestones.each do |milestone| milestones.each do |milestone|
Milestones::UpdateService.new(milestone.parent, current_user, update_params).execute(milestone) Milestones::UpdateService.new(milestone.parent, current_user, update_params).execute(milestone)
end end
@ -71,6 +64,14 @@ class Groups::MilestonesController < Groups::ApplicationController
private private
def get_milestones_for_update
if @milestone.legacy_group_milestone?
[@milestone.milestones, legacy_milestone_params]
else
[[@milestone], milestone_params]
end
end
def authorize_admin_milestones! def authorize_admin_milestones!
return render_404 unless can?(current_user, :admin_milestone, group) return render_404 unless can?(current_user, :admin_milestone, group)
end end
@ -79,6 +80,10 @@ class Groups::MilestonesController < Groups::ApplicationController
params.require(:milestone).permit(:title, :description, :start_date, :due_date, :state_event) params.require(:milestone).permit(:title, :description, :start_date, :due_date, :state_event)
end end
def legacy_milestone_params
params.require(:milestone).permit(:state_event)
end
def milestone_path def milestone_path
if @milestone.legacy_group_milestone? if @milestone.legacy_group_milestone?
group_milestone_path(group, @milestone.safe_title, title: @milestone.title) group_milestone_path(group, @milestone.safe_title, title: @milestone.title)

View file

@ -237,12 +237,15 @@ module MilestonesHelper
end end
end end
def group_or_dashboard_milestone_path(milestone) def group_or_project_milestone_path(milestone)
if milestone.group_milestone? params =
group_milestone_path(milestone.group, milestone.iid, milestone: { title: milestone.title }) if milestone.group_milestone?
else { milestone: { title: milestone.title } }
dashboard_milestone_path(milestone.safe_title, title: milestone.title) else
end { title: milestone.title }
end
milestone_path(milestone.milestone, params)
end end
def can_admin_project_milestones? def can_admin_project_milestones?

View file

@ -42,7 +42,7 @@ module Milestoneish
def issues_visible_to_user(user) def issues_visible_to_user(user)
memoize_per_user(user, :issues_visible_to_user) do memoize_per_user(user, :issues_visible_to_user) do
IssuesFinder.new(user, issues_finder_params) IssuesFinder.new(user, issues_finder_params)
.execute.preload(:assignees).where(milestone_id: milestoneish_ids) .execute.preload(:assignees).where(milestone_id: milestoneish_id)
end end
end end

View file

@ -7,7 +7,7 @@ class DashboardGroupMilestone < GlobalMilestone
override :initialize override :initialize
def initialize(milestone) def initialize(milestone)
super(milestone.title, Array(milestone)) super
@group_name = milestone.group.full_name @group_name = milestone.group.full_name
end end
@ -19,22 +19,4 @@ class DashboardGroupMilestone < GlobalMilestone
.active .active
.map { |m| new(m) } .map { |m| new(m) }
end end
override :group_milestone?
def group_milestone?
@first_milestone.group_milestone?
end
override :milestoneish_ids
def milestoneish_ids
milestones.map(&:id)
end
def group
@first_milestone.group
end
def iid
@first_milestone.iid
end
end end

View file

@ -1,11 +1,15 @@
# frozen_string_literal: true # frozen_string_literal: true
class DashboardMilestone < GlobalMilestone class DashboardMilestone < GlobalMilestone
def issues_finder_params attr_reader :project_name
{ authorized_only: true }
def initialize(milestone)
super
@project_name = milestone.project.full_name
end end
def dashboard_milestone? def project_milestone?
true true
end end
end end

View file

@ -3,69 +3,78 @@
class GlobalMilestone class GlobalMilestone
include Milestoneish include Milestoneish
EPOCH = DateTime.parse('1970-01-01')
STATE_COUNT_HASH = { opened: 0, closed: 0, all: 0 }.freeze STATE_COUNT_HASH = { opened: 0, closed: 0, all: 0 }.freeze
attr_accessor :title, :milestones attr_reader :milestone
alias_attribute :name, :title alias_attribute :name, :title
delegate :title, :state, :due_date, :start_date, :participants, :project, :group, :expires_at, :closed?, :iid, :group_milestone?, :safe_title, :milestoneish_id, to: :milestone
def to_hash
{
name: title,
title: title,
group_name: group&.full_name,
project_name: project&.full_name
}
end
def for_display def for_display
@first_milestone @milestone
end end
def self.build_collection(projects, params) def self.build_collection(projects, params)
params = items = Milestone.of_projects(projects)
{ project_ids: projects.map(&:id), state: params[:state] } .reorder_by_due_date_asc
.order_by_name_asc
child_milestones = MilestonesFinder.new(params).execute # rubocop: disable CodeReuse/Finder Milestone.filter_by_state(items, params[:state]).map { |m| new(m) }
milestones = child_milestones.select(:id, :title).group_by(&:title).map do |title, grouped|
milestones_relation = Milestone.where(id: grouped.map(&:id))
new(title, milestones_relation)
end
milestones.sort_by { |milestone| milestone.due_date || EPOCH }
end end
# necessary for legacy milestones
def self.build(projects, title) def self.build(projects, title)
child_milestones = Milestone.of_projects(projects).where(title: title) milestones = Milestone.of_projects(projects).where(title: title)
return if child_milestones.blank? return if milestones.blank?
new(title, child_milestones) new(milestones.first)
end end
def self.count_by_state(milestones_by_state_and_title, state) def self.states_count(projects, group = nil)
milestones_by_state_and_title.count do |(milestone_state, _), _| legacy_group_milestones_count = legacy_group_milestone_states_count(projects)
milestone_state == state group_milestones_count = group_milestones_states_count(group)
legacy_group_milestones_count.merge(group_milestones_count) do |k, legacy_group_milestones_count, group_milestones_count|
legacy_group_milestones_count + group_milestones_count
end end
end end
private_class_method :count_by_state
def initialize(title, milestones) def self.group_milestones_states_count(group)
@title = title return STATE_COUNT_HASH unless group
@name = title
@milestones = milestones counts_by_state = Milestone.of_groups(group).count_by_state
@first_milestone = milestones.find {|m| m.description.present? } || milestones.first
{
opened: counts_by_state['active'] || 0,
closed: counts_by_state['closed'] || 0,
all: counts_by_state.values.sum
}
end end
def milestoneish_ids def self.legacy_group_milestone_states_count(projects)
milestones.select(:id) return STATE_COUNT_HASH unless projects
# We need to reorder(nil) on the projects, because the controller passes them in sorted.
relation = Milestone.of_projects(projects.reorder(nil)).count_by_state
{
opened: relation['active'] || 0,
closed: relation['closed'] || 0,
all: relation.values.sum
}
end end
def safe_title def initialize(milestone)
@title.to_slug.normalize.to_s @milestone = milestone
end
def projects
@projects ||= Project.for_milestones(milestoneish_ids)
end
def state
milestones.each do |milestone|
return 'active' if milestone.state != 'closed'
end
'closed'
end end
def active? def active?
@ -77,37 +86,14 @@ class GlobalMilestone
end end
def issues def issues
@issues ||= Issue.of_milestones(milestoneish_ids).includes(:project, :assignees, :labels) @issues ||= Issue.of_milestones(milestone).includes(:project, :assignees, :labels)
end end
def merge_requests def merge_requests
@merge_requests ||= MergeRequest.of_milestones(milestoneish_ids).includes(:target_project, :assignee, :labels) @merge_requests ||= MergeRequest.of_milestones(milestone).includes(:target_project, :assignee, :labels)
end
def participants
@participants ||= milestones.map(&:participants).flatten.uniq
end end
def labels def labels
@labels ||= GlobalLabel.build_collection(milestones.includes(:labels).map(&:labels).flatten) @labels ||= GlobalLabel.build_collection(milestone.labels).sort_by!(&:title)
.sort_by!(&:title)
end
def due_date
return @due_date if defined?(@due_date)
@due_date =
if @milestones.all? { |x| x.due_date == @milestones.first.due_date }
@milestones.first.due_date
end
end
def start_date
return @start_date if defined?(@start_date)
@start_date =
if @milestones.all? { |x| x.start_date == @milestones.first.start_date }
@milestones.first.start_date
end
end end
end end

View file

@ -1,18 +1,35 @@
# frozen_string_literal: true # frozen_string_literal: true
# Group Milestones are milestones that can be shared among many projects within the same group # Group Milestones are milestones that can be shared among many projects within the same group
class GroupMilestone < GlobalMilestone class GroupMilestone < GlobalMilestone
attr_accessor :group attr_reader :group, :milestones
def self.build_collection(group, projects, params) def self.build_collection(group, projects, params)
super(projects, params).each do |milestone| params =
milestone.group = group { state: params[:state] }
project_milestones = Milestone.of_projects(projects)
child_milestones = Milestone.filter_by_state(project_milestones, params[:state])
grouped_milestones = child_milestones.group_by(&:title)
grouped_milestones.map do |title, grouped|
new(title, grouped, group)
end end
end end
def self.build(group, projects, title) def self.build(group, projects, title)
super(projects, title).tap do |milestone| child_milestones = Milestone.of_projects(projects).where(title: title)
milestone&.group = group return if child_milestones.blank?
end
new(title, child_milestones, group)
end
def initialize(title, milestones, group)
@milestones = milestones
@group = group
end
def milestone
@milestone ||= milestones.find { |m| m.description.present? } || milestones.first
end end
def issues_finder_params def issues_finder_params

View file

@ -94,6 +94,10 @@ class Milestone < ActiveRecord::Base
end end
end end
def count_by_state
reorder(nil).group(:state).count
end
def predefined?(milestone) def predefined?(milestone)
milestone == Any || milestone == Any ||
milestone == None || milestone == None ||
@ -215,7 +219,7 @@ class Milestone < ActiveRecord::Base
self.title self.title
end end
def milestoneish_ids def milestoneish_id
id id
end end

View file

@ -1,5 +1,5 @@
= render 'shared/milestones/milestone', = render 'shared/milestones/milestone',
milestone_path: group_or_dashboard_milestone_path(milestone), milestone_path: group_or_project_milestone_path(milestone),
issues_path: issues_dashboard_path(milestone_title: milestone.title), issues_path: issues_dashboard_path(milestone_title: milestone.title),
merge_requests_path: merge_requests_dashboard_path(milestone_title: milestone.title), merge_requests_path: merge_requests_dashboard_path(milestone_title: milestone.title),
milestone: milestone, milestone: milestone,

View file

@ -1,5 +1,5 @@
- dashboard = local_assigns[:dashboard] - dashboard = local_assigns[:dashboard]
- custom_dom_id = dom_id(milestone.try(:milestones) ? milestone.milestones.first : milestone) - custom_dom_id = dom_id(milestone.try(:milestone) ? milestone.milestone : milestone)
- milestone_type = milestone.group_milestone? ? 'Group Milestone' : 'Project Milestone' - milestone_type = milestone.group_milestone? ? 'Group Milestone' : 'Project Milestone'
%li{ class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: custom_dom_id } %li{ class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: custom_dom_id }
@ -21,10 +21,12 @@
= milestone.group.full_name = milestone.group.full_name
- if milestone.legacy_group_milestone? - if milestone.legacy_group_milestone?
.projects .projects
- milestone.milestones.each do |milestone| - link_to milestone_path(milestone.milestone) do
= link_to milestone_path(milestone) do %span.label-badge.label-badge-blue.d-inline-block.append-bottom-5
%span.label-badge.label-badge-blue.d-inline-block.append-bottom-5 = dashboard ? milestone.project.full_name : milestone.project.name
= dashboard ? milestone.project.full_name : milestone.project.name - if milestone.project
.label-badge.label-badge-gray.d-inline-block
= milestone.project.full_name
.col-sm-4.milestone-progress .col-sm-4.milestone-progress
= milestone_progress_bar(milestone) = milestone_progress_bar(milestone)
@ -58,5 +60,5 @@
- else - else
= link_to 'Close Milestone', group_milestone_route(milestone, {state_event: :close }), method: :put, class: "btn btn-sm btn-grouped btn-close" = link_to 'Close Milestone', group_milestone_route(milestone, {state_event: :close }), method: :put, class: "btn btn-sm btn-grouped btn-close"
- if dashboard - if dashboard
.status-box.status-box-milestone .label-badge.label-badge-gray
= milestone_type = milestone_type

View file

@ -62,20 +62,19 @@
%th Open issues %th Open issues
%th State %th State
%th Due date %th Due date
- milestone.milestones.each do |ms|
%tr %tr
%td %td
- project_name = group ? ms.project.name : ms.project.full_name - project_name = group ? milestone.project.name : milestone.project.full_name
= link_to project_name, project_milestone_path(ms.project, ms) = link_to project_name, milestone_path(milestone.milestone)
%td %td
= ms.issues_visible_to_user(current_user).opened.count = milestone.milestone.issues_visible_to_user(current_user).opened.count
%td %td
- if ms.closed? - if milestone.closed?
Closed Closed
- else - else
Open Open
%td %td
= ms.expires_at = milestone.expires_at
- elsif milestone.group_milestone? - elsif milestone.group_milestone?
%br %br
View View

View file

@ -0,0 +1,5 @@
---
title: Add project milestone link
merge_request: 22552
author:
type: added

View file

@ -52,7 +52,7 @@ describe Dashboard::MilestonesController do
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(json_response.size).to eq(2) expect(json_response.size).to eq(2)
expect(json_response.map { |i| i["first_milestone"]["id"] }).to match_array([group_milestone.id, project_milestone.id]) expect(json_response.map { |i| i["name"] }).to match_array([group_milestone.name, project_milestone.name])
expect(json_response.map { |i| i["group_name"] }.compact).to match_array(group.name) expect(json_response.map { |i| i["group_name"] }.compact).to match_array(group.name)
end end

View file

@ -64,7 +64,7 @@ describe Groups::MilestonesController do
context 'when there is a title parameter' do context 'when there is a title parameter' do
it 'searches for a legacy group milestone' do it 'searches for a legacy group milestone' do
expect(GlobalMilestone).to receive(:build) expect(GroupMilestone).to receive(:build)
expect(Milestone).not_to receive(:find_by_iid) expect(Milestone).not_to receive(:find_by_iid)
get :show, params: { group_id: group.to_param, id: title, title: milestone1.safe_title } get :show, params: { group_id: group.to_param, id: title, title: milestone1.safe_title }

View file

@ -81,7 +81,7 @@ describe 'Group milestones' do
description: 'Lorem Ipsum is simply dummy text' description: 'Lorem Ipsum is simply dummy text'
) )
end end
let!(:active_project_milestone2) { create(:milestone, project: other_project, state: 'active', title: 'v1.0') } let!(:active_project_milestone2) { create(:milestone, project: other_project, state: 'active', title: 'v1.1') }
let!(:closed_project_milestone1) { create(:milestone, project: project, state: 'closed', title: 'v2.0') } let!(:closed_project_milestone1) { create(:milestone, project: project, state: 'closed', title: 'v2.0') }
let!(:closed_project_milestone2) { create(:milestone, project: other_project, state: 'closed', title: 'v2.0') } let!(:closed_project_milestone2) { create(:milestone, project: other_project, state: 'closed', title: 'v2.0') }
let!(:active_group_milestone) { create(:milestone, group: group, state: 'active', title: 'GL-113') } let!(:active_group_milestone) { create(:milestone, group: group, state: 'active', title: 'GL-113') }
@ -104,7 +104,7 @@ describe 'Group milestones' do
legacy_milestone = GroupMilestone.build_collection(group, group.projects, { state: 'active' }).first legacy_milestone = GroupMilestone.build_collection(group, group.projects, { state: 'active' }).first
expect(page).to have_selector("#milestone_#{active_group_milestone.id}", count: 1) expect(page).to have_selector("#milestone_#{active_group_milestone.id}", count: 1)
expect(page).to have_selector("#milestone_#{legacy_milestone.milestones.first.id}", count: 1) expect(page).to have_selector("#milestone_#{legacy_milestone.milestone.id}", count: 1)
end end
it 'shows milestone detail and supports its edit' do it 'shows milestone detail and supports its edit' do
@ -121,6 +121,7 @@ describe 'Group milestones' do
it 'renders milestones' do it 'renders milestones' do
expect(page).to have_content('v1.0') expect(page).to have_content('v1.0')
expect(page).to have_content('v1.1')
expect(page).to have_content('GL-113') expect(page).to have_content('GL-113')
expect(page).to have_link( expect(page).to have_link(
'1 Issue', '1 Issue',

View file

@ -42,6 +42,7 @@ describe 'Milestones sorting', :js do
expect(page).to have_button('Due later') expect(page).to have_button('Due later')
# assert descending sorting
within '.milestones' do within '.milestones' do
expect(page.all('ul.content-list > li').first.text).to include('v1.0') expect(page.all('ul.content-list > li').first.text).to include('v1.0')
expect(page.all('ul.content-list > li')[1].text).to include('v3.0') expect(page.all('ul.content-list > li')[1].text).to include('v3.0')

View file

@ -65,56 +65,103 @@ describe GlobalMilestone do
) )
end end
before do let!(:projects) do
projects = [ [
project1, project1,
project2, project2,
project3 project3
] ]
@global_milestones = described_class.build_collection(projects, {})
end end
it 'has all project milestones' do let!(:global_milestones) { described_class.build_collection(projects, {}) }
expect(@global_milestones.count).to eq(2)
context 'when building a collection of milestones' do
it 'has all project milestones' do
expect(global_milestones.count).to eq(6)
end
it 'has all project milestones titles' do
expect(global_milestones.map(&:title)).to match_array(['Milestone v1.2', 'Milestone v1.2', 'Milestone v1.2', 'VD-123', 'VD-123', 'VD-123'])
end
it 'has all project milestones' do
expect(global_milestones.size).to eq(6)
end
it 'sorts collection by due date' do
expect(global_milestones.map(&:due_date)).to eq [milestone1_due_date, milestone1_due_date, milestone1_due_date, nil, nil, nil]
end
end end
it 'has all project milestones titles' do context 'when adding new milestones' do
expect(@global_milestones.map(&:title)).to match_array(['Milestone v1.2', 'VD-123']) it 'does not add more queries' do
control_count = ActiveRecord::QueryRecorder.new do
described_class.build_collection(projects, {})
end.count
create_list(:milestone, 3, project: project3)
expect do
described_class.build_collection(projects, {})
end.not_to exceed_all_query_limit(control_count)
end
end
end
describe '.states_count' do
context 'when the projects have milestones' do
before do
create(:closed_milestone, title: 'Active Group Milestone', project: project3)
create(:active_milestone, title: 'Active Group Milestone', project: project1)
create(:active_milestone, title: 'Active Group Milestone', project: project2)
create(:closed_milestone, title: 'Closed Group Milestone', project: project1)
create(:closed_milestone, title: 'Closed Group Milestone', project: project2)
create(:closed_milestone, title: 'Closed Group Milestone', project: project3)
create(:closed_milestone, title: 'Closed Group Milestone 4', group: group)
end
it 'returns the quantity of global milestones and group milestones in each possible state' do
expected_count = { opened: 2, closed: 5, all: 7 }
count = described_class.states_count(Project.all, group)
expect(count).to eq(expected_count)
end
it 'returns the quantity of global milestones in each possible state' do
expected_count = { opened: 2, closed: 4, all: 6 }
count = described_class.states_count(Project.all)
expect(count).to eq(expected_count)
end
end end
it 'has all project milestones' do context 'when the projects do not have milestones' do
expect(@global_milestones.map { |group_milestone| group_milestone.milestones.count }.sum).to eq(6) before do
end project1
end
it 'sorts collection by due date' do it 'returns 0 as the quantity of global milestones in each state' do
expect(@global_milestones.map(&:due_date)).to eq [nil, milestone1_due_date] expected_count = { opened: 0, closed: 0, all: 0 }
count = described_class.states_count(Project.all)
expect(count).to eq(expected_count)
end
end end
end end
describe '#initialize' do describe '#initialize' do
let(:milestone1_project1) { create(:milestone, title: "Milestone v1.2", project: project1) } let(:milestone1_project1) { create(:milestone, title: "Milestone v1.2", project: project1) }
let(:milestone1_project2) { create(:milestone, title: "Milestone v1.2", project: project2) } subject(:global_milestone) { described_class.new(milestone1_project1) }
let(:milestone1_project3) { create(:milestone, title: "Milestone v1.2", project: project3) }
before do
milestones =
[
milestone1_project1,
milestone1_project2,
milestone1_project3
]
milestones_relation = Milestone.where(id: milestones.map(&:id))
@global_milestone = described_class.new(milestone1_project1.title, milestones_relation)
end
it 'has exactly one group milestone' do it 'has exactly one group milestone' do
expect(@global_milestone.title).to eq('Milestone v1.2') expect(global_milestone.title).to eq('Milestone v1.2')
end end
it 'has all project milestones with the same title' do it 'has all project milestones with the same title' do
expect(@global_milestone.milestones.count).to eq(3) expect(global_milestone.milestone).to eq(milestone1_project1)
end end
end end
@ -122,7 +169,7 @@ describe GlobalMilestone do
let(:milestone) { create(:milestone, title: "git / test", project: project1) } let(:milestone) { create(:milestone, title: "git / test", project: project1) }
it 'strips out slashes and spaces' do it 'strips out slashes and spaces' do
global_milestone = described_class.new(milestone.title, Milestone.where(id: milestone.id)) global_milestone = described_class.new(milestone)
expect(global_milestone.safe_title).to eq('git-test') expect(global_milestone.safe_title).to eq('git-test')
end end
@ -132,11 +179,8 @@ describe GlobalMilestone do
context 'when at least one milestone is active' do context 'when at least one milestone is active' do
it 'returns active' do it 'returns active' do
title = 'Active Group Milestone' title = 'Active Group Milestone'
milestones = [
create(:active_milestone, title: title), global_milestone = described_class.new(create(:active_milestone, title: title))
create(:closed_milestone, title: title)
]
global_milestone = described_class.new(title, milestones)
expect(global_milestone.state).to eq('active') expect(global_milestone.state).to eq('active')
end end
@ -145,11 +189,8 @@ describe GlobalMilestone do
context 'when all milestones are closed' do context 'when all milestones are closed' do
it 'returns closed' do it 'returns closed' do
title = 'Closed Group Milestone' title = 'Closed Group Milestone'
milestones = [
create(:closed_milestone, title: title), global_milestone = described_class.new(create(:closed_milestone, title: title))
create(:closed_milestone, title: title)
]
global_milestone = described_class.new(title, milestones)
expect(global_milestone.state).to eq('closed') expect(global_milestone.state).to eq('closed')
end end

View file

@ -20,13 +20,36 @@ describe GroupMilestone do
end end
describe '.build_collection' do describe '.build_collection' do
before do let(:group) { create(:group) }
project_milestone let(:project1) { create(:project, group: group) }
let(:project2) { create(:project, path: 'gitlab-ci', group: group) }
let(:project3) { create(:project, path: 'cookbook-gitlab', group: group) }
let!(:projects) do
[
project1,
project2,
project3
]
end end
it 'returns array of milestones, each with group assigned' do it 'returns array of milestones, each with group assigned' do
milestones = described_class.build_collection(group, [project], {}) milestones = described_class.build_collection(group, [project], {})
expect(milestones).to all(have_attributes(group: group)) expect(milestones).to all(have_attributes(group: group))
end end
context 'when adding new milestones' do
it 'does not add more queries' do
control_count = ActiveRecord::QueryRecorder.new do
described_class.build_collection(group, projects, {})
end.count
create(:milestone, title: 'This title', project: project1)
expect do
described_class.build_collection(group, projects, {})
end.not_to exceed_all_query_limit(control_count)
end
end
end end
end end