Commit e3ebb7c4 authored by Simon Knox's avatar Simon Knox

More helpful empty states for milestones

Add description and new milestone button
Hide filters if no open or closed exist

Changelog: changed
parent ab63d511
......@@ -26,6 +26,7 @@ class Projects::MilestonesController < Projects::ApplicationController
respond_to do |format|
format.html do
@milestone_states = Milestone.states_count(@project)
# We need to show group milestones in the JSON response
# so that people can filter by and assign group milestones,
# but we don't need to show them on the project milestones page itself.
......
......@@ -63,21 +63,6 @@ module TimeboxesHelper
issues.size
end
# Returns count of milestones for different states
# Uses explicit hash keys as the 'opened' state URL params differs from the db value
# and we need to add the total
# rubocop: disable CodeReuse/ActiveRecord
def milestone_counts(milestones)
counts = milestones.reorder(nil).group(:state).count
{
opened: counts['active'] || 0,
closed: counts['closed'] || 0,
all: counts.values.sum || 0
}
end
# rubocop: enable CodeReuse/ActiveRecord
def milestone_progress_tooltip_text(milestone)
has_issues = milestone.total_issues_count > 0
......
......@@ -12,16 +12,29 @@
path: '-/milestones/new', label: 'New milestone',
include_groups: true, type: :milestones
.top-area
- if @milestone_states.any? { |name, count| count > 0 }
.top-area
= render 'shared/milestones_filter', counts: @milestone_states
.nav-controls
= render 'shared/milestones/search_form'
- if @milestones.blank?
= render 'shared/empty_states/milestones'
- else
- if @milestones.blank?
= render 'shared/empty_states/milestones_tab', active_tab: params[:state] do
- if current_user
.page-title-controls
= render 'shared/new_project_item_select',
path: '-/milestones/new', label: 'New milestone',
include_groups: true, type: :milestones
- else
.milestones
%ul.content-list
- @milestones.each do |milestone|
= render 'milestone', milestone: milestone
= paginate @milestones, theme: 'gitlab'
- else
= render 'shared/empty_states/milestones' do
- if current_user
.page-title-controls
= render 'shared/new_project_item_select',
path: '-/milestones/new', label: 'New milestone',
include_groups: true, type: :milestones
- page_title _("Milestones")
- add_page_specific_style 'page_bundles/milestone'
.top-area
- if @milestone_states.any? { |name, count| count > 0 }
.top-area
= render 'shared/milestones_filter', counts: @milestone_states
.nav-controls
......@@ -10,9 +11,12 @@
- if can?(current_user, :admin_milestone, @group)
= link_to _('New milestone'), new_group_milestone_path(@group), class: "btn gl-button btn-confirm", data: { qa_selector: "new_group_milestone_link" }
- if @milestones.blank?
= render 'shared/empty_states/milestones'
- else
- if @milestones.blank?
= render 'shared/empty_states/milestones_tab', learn_more_path: help_page_path('user/group/milestones') do
- if can?(current_user, :admin_milestone, @group)
.text-center
= link_to _('New milestone'), new_group_milestone_path(@group), class: "btn gl-button btn-confirm", data: { qa_selector: "new_group_milestone_link" }
- else
.milestones
%ul.content-list
- @milestones.each do |milestone|
......@@ -21,3 +25,8 @@
- else
= render 'milestone', milestone: milestone
= paginate @milestones, theme: "gitlab"
- else
= render 'shared/empty_states/milestones', learn_more_path: help_page_path('user/group/milestones') do
- if can?(current_user, :admin_milestone, @group)
.text-center
= link_to _('New milestone'), new_group_milestone_path(@group), class: "btn gl-button btn-confirm", data: { qa_selector: "new_group_milestone_link" }
- page_title _('Milestones')
- add_page_specific_style 'page_bundles/milestone'
.top-area
= render 'shared/milestones_filter', counts: milestone_counts(@project.milestones)
- if @milestone_states.any? { |name, count| count > 0 }
.top-area
= render 'shared/milestones_filter', counts: @milestone_states
.nav-controls
= render 'shared/milestones/search_form'
......@@ -11,9 +12,14 @@
= link_to new_project_milestone_path(@project), class: 'gl-button btn btn-confirm', data: { qa_selector: "new_project_milestone_link" }, title: _('New milestone') do
= _('New milestone')
- if @milestones.blank?
= render 'shared/empty_states/milestones'
- else
- if @milestones.blank?
= render 'shared/empty_states/milestones_tab' do
- if can?(current_user, :admin_milestone, @project)
.text-center
= link_to new_project_milestone_path(@project), class: 'gl-button btn btn-confirm', data: { qa_selector: "new_project_milestone_link" }, title: _('New milestone') do
= _('New milestone')
- else
.milestones
#js-delete-milestone-modal
#promote-milestone-modal
......@@ -22,3 +28,9 @@
= render @milestones
= paginate @milestones, theme: 'gitlab'
- else
= render 'shared/empty_states/milestones' do
- if can?(current_user, :admin_milestone, @project)
.text-center
= link_to new_project_milestone_path(@project), class: 'gl-button btn btn-confirm', data: { qa_selector: "new_project_milestone_link" }, title: _('New milestone') do
= _('New milestone')
- learn_more_path = local_assigns.fetch(:learn_more_path, help_page_path('user/project/milestones/index'))
- learn_more_link = link_to _('Learn more.'), learn_more_path
.row.empty-state
.col-12
.svg-content
= image_tag 'illustrations/milestone_burndown_chart.svg'
.col-12
.text-content
%h4.text-center= _('No milestones to show')
%h4= s_('Milestones|Use milestones to track issues and merge requests over a fixed period of time')
%p.state-description
= s_('Milestones|Organize issues and merge requests into a cohesive group, and set an optional start and due dates. %{learn_more_link}').html_safe % { learn_more_link: learn_more_link }
= yield
- learn_more_path = local_assigns.fetch(:learn_more_path, help_page_path('user/project/milestones/index'))
- learn_more_link = link_to _('Learn more.'), learn_more_path
- closed_tab_selected = params[:state] == 'closed'
.row.empty-state
.col-12
.svg-content
= image_tag 'illustrations/milestone_burndown_chart.svg'
.col-12
.text-content
- if closed_tab_selected
%h4.text-center= s_('Milestones|There are no closed milestones')
- else
%h4.text-center= s_('Milestones|There are no open milestones')
%p.state-description
= s_('Milestones|Create a milestone to better track your issues and merge requests. %{learn_more_link}').html_safe % { learn_more_link: learn_more_link }
= yield
......@@ -24091,6 +24091,9 @@ msgstr ""
msgid "Milestones|Completed Issues (closed)"
msgstr ""
msgid "Milestones|Create a milestone to better track your issues and merge requests. %{learn_more_link}"
msgstr ""
msgid "Milestones|Delete milestone"
msgstr ""
......@@ -24109,6 +24112,9 @@ msgstr ""
msgid "Milestones|Ongoing Issues (open and assigned)"
msgstr ""
msgid "Milestones|Organize issues and merge requests into a cohesive group, and set an optional start and due dates. %{learn_more_link}"
msgstr ""
msgid "Milestones|Project Milestone"
msgstr ""
......@@ -24127,12 +24133,21 @@ msgstr ""
msgid "Milestones|Reopen Milestone"
msgstr ""
msgid "Milestones|There are no closed milestones"
msgstr ""
msgid "Milestones|There are no open milestones"
msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
msgid "Milestones|Unstarted Issues (open and unassigned)"
msgstr ""
msgid "Milestones|Use milestones to track issues and merge requests over a fixed period of time"
msgstr ""
msgid "Minimum capacity to be available before we schedule more mirrors preemptively."
msgstr ""
......@@ -25208,9 +25223,6 @@ msgstr ""
msgid "No milestone"
msgstr ""
msgid "No milestones to show"
msgstr ""
msgid "No namespace"
msgstr ""
......
......@@ -66,7 +66,7 @@ RSpec.describe 'Group milestones' do
context 'when no milestones' do
it 'renders no milestones text' do
visit group_milestones_path(group)
expect(page).to have_content('No milestones to show')
expect(page).to have_content('Use milestones to track issues and merge requests')
end
end
......
......@@ -21,7 +21,7 @@ RSpec.describe "User deletes milestone", :js do
click_button("Delete")
click_button("Delete milestone")
expect(page).to have_content("No milestones to show")
expect(page).to have_content("Use milestones to track issues and merge requests over a fixed period of time")
visit(activity_project_path(project))
......
......@@ -24,34 +24,6 @@ RSpec.describe TimeboxesHelper do
end
end
describe '#milestone_counts' do
let(:project) { create(:project) }
let(:counts) { helper.milestone_counts(project.milestones) }
context 'when there are milestones' do
it 'returns the correct counts' do
create_list(:active_milestone, 2, project: project)
create(:closed_milestone, project: project)
expect(counts).to eq(opened: 2, closed: 1, all: 3)
end
end
context 'when there are only milestones of one type' do
it 'returns the correct counts' do
create_list(:active_milestone, 2, project: project)
expect(counts).to eq(opened: 2, closed: 0, all: 2)
end
end
context 'when there are no milestones' do
it 'returns the correct counts' do
expect(counts).to eq(opened: 0, closed: 0, all: 0)
end
end
end
describe "#group_milestone_route" do
let(:group) { build_stubbed(:group) }
let(:subgroup) { build_stubbed(:group, parent: group, name: "Test Subgrp") }
......
# frozen_string_literal: true
RSpec.shared_examples 'milestone empty states' do
include Devise::Test::ControllerHelpers
let_it_be(:user) { build(:user) }
let(:empty_state) { 'Use milestones to track issues and merge requests over a fixed period of time' }
before do
assign(:projects, [])
allow(view).to receive(:current_user).and_return(user)
end
context 'with no milestones' do
before do
assign(:milestones, [])
assign(:milestone_states, { opened: 0, closed: 0, all: 0 })
render
end
it 'shows empty state' do
expect(rendered).to have_content(empty_state)
end
it 'does not show tabs or searchbar' do
expect(rendered).not_to have_link('Open')
expect(rendered).not_to have_link('Closed')
expect(rendered).not_to have_link('All')
end
end
context 'with no open milestones' do
before do
allow(view).to receive(:milestone_path).and_return("/milestones/1")
assign(:milestones, [])
assign(:milestone_states, { opened: 0, closed: 1, all: 1 })
end
it 'shows tabs and searchbar', :aggregate_failures do
render
expect(rendered).not_to have_content(empty_state)
expect(rendered).to have_link('Open')
expect(rendered).to have_link('Closed')
expect(rendered).to have_link('All')
end
it 'shows empty state' do
render
expect(rendered).to have_content('There are no open milestones')
end
end
context 'with no closed milestones' do
before do
allow(view).to receive(:milestone_path).and_return("/milestones/1")
allow(view).to receive(:params).and_return(state: 'closed')
assign(:milestones, [])
assign(:milestone_states, { opened: 1, closed: 0, all: 1 })
end
it 'shows tabs and searchbar', :aggregate_failures do
render
expect(rendered).not_to have_content(empty_state)
expect(rendered).to have_link('Open')
expect(rendered).to have_link('Closed')
expect(rendered).to have_link('All')
end
it 'shows empty state on closed milestones' do
render
expect(rendered).to have_content('There are no closed milestones')
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'dashboard/milestones/index.html.haml' do
it_behaves_like 'milestone empty states'
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'groups/milestones/index.html.haml' do
it_behaves_like 'milestone empty states'
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'projects/milestones/index.html.haml' do
it_behaves_like 'milestone empty states'
end
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