Commit c916bb3c authored by Heinrich Lee Yu's avatar Heinrich Lee Yu

Merge branch '39545-cleanup-dynamic-milestone-pages-ee' into 'master'

Remove deprecated dashboard & group milestone pages

See merge request gitlab-org/gitlab!13237
parents d00e8e24 8a29bcbd
......@@ -51,13 +51,7 @@ module MilestoneActions
}
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def milestone_redirect_path
if @milestone.global_milestone?
url_for(action: :show, title: @milestone.title)
else
url_for(action: :show)
end
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
end
# frozen_string_literal: true
class Dashboard::MilestonesController < Dashboard::ApplicationController
include MilestoneActions
before_action :projects
before_action :groups, only: :index
before_action :milestone, only: [:show, :merge_requests, :participants, :labels]
def index
respond_to do |format|
format.html do
@milestone_states = Milestone.states_count(@projects.select(:id), @groups.select(:id))
@milestones = Kaminari.paginate_array(milestones).page(params[:page])
@milestone_states = Milestone.states_count(@projects.select(:id), groups.select(:id))
@milestones = milestones.page(params[:page])
end
format.json do
render json: milestones
end
render json: milestones.to_json(only: [:id, :title], methods: :name)
end
end
def show
end
private
def group_milestones
DashboardGroupMilestone.build_collection(groups, params)
end
# See [#39545](https://gitlab.com/gitlab-org/gitlab-foss/issues/39545) for info about the deprecation of dynamic milestones
def dynamic_milestones
DashboardMilestone.build_collection(@projects, params)
end
def milestones
@milestones = group_milestones + dynamic_milestones
end
def milestone
@milestone = DashboardMilestone.build(@projects, params[:title])
render_404 unless @milestone
MilestonesFinder.new(search_params).execute
end
def groups
@groups ||= GroupsFinder.new(current_user, all_available: false).execute
end
def search_params
params.permit(:state, :search_title).merge(group_ids: groups, project_ids: projects)
end
end
......@@ -13,10 +13,10 @@ class Groups::MilestonesController < Groups::ApplicationController
respond_to do |format|
format.html do
@milestone_states = Milestone.states_count(group_projects_with_access, [group])
@milestones = Kaminari.paginate_array(milestones).page(params[:page])
@milestones = milestones.page(params[:page])
end
format.json do
render json: milestones.map { |m| m.for_display.slice(:id, :title, :name) }
render json: milestones.to_json(only: [:id, :title], methods: :name)
end
end
end
......@@ -29,7 +29,7 @@ class Groups::MilestonesController < Groups::ApplicationController
@milestone = Milestones::CreateService.new(group, current_user, milestone_params).execute
if @milestone.persisted?
redirect_to milestone_path
redirect_to milestone_path(@milestone)
else
render "new"
end
......@@ -39,23 +39,15 @@ class Groups::MilestonesController < Groups::ApplicationController
end
def edit
render_404 if @milestone.legacy_group_milestone?
end
def update
# Keep this compatible with legacy group milestones where we have to update
# all projects milestones states at once.
milestones, update_params = get_milestones_for_update
milestones.each do |milestone|
Milestones::UpdateService.new(milestone.resource_parent, current_user, update_params).execute(milestone)
end
Milestones::UpdateService.new(@milestone.parent, current_user, milestone_params).execute(@milestone)
redirect_to milestone_path
redirect_to milestone_path(@milestone)
end
def destroy
return render_404 if @milestone.legacy_group_milestone?
Milestones::DestroyService.new(group, current_user).execute(@milestone)
respond_to do |format|
......@@ -66,14 +58,6 @@ class Groups::MilestonesController < Groups::ApplicationController
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!
return render_404 unless can?(current_user, :admin_milestone, group)
end
......@@ -82,27 +66,21 @@ class Groups::MilestonesController < Groups::ApplicationController
params.require(:milestone).permit(:title, :description, :start_date, :due_date, :state_event)
end
def legacy_milestone_params
params.require(:milestone).permit(:state_event)
def milestones
MilestonesFinder.new(search_params).execute
end
def milestone_path
if @milestone.legacy_group_milestone?
group_milestone_path(group, @milestone.safe_title, title: @milestone.title)
else
group_milestone_path(group, @milestone.iid)
end
def milestone
@milestone = group.milestones.find_by_iid(params[:id])
render_404 unless @milestone
end
def milestones
milestones = MilestonesFinder.new(search_params).execute
def search_params
groups = request.format.json? ? group_ids(include_ancestors: true) : group_ids
@sort = params[:sort] || 'due_date_asc'
MilestoneArray.sort(milestones + legacy_milestones, @sort)
end
def legacy_milestones
GroupMilestone.build_collection(group, group_projects_with_access, params)
params.permit(:state, :search_title).merge(sort: @sort, group_ids: groups, project_ids: group_projects_with_access)
end
def group_projects_with_access
......@@ -116,23 +94,6 @@ class Groups::MilestonesController < Groups::ApplicationController
group.self_and_descendants.public_or_visible_to_user(current_user).select(:id)
end
end
def milestone
@milestone =
if params[:title]
GroupMilestone.build(group, group_projects_with_access, params[:title])
else
group.milestones.find_by_iid(params[:id])
end
render_404 unless @milestone
end
def search_params
groups = request.format.json? ? group_ids(include_ancestors: true) : group_ids
params.permit(:state, :search_title).merge(group_ids: groups)
end
end
Groups::MilestonesController.prepend_if_ee('EE::Groups::MilestonesController')
......@@ -34,7 +34,7 @@ class Projects::MilestonesController < Projects::ApplicationController
@milestones = @milestones.page(params[:page])
end
format.json do
render json: @milestones.to_json(methods: :name)
render json: @milestones.to_json(only: [:id, :title], methods: :name)
end
end
end
......
......@@ -58,10 +58,8 @@ class MilestonesFinder
Milestone.filter_by_state(items, params[:state])
end
# rubocop: disable CodeReuse/ActiveRecord
def order(items)
order_statement = Gitlab::Database.nulls_last_order('due_date', 'ASC')
items.reorder(order_statement).order('title ASC')
sort_by = params[:sort].presence || 'due_date_asc'
items.sort_by_attribute(sort_by)
end
# rubocop: enable CodeReuse/ActiveRecord
end
......@@ -229,12 +229,8 @@ module TimeboxesHelper
alias_method :milestone_date_range, :timebox_date_range
def milestone_tab_path(milestone, tab)
if milestone.global_milestone?
url_for(action: tab, title: milestone.title, format: :json)
else
url_for(action: tab, format: :json)
end
end
def update_milestone_path(milestone, params = {})
if milestone.project_milestone?
......@@ -247,12 +243,8 @@ module TimeboxesHelper
def group_milestone_route(milestone, params = {})
params = nil if params.empty?
if milestone.legacy_group_milestone?
group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: params)
else
group_milestone_path(milestone.group, milestone.iid, milestone: params)
end
end
def group_or_project_milestone_path(milestone)
params =
......
......@@ -97,26 +97,6 @@ module Milestoneish
due_date && due_date.past?
end
def group_milestone?
false
end
def project_milestone?
false
end
def legacy_group_milestone?
false
end
def dashboard_milestone?
false
end
def global_milestone?
false
end
def total_time_spent
@total_time_spent ||= issues.joins(:timelogs).sum(:time_spent) + merge_requests.joins(:timelogs).sum(:time_spent)
end
......
# frozen_string_literal: true
# Dashboard Group Milestones are milestones that allow us to pull more info out for the UI that the Milestone object doesn't allow for
class DashboardGroupMilestone < GlobalMilestone
extend ::Gitlab::Utils::Override
attr_reader :group_name
def initialize(milestone)
super
@group_name = milestone.group.full_name
end
def self.build_collection(groups, params)
milestones = Milestone.of_groups(groups.select(:id))
.reorder_by_due_date_asc
.order_by_name_asc
milestones = milestones.search_title(params[:search_title]) if params[:search_title].present?
Milestone.filter_by_state(milestones, params[:state]).map { |m| new(m) }
end
def dashboard_milestone?
true
end
def merge_requests_enabled?
true
end
end
# frozen_string_literal: true
class DashboardMilestone < GlobalMilestone
attr_reader :project_name
def initialize(milestone)
super
@project_name = milestone.project.full_name
end
def project_milestone?
true
end
def merge_requests_enabled?
project.merge_requests_enabled?
end
end
# frozen_string_literal: true
# Global Milestones are milestones that can be shared across multiple projects
class GlobalMilestone
include Milestoneish
STATE_COUNT_HASH = { opened: 0, closed: 0, all: 0 }.freeze
attr_reader :milestone
alias_attribute :name, :title
delegate :title, :state, :due_date, :start_date, :participants, :project,
:group, :expires_at, :closed?, :iid, :group_milestone?, :safe_title,
:timebox_id, :milestoneish_id, :resource_parent, :releases, to: :milestone
def to_hash
{
name: title,
title: title,
group_name: group&.full_name,
project_name: project&.full_name
}
end
def for_display
@milestone
end
def self.build_collection(projects, params)
items = Milestone.of_projects(projects)
.reorder_by_due_date_asc
.order_by_name_asc
items = items.search_title(params[:search_title]) if params[:search_title].present?
Milestone.filter_by_state(items, params[:state]).map { |m| new(m) }
end
# necessary for legacy milestones
def self.build(projects, title)
milestones = Milestone.of_projects(projects).where(title: title)
return if milestones.blank?
new(milestones.first)
end
def self.states_count(projects, group = nil)
legacy_group_milestones_count = legacy_group_milestone_states_count(projects)
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
def self.group_milestones_states_count(group)
return STATE_COUNT_HASH unless group
counts_by_state = Milestone.of_groups(group).count_by_state
{
opened: counts_by_state['active'] || 0,
closed: counts_by_state['closed'] || 0,
all: counts_by_state.values.sum
}
end
def self.legacy_group_milestone_states_count(projects)
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
def initialize(milestone)
@milestone = milestone
end
def active?
state == 'active'
end
def closed?
state == 'closed'
end
def issues
@issues ||= Issue.of_milestones(milestone).includes(:project, :assignees, :labels)
end
def merge_requests
@merge_requests ||= MergeRequest.of_milestones(milestone).includes(:target_project, :assignees, :labels)
end
def labels
@labels ||= GlobalLabel.build_collection(milestone.labels).sort_by!(&:title)
end
def global_milestone?
true
end
end
GlobalMilestone.include_if_ee('::EE::GlobalMilestone')
# frozen_string_literal: true
# Group Milestones are milestones that can be shared among many projects within the same group
class GroupMilestone < GlobalMilestone
attr_reader :group, :milestones
def self.build_collection(group, projects, params)
params =
{ state: params[:state], search_title: params[:search_title] }
project_milestones = Milestone.of_projects(projects)
project_milestones = project_milestones.search_title(params[:search_title]) if params[:search_title].present?
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
def self.build(group, projects, title)
child_milestones = Milestone.of_projects(projects).where(title: title)
return if child_milestones.blank?
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
def issues_finder_params
{ group_id: group.id }
end
def legacy_group_milestone?
true
end
def merge_requests_enabled?
true
end
end
GroupMilestone.include_if_ee('::EE::GroupMilestone')
= render 'shared/milestones/milestone',
milestone_path: group_or_project_milestone_path(milestone),
issues_path: issues_dashboard_path(milestone_title: milestone.title),
merge_requests_path: merge_requests_dashboard_path(milestone_title: milestone.title),
milestone: milestone,
......
- header_title "Milestones", dashboard_milestones_path
= render 'shared/milestones/top', milestone: @milestone
= render 'shared/milestones/tabs', milestone: @milestone, show_full_project_name: true
= render 'shared/milestones/sidebar', milestone: @milestone, affix_offset: 51
= render 'shared/milestones/milestone',
milestone_path: group_milestone_route(milestone),
issues_path: issues_group_path(@group, milestone_title: milestone.title),
merge_requests_path: merge_requests_group_path(@group, milestone_title: milestone.title),
milestone: milestone
......@@ -16,5 +16,8 @@
.nothing-here-block No milestones to show
- else
- @milestones.each do |milestone|
- if milestone.project_milestone?
= render 'projects/milestones/milestone', milestone: milestone
- else
= render 'milestone', milestone: milestone
= paginate @milestones, theme: "gitlab"
= render 'shared/milestones/milestone',
milestone_path: project_milestone_path(milestone.project, milestone),
issues_path: project_issues_path(milestone.project, milestone_title: milestone.title),
merge_requests_path: project_merge_requests_path(milestone.project, milestone_title: milestone.title),
milestone: milestone
......@@ -11,7 +11,6 @@
.milestone-buttons
- if can?(current_user, :admin_milestone, @group || @project)
- unless milestone.legacy_group_milestone?
= link_to _('Edit'), edit_milestone_path(milestone), class: 'btn btn-grouped'
- if milestone.project_milestone? && milestone.project.group
......@@ -31,7 +30,6 @@
- else
= link_to _('Reopen milestone'), update_milestone_path(milestone, { state_event: :activate }), method: :put, class: 'btn btn-grouped btn-reopen'
- unless milestone.legacy_group_milestone?
= render 'shared/milestones/delete_button'
%button.btn.btn-default.btn-grouped.float-right.d-block.d-sm-none.js-sidebar-toggle{ type: 'button' }
......
......@@ -6,11 +6,10 @@
.row
.col-sm-6
.append-bottom-5
%strong= link_to truncate(milestone.title, length: 100), milestone_path
%strong= link_to truncate(milestone.title, length: 100), milestone_path(milestone)
- if @group
= " - #{milestone_type}"
- if @project || milestone.is_a?(GlobalMilestone) || milestone.group_milestone?
- if milestone.due_date || milestone.start_date
.text-tertiary.append-bottom-5
= milestone_date_range(milestone)
......@@ -31,12 +30,7 @@
- if milestone.group_milestone?
.label-badge.label-badge-blue.d-inline-block
= milestone.group.full_name
- if milestone.legacy_group_milestone?
.projects
- link_to milestone_path(milestone.milestone) do
%span.label-badge.label-badge-blue.d-inline-block.append-bottom-5
= dashboard ? milestone.project.full_name : milestone.project.name
- if milestone.project
- if milestone.project_milestone?
.label-badge.label-badge-gray.d-inline-block
= milestone.project.full_name
......@@ -49,8 +43,7 @@
.float-lg-right.light #{milestone.percent_complete}% complete
.col-sm-2
.milestone-actions.d-flex.justify-content-sm-start.justify-content-md-end
- if @project
- if can_admin_project_milestones? and milestone.active?
- if @project # if in milestones list on project level
- if can_admin_group_milestones?
%button.js-promote-project-milestone-button.btn.btn-blank.btn-sm.btn-grouped.has-tooltip{ title: s_('Milestones|Promote to Group Milestone'),
disabled: true,
......@@ -63,15 +56,12 @@
toggle: 'modal' } }
= sprite_icon('level-up', size: 14)
= link_to s_('Milestones|Close Milestone'), project_milestone_path(@project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-sm btn-close btn-grouped"
- unless milestone.active?
= link_to s_('Milestones|Reopen Milestone'), project_milestone_path(@project, milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen"
- if @group
- if can?(current_user, :admin_milestone, @group)
- if can?(current_user, :admin_milestone, milestone)
- if milestone.closed?
= link_to s_('Milestones|Reopen Milestone'), group_milestone_route(milestone, {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen"
= link_to s_('Milestones|Reopen Milestone'), milestone_path(milestone, milestone: { state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen"
- else
= link_to s_('Milestones|Close Milestone'), group_milestone_route(milestone, {state_event: :close }), method: :put, class: "btn btn-sm btn-grouped btn-close"
= link_to s_('Milestones|Close Milestone'), milestone_path(milestone, milestone: { state_event: :close }), method: :put, class: "btn btn-sm btn-grouped btn-close"
- if dashboard
.label-badge.label-badge-gray
= milestone_type
- page_title milestone.title
- @breadcrumb_link = dashboard_milestone_path(milestone.safe_title, title: milestone.title)
- @breadcrumb_link = milestone_path(milestone)
- group = local_assigns[:group]
- is_dynamic_milestone = milestone.legacy_group_milestone? || milestone.dashboard_milestone?
= render 'shared/milestones/header', milestone: milestone
= render 'shared/milestones/deprecation_message' if is_dynamic_milestone
= render 'shared/milestones/description', milestone: milestone
- if milestone.complete? && milestone.active?
......@@ -15,26 +13,3 @@
= group ? _('You may close the milestone now.') : _('Navigate to the project to close the milestone.')
= render_if_exists 'shared/milestones/burndown', milestone: milestone, project: @project
- if is_dynamic_milestone
.table-holder
%table.table
%thead
%tr
%th= _('Project')
%th= _('Open issues')
%th= _('State')
%th= _('Due date')
%tr
%td
- project_name = group ? milestone.project.name : milestone.project.full_name
= link_to project_name, milestone_path(milestone.milestone)
%td
= milestone.milestone.issues_visible_to_user(current_user).opened.count
%td
- if milestone.closed?
= _('Closed')
- else
= _('Open')
%td
= milestone.expires_at
---
title: Remove deprecated dashboard & group milestone pages
merge_request: 13237
author:
type: removed
......@@ -5,13 +5,7 @@ resource :dashboard, controller: 'dashboard', only: [] do
get :activity
scope module: :dashboard do
resources :milestones, only: [:index, :show] do
member do
get :merge_requests
get :participants
get :labels
end
end
resources :milestones, only: [:index]
resources :labels, only: [:index]
resources :groups, only: [:index]
......
......@@ -5,9 +5,9 @@ module EE
module MilestonesController
extend ::Gitlab::Utils::Override
override :legacy_milestones
def legacy_milestones
params[:only_group_milestones] ? [] : super
override :search_params
def search_params
params[:only_group_milestones].present? ? super.merge(project_ids: []) : super
end
end
end
......
# frozen_string_literal: true
module EE
module GlobalMilestone
def supports_weight?
false
end
# Legacy group milestones or dashboard milestones (grouped by title)
# can't present Burndown charts since they don't have
# proper limits set.
def supports_burndown_charts?
false
end
end
end
# frozen_string_literal: true
module EE
module GroupMilestone
def supports_weight?
group&.feature_available?(:issue_weights)
end
end
end
......@@ -70,23 +70,4 @@ describe 'Burndown charts', :js do
expect(page).to have_content('Improve milestones with Burndown Charts')
end
end
describe 'grouped by title milestones' do
let(:group) { nil }
let(:project) { create(:project) }
before do
project.add_maintainer(current_user)
end
it 'does not present burndown chart or promotion' do
allow(License).to receive(:current) { nil }
allow(Gitlab::CurrentSettings).to receive(:should_check_namespace_plan?) { true }
visit dashboard_milestone_path(milestone.safe_title, title: milestone.title)
expect(page).not_to have_css('.burndown-chart')
expect(page).not_to have_content('Improve milestones with Burndown Charts')
end
end
end
# frozen_string_literal: true
module MilestoneArray
class << self
def sort(array, sort_method)
case sort_method
when 'due_date_asc'
sort_asc_nulls_last(array, 'due_date')
when 'due_date_desc'
sort_desc_nulls_last(array, 'due_date')
when 'start_date_asc'
sort_asc_nulls_last(array, 'start_date')
when 'start_date_desc'
sort_desc_nulls_last(array, 'start_date')
when 'name_asc'
sort_asc(array, 'title')
when 'name_desc'
sort_asc(array, 'title').reverse
else
array
end
end
private
def sort_asc_nulls_last(array, attribute)
attribute = attribute.to_sym
array.select(&attribute).sort_by(&attribute) + array.reject(&attribute)
end
def sort_desc_nulls_last(array, attribute)
attribute = attribute.to_sym
array.select(&attribute).sort_by(&attribute).reverse + array.reject(&attribute)
end
def sort_asc(array, attribute)
array.sort_by(&attribute.to_sym)
end
end
end
......@@ -20735,9 +20735,6 @@ msgstr ""
msgid "Starts at (UTC)"
msgstr ""
msgid "State"
msgstr ""
msgid "State your message to activate"
msgstr ""
......
......@@ -8,12 +8,7 @@ describe Dashboard::MilestonesController do
let(:user) { create(:user) }
let(:project_milestone) { create(:milestone, project: project) }
let(:group_milestone) { create(:milestone, group: group) }
let(:milestone) do
DashboardMilestone.build(
[project],
project_milestone.title
)
end
let(:milestone) { create(:milestone, group: group) }
let(:issue) { create(:issue, project: project, milestone: project_milestone) }
let(:group_issue) { create(:issue, milestone: group_milestone, project: create(:project, group: group)) }
......@@ -28,22 +23,6 @@ describe Dashboard::MilestonesController do
group.add_developer(user)
end
it_behaves_like 'milestone tabs'
describe "#show" do
render_views
def view_milestone
get :show, params: { id: milestone.safe_title, title: milestone.title }
end
it 'shows milestone page' do
view_milestone
expect(response).to have_gitlab_http_status(:ok)
end
end
describe "#index" do
let(:public_group) { create(:group, :public) }
let!(:public_milestone) { create(:milestone, group: public_group) }
......@@ -58,7 +37,6 @@ describe Dashboard::MilestonesController do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.size).to eq(2)
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)
end
it 'returns closed group and project milestones to which the user belongs' do
......@@ -67,7 +45,6 @@ describe Dashboard::MilestonesController do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.size).to eq(2)
expect(json_response.map { |i| i["name"] }).to match_array([closed_group_milestone.name, closed_project_milestone.name])
expect(json_response.map { |i| i["group_name"] }.compact).to match_array(group.name)
end
it 'searches legacy project milestones by title when search_title is given' do
......
......@@ -8,15 +8,7 @@ describe Groups::MilestonesController do
let!(:project2) { create(:project, group: group) }
let(:user) { create(:user) }
let(:title) { '肯定不是中文的问题' }
let(:milestone) do
project_milestone = create(:milestone, project: project)
GroupMilestone.build(
group,
[project],
project_milestone.title
)
end
let(:milestone) { create(:milestone, project: project) }
let(:milestone_path) { group_milestone_path(group, milestone.safe_title, title: milestone.title) }
let(:milestone_params) do
......@@ -168,17 +160,16 @@ describe Groups::MilestonesController do
context 'as JSON' do
let!(:milestone) { create(:milestone, group: group, title: 'group milestone') }
let!(:legacy_milestone1) { create(:milestone, project: project, title: 'legacy') }
let!(:legacy_milestone2) { create(:milestone, project: project2, title: 'legacy') }
let!(:project_milestone1) { create(:milestone, project: project, title: 'same name') }
let!(:project_milestone2) { create(:milestone, project: project2, title: 'same name') }
it 'lists legacy group milestones and group milestones' do
it 'lists project and group milestones' do
get :index, params: { group_id: group.to_param }, format: :json
milestones = json_response
expect(milestones.count).to eq(2)
expect(milestones.first["title"]).to eq("group milestone")
expect(milestones.second["title"]).to eq("legacy")
expect(milestones.count).to eq(3)
expect(milestones.collect { |m| m['title'] }).to match_array(['same name', 'same name', 'group milestone'])
expect(response).to have_gitlab_http_status(:ok)
expect(response.content_type).to eq 'application/json'
end
......@@ -191,8 +182,9 @@ describe Groups::MilestonesController do
get :index, params: { group_id: group.to_param }, format: :json
milestones = json_response
expect(milestones.count).to eq(3)
expect(milestones.second["title"]).to eq("subgroup milestone")
milestone_titles = milestones.map { |m| m['title'] }
expect(milestones.count).to eq(4)
expect(milestone_titles).to match_array(['same name', 'same name', 'group milestone', 'subgroup milestone'])
end
end
......@@ -218,31 +210,18 @@ describe Groups::MilestonesController do
end
describe '#show' do
let(:milestone1) { create(:milestone, project: project, title: 'legacy') }
let(:milestone2) { create(:milestone, project: project, title: 'legacy') }
let(:group_milestone) { create(:milestone, group: group) }
render_views
context 'when there is a title parameter' do
it 'searches for a legacy group milestone' do
expect(GroupMilestone).to receive(:build)
expect(Milestone).not_to receive(:find_by_iid)
let!(:group_milestone) { create(:milestone, group: group) }
get :show, params: { group_id: group.to_param, id: title, title: milestone1.safe_title }
end
end
it 'renders for a group milestone' do
get :show, params: { group_id: group.to_param, id: group_milestone.iid }
context 'when there is not a title parameter' do
it 'searches for a group milestone' do
expect(GlobalMilestone).not_to receive(:build)
expect(Milestone).to receive(:find_by_iid)
get :show, params: { group_id: group.to_param, id: group_milestone.id }
end
expect(response).to have_gitlab_http_status(:ok)
expect(response.body).to include(group_milestone.title)
end
end
it_behaves_like 'milestone tabs'
describe "#create" do
it "creates group milestone with Chinese title" do
post :create,
......@@ -277,34 +256,6 @@ describe Groups::MilestonesController do
expect(response).to redirect_to(group_milestone_path(group, milestone.iid))
expect(milestone.title).to eq("title changed")
end
context "legacy group milestones" do
let!(:milestone1) { create(:milestone, project: project, title: 'legacy milestone', description: "old description") }
let!(:milestone2) { create(:milestone, project: project2, title: 'legacy milestone', description: "old description") }
it "updates only group milestones state" do
milestone_params[:title] = "title changed"
milestone_params[:description] = "description changed"
milestone_params[:state_event] = "close"
put :update,
params: {
id: milestone1.title.to_slug.to_s,
group_id: group.to_param,
milestone: milestone_params,
title: milestone1.title
}
expect(response).to redirect_to(group_milestone_path(group, milestone1.safe_title, title: milestone1.title))
[milestone1, milestone2].each do |milestone|
milestone.reload
expect(milestone.title).to eq("legacy milestone")
expect(milestone.description).to eq("old description")
expect(milestone.state).to eq("closed")
end
end
end
end
describe "#destroy" do
......
# frozen_string_literal: true
require 'spec_helper'
describe 'Dashboard milestone tabs', :js do
let(:user) { create(:user) }
let(:project) { create(:project) }
let!(:label) { create(:label, project: project) }
let(:project_milestone) { create(:milestone, project: project) }
let(:milestone) do
DashboardMilestone.build(
[project],
project_milestone.title
)
end
let!(:merge_request) { create(:labeled_merge_request, source_project: project, target_project: project, milestone: project_milestone, labels: [label]) }
before do
project.add_maintainer(user)
sign_in(user)
visit dashboard_milestone_path(milestone.safe_title, title: milestone.title)
end
it 'loads merge requests async' do
click_link 'Merge Requests'
expect(page).to have_selector('.milestone-merge_requests-list')
end
it 'loads participants async' do
click_link 'Participants'
expect(page).to have_selector('#tab-participants .bordered-list')
end
it 'loads labels async' do
click_link 'Labels'
expect(page).to have_selector('#tab-labels .bordered-list')
end
end
......@@ -102,11 +102,9 @@ describe 'Group milestones' do
expect(find('.top-area .all .badge').text).to eq("6")
end
it 'lists legacy group milestones and group milestones' do
legacy_milestone = GroupMilestone.build_collection(group, group.projects, { state: 'active' }).first
it 'lists group and project milestones' do
expect(page).to have_selector("#milestone_#{active_group_milestone.id}", count: 1)
expect(page).to have_selector("#milestone_#{legacy_milestone.milestone.id}", count: 1)
expect(page).to have_selector("#milestone_#{active_project_milestone2.id}", count: 1)
end
it 'shows milestone detail and supports its edit' do
......@@ -125,75 +123,35 @@ describe 'Group milestones' do
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_link(
'v1.0',
href: project_milestone_path(project, active_project_milestone1)
)
expect(page).to have_link(
'1 Issue',
href: issues_group_path(group, milestone_title: 'v1.0')
href: project_issues_path(project, milestone_title: 'v1.0')
)
expect(page).to have_link(
'0 Merge Requests',
href: merge_requests_group_path(group, milestone_title: 'v1.0')
href: project_merge_requests_path(project, milestone_title: 'v1.0')
)
expect(page).to have_link(
'GL-113',
href: group_milestone_path(group, active_group_milestone)
)
expect(page).to have_link(
'0 Issues',
href: issues_group_path(group, milestone_title: 'GL-113')
)
expect(page).to have_link(
'0 Merge Requests',
href: merge_requests_group_path(group, milestone_title: 'GL-113')
)
end
it 'renders group milestone details' do
click_link 'v1.0'
expect(page).to have_content('expires on Aug 20, 2114')
expect(page).to have_content('v1.0')
expect(page).to have_content('Issues 1 Open: 1 Closed: 0')
expect(page).to have_link(issue.title, href: project_issue_path(issue.project, issue))
end
end
end
describe 'milestone tabs', :js do
context 'for a legacy group milestone' do
let_it_be(:milestone) { create(:milestone, project: project) }
let_it_be(:label) { create(:label, project: project) }
let_it_be(:issue) { create(:labeled_issue, project: project, milestone: milestone, labels: [label], assignees: [create(:user)]) }
let_it_be(:mr) { create(:merge_request, source_project: project, milestone: milestone) }
before do
visit group_milestone_path(group, milestone.title, title: milestone.title)
end
it 'renders the issues tab' do
within('#tab-issues') do
expect(page).to have_content issue.title
end
end
it 'renders the merge requests tab' do
within('.js-milestone-tabs') do
click_link('Merge Requests')
end
within('#tab-merge-requests') do
expect(page).to have_content mr.title
end
end
it 'renders the participants tab' do
within('.js-milestone-tabs') do
click_link('Participants')
end
within('#tab-participants') do
expect(page).to have_content issue.assignees.first.name
end
end
it 'renders the labels tab' do
within('.js-milestone-tabs') do
click_link('Labels')
end
within('#tab-labels') do
expect(page).to have_content label.title
end
end
end
context 'for a group milestone' do
let_it_be(:other_project) { create(:project_empty_repo, group: group) }
let_it_be(:milestone) { create(:milestone, group: group) }
......
......@@ -24,21 +24,12 @@ describe 'Milestones sorting', :js do
# assert default sorting
within '.milestones' do
expect(page.all('ul.content-list > li').first.text).to include('v2.0')
expect(page.all('ul.content-list > li')[1].text).to include('v3.0')
expect(page.all('ul.content-list > li').last.text).to include('v1.0')
expect(page.all('ul.content-list > li strong > a').map(&:text)).to eq(['v2.0', 'v2.0', 'v3.0', 'v1.0', 'v1.0'])
end
click_button 'Due soon'
sort_options = find('ul.dropdown-menu-sort li').all('a').collect(&:text)
expect(sort_options[0]).to eq('Due soon')
expect(sort_options[1]).to eq('Due later')
expect(sort_options[2]).to eq('Start soon')
expect(sort_options[3]).to eq('Start later')
expect(sort_options[4]).to eq('Name, ascending')
expect(sort_options[5]).to eq('Name, descending')
expect(find('ul.dropdown-menu-sort li').all('a').map(&:text)).to eq(['Due soon', 'Due later', 'Start soon', 'Start later', 'Name, ascending', 'Name, descending'])
click_link 'Due later'
......@@ -46,9 +37,7 @@ describe 'Milestones sorting', :js do
# assert descending sorting
within '.milestones' do
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').last.text).to include('v2.0')
expect(page.all('ul.content-list > li strong > a').map(&:text)).to eq(['v1.0', 'v1.0', 'v3.0', 'v2.0', 'v2.0'])
end
end
end
......@@ -111,20 +111,6 @@ describe 'Milestone' do
end
end
describe 'deprecation popover', :js do
it 'opens deprecation popover' do
milestone = create(:milestone, project: project)
visit group_milestone_path(group, milestone, title: milestone.title)
expect(page).to have_selector('.milestone-deprecation-message')
find('.milestone-deprecation-message .js-popover-link').click
expect(page).to have_selector('.popover')
end
end
describe 'reopen closed milestones' do
before do
create(:milestone, :closed, project: project)
......
# frozen_string_literal: true
require 'spec_helper'
describe MilestoneArray do
let(:object1) { instance_double("BirdMilestone", due_date: Time.now, start_date: Time.now - 15.days, title: 'v2.0') }
let(:object2) { instance_double("CatMilestone", due_date: Time.now - 1.day, start_date: nil, title: 'v1.0') }
let(:object3) { instance_double("DogMilestone", due_date: nil, start_date: Time.now - 30.days, title: 'v3.0') }
let(:array) { [object1, object3, object2] }
describe '#sort' do
it 'reorders array with due date in ascending order with nulls last' do
expect(described_class.sort(array, 'due_date_asc')).to eq([object2, object1, object3])
end
it 'reorders array with due date in desc order with nulls last' do
expect(described_class.sort(array, 'due_date_desc')).to eq([object1, object2, object3])
end
it 'reorders array with start date in ascending order with nulls last' do
expect(described_class.sort(array, 'start_date_asc')).to eq([object3, object1, object2])
end
it 'reorders array with start date in descending order with nulls last' do
expect(described_class.sort(array, 'start_date_desc')).to eq([object1, object3, object2])
end
it 'reorders array with title in ascending order' do
expect(described_class.sort(array, 'name_asc')).to eq([object2, object1, object3])
end
it 'reorders array with title in descending order' do
expect(described_class.sort(array, 'name_desc')).to eq([object3, object1, object2])
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe GlobalMilestone do
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:group) { create(:group) }
let(:project1) { create(:project, group: group) }
let(:project2) { create(:project, path: 'gitlab-ci', group: group) }
let(:project3) { create(:project, path: 'cookbook-gitlab', group: group) }
describe '.build_collection' do
let(:milestone1_due_date) { 2.weeks.from_now.to_date }
let!(:milestone1_project1) do
create(
:milestone,
title: "Milestone v1.2",
project: project1,
due_date: milestone1_due_date
)
end
let!(:milestone1_project2) do
create(
:milestone,
title: "Milestone v1.2",
project: project2,
due_date: milestone1_due_date
)
end
let!(:milestone1_project3) do
create(
:milestone,
title: "Milestone v1.2",
project: project3,
due_date: milestone1_due_date
)
end
let!(:milestone2_project1) do
create(
:milestone,
title: "VD-123",
project: project1,
due_date: nil
)
end
let!(:milestone2_project2) do
create(
:milestone,
title: "VD-123",
project: project2,
due_date: nil
)
end
let!(:milestone2_project3) do
create(
:milestone,
title: "VD-123",
project: project3,
due_date: nil
)
end
let!(:projects) do
[
project1,
project2,
project3
]
end
let!(:global_milestones) { described_class.build_collection(projects, {}) }
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
it 'filters milestones by search_title when params[:search_title] is present' do
global_milestones = described_class.build_collection(projects, { search_title: 'v1.2' })
expect(global_milestones.map(&:title)).to match_array(['Milestone v1.2', 'Milestone v1.2', 'Milestone v1.2'])
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(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
context 'when the projects do not have milestones' do
before do
project1
end
it 'returns 0 as the quantity of global milestones in each state' do
expected_count = { opened: 0, closed: 0, all: 0 }
count = described_class.states_count(Project.all)
expect(count).to eq(expected_count)
end
end
end
describe '#initialize' do
let(:milestone1_project1) { create(:milestone, title: "Milestone v1.2", project: project1) }
subject(:global_milestone) { described_class.new(milestone1_project1) }
it 'has exactly one group milestone' do
expect(global_milestone.title).to eq('Milestone v1.2')
end
it 'has all project milestones with the same title' do
expect(global_milestone.milestone).to eq(milestone1_project1)
end
end
describe '#safe_title' do
let(:milestone) { create(:milestone, title: "git / test", project: project1) }
it 'strips out slashes and spaces' do
global_milestone = described_class.new(milestone)
expect(global_milestone.safe_title).to eq('git-test')
end
end
describe '#state' do
context 'when at least one milestone is active' do
it 'returns active' do
title = 'Active Group Milestone'
global_milestone = described_class.new(create(:active_milestone, title: title))
expect(global_milestone.state).to eq('active')
end
end
context 'when all milestones are closed' do
it 'returns closed' do
title = 'Closed Group Milestone'
global_milestone = described_class.new(create(:closed_milestone, title: title))
expect(global_milestone.state).to eq('closed')
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe GroupMilestone do
let(:group) { create(:group) }
let(:project) { create(:project, group: group) }
let(:project_milestone) do
create(:milestone, title: "Milestone v1.2", project: project)
end
describe '.build' do
it 'returns milestone with group assigned' do
milestone = described_class.build(
group,
[project],
project_milestone.title
)
expect(milestone.group).to eq group
end
end
describe '.build_collection' do
let(:group) { create(:group) }
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
it 'returns array of milestones, each with group assigned' do
milestones = described_class.build_collection(group, [project], {})
expect(milestones).to all(have_attributes(group: group))
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
......@@ -2,15 +2,7 @@
RSpec.shared_examples 'milestone tabs' do
def go(path, extra_params = {})
params =
case milestone
when DashboardMilestone
{ id: milestone.safe_title, title: milestone.title }
when GroupMilestone
{ group_id: group.to_param, id: milestone.safe_title, title: milestone.title }
else
{ namespace_id: project.namespace.to_param, project_id: project, id: milestone.iid }
end
params = { namespace_id: project.namespace.to_param, project_id: project, id: milestone.iid }
get path, params: params.merge(extra_params)
end
......
......@@ -12,22 +12,6 @@ describe 'shared/milestones/_top.html.haml' do
allow(milestone).to receive(:milestone) { milestone }
end
it 'renders a deprecation message for a legacy milestone' do
allow(milestone).to receive(:legacy_group_milestone?) { true }
render 'shared/milestones/top', milestone: milestone
expect(rendered).to have_css('.milestone-deprecation-message')
end
it 'renders a deprecation message for a dashboard milestone' do
allow(milestone).to receive(:dashboard_milestone?) { true }
render 'shared/milestones/top', milestone: milestone
expect(rendered).to have_css('.milestone-deprecation-message')
end
it 'does not render a deprecation message for a non-legacy and non-dashboard milestone' do
assign :group, group
......
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