Commit 2f04dac8 authored by Annabel Dunstone Gray's avatar Annabel Dunstone Gray

Merge branch '7014-epic-status-tabs' into 'master'

Add Open/Closed epics tabs in list view

Closes #7014

See merge request gitlab-org/gitlab-ee!7424
parents 7bbe8949 69807e09
......@@ -128,7 +128,7 @@ class IssuableFinder
labels_count = 1 if use_cte_for_search?
finder.execute.reorder(nil).group(:state).count.each do |key, value|
counts[Array(key).last.to_sym] += value / labels_count
counts[count_key(key)] += value / labels_count
end
counts[:all] = counts.values.sum
......@@ -297,6 +297,10 @@ class IssuableFinder
klass.all
end
def count_key(value)
Array(value).last.to_sym
end
# rubocop: disable CodeReuse/ActiveRecord
def by_scope(items)
return items.none if current_user_related? && !current_user
......
......@@ -61,7 +61,7 @@
<template>
<div class="dropdown new-epic-dropdown">
<button
class="btn btn-success "
class="btn btn-success"
type="button"
data-toggle="dropdown"
@click="focusInput"
......@@ -83,7 +83,7 @@
:disabled="isCreatingDisabled"
:loading="creating"
:label="buttonLabel"
container-class="btn btn-success btn-inverted"
container-class="btn btn-success btn-inverted prepend-top-10"
@click.stop="createEpic"
/>
</div>
......
......@@ -10,7 +10,6 @@
.btn-success {
display: flex;
margin-top: $gl-btn-padding;
}
}
......
......@@ -101,7 +101,7 @@ class Groups::EpicsController < Groups::ApplicationController
# we need to override the default state which is opened for now because we don't have
# states for epics and need all as default for navigation to work correctly (#4017)
def set_default_state
params[:state] = 'all'
params[:state] = 'opened' if params[:state].blank?
end
def authorize_create_epic!
......
......@@ -11,28 +11,12 @@ class EpicsFinder < IssuableFinder
items = by_search(items)
items = by_author(items)
items = by_timeframe(items)
items = by_state(items)
items = by_label(items)
sort(items)
end
def row_count
count = execute.count
# When filtering by multiple labels, count returns a hash of
# records grouped by id - so we just have to get length of the Hash.
# Once we have state for epics, we can use default issuables row_count
# method.
count.is_a?(Hash) ? count.length : count
end
# we don't have states for epics for now this method (#4017)
def count_by_state
{
all: row_count
}
end
def group
return nil unless params[:group_id]
return @group if defined?(@group)
......@@ -53,6 +37,10 @@ class EpicsFinder < IssuableFinder
private
def count_key(value)
Epic.states.invert[Array(value).last].to_sym
end
# rubocop: disable CodeReuse/ActiveRecord
def groups_user_can_read_epics(groups)
groups = Gitlab::GroupPlansPreloader.new.preload(groups)
......
- has_filters_applied = params[:label_name].present? || params[:author_username].present? || params[:search].present?
- page_title "Epics"
- if has_filters_applied || @epics.to_a.any?
.top-area
= render 'shared/issuable/epic_nav', type: :epics
.nav-controls
- if can?(current_user, :create_epic, @group)
#new-epic-app{ data: { endpoint: request.url, 'align-right' => true } }
.top-area
= render 'shared/issuable/epic_nav', type: :epics
.nav-controls
- if can?(current_user, :create_epic, @group)
#new-epic-app{ data: { endpoint: request.url, 'align-right' => true } }
= render 'shared/epic/search_bar', type: :epics
= render 'shared/epic/search_bar', type: :epics
- if @epics.to_a.any?
= render 'shared/epics'
......
%ul.content-list.issuable-list
= render partial: 'groups/epics/epic', collection: @epics
.card.card-small.card-without-border
%ul.content-list.issuable-list
= render partial: 'groups/epics/epic', collection: @epics
= paginate @epics, theme: "gitlab"
......@@ -2,5 +2,13 @@
- page_context_word = type.to_s.humanize(capitalize: false)
- display_count = local_assigns.fetch(:display_count, :true)
%ul.nav-links.epics-state-filters
%ul.nav-links.mobile-separator.epics-state-filters
%li{ class: active_when(params[:state] == 'opened') }>
= link_to page_filter_path(state: 'opened', label: true), id: 'state-opened', title: (_("Filter by %{issuable_type} that are currently opened.") % { issuable_type: page_context_word }), data: { state: 'opened' } do
#{issuables_state_counter_text(type, :opened, display_count)}
%li{ class: active_when(params[:state] == 'closed') }>
= link_to page_filter_path(state: 'closed', label: true), id: 'state-closed', title: (_("Filter by %{issuable_type} that are currently closed.") % { issuable_type: page_context_word }), data: { state: 'closed' } do
#{issuables_state_counter_text(type, :closed, display_count)}
= render 'shared/issuable/nav_links/all', page_context_word: page_context_word, counter: issuables_state_counter_text(type, :all, display_count)
---
title: Add Open/Closed epics tabs in list view
merge_request: 7424
author:
type: added
......@@ -13,6 +13,15 @@ FactoryBot.define do
due_date_is_fixed true
end
trait :opened do
state :opened
end
trait :closed do
state :closed
closed_at { Time.now }
end
factory :labeled_epic do
transient do
labels []
......
......@@ -19,6 +19,19 @@ describe 'epics list', :js do
visit group_epics_path(group)
end
it 'shows epics tabs for each status type' do
page.within('.epics-state-filters') do
expect(page).to have_selector('li > a#state-opened')
expect(find('li > a#state-opened')[:title]).to eq('Filter by epics that are currently opened.')
expect(page).to have_selector('li > a#state-closed')
expect(find('li > a#state-closed')[:title]).to eq('Filter by epics that are currently closed.')
expect(page).to have_selector('li > a#state-all')
expect(find('li > a#state-all')[:title]).to eq('Show all epics.')
end
end
it 'shows the epics in the navigation sidebar' do
expect(first('.nav-sidebar .active a .nav-item-name')).to have_content('Epics')
expect(first('.nav-sidebar .active a .count')).to have_content('3')
......@@ -106,13 +119,23 @@ describe 'epics list', :js do
end
context 'when no epics exist for the group' do
it 'renders the empty list page' do
before do
visit group_epics_path(group)
end
it 'renders the empty list page' do
within('#content-body') do
expect(find('.empty-state h4'))
.to have_content('Epics let you manage your portfolio of projects more efficiently and with less effort')
end
end
it 'shows epics tabs for each status type' do
page.within('.epics-state-filters') do
expect(page).to have_selector('li > a#state-opened')
expect(page).to have_selector('li > a#state-closed')
expect(page).to have_selector('li > a#state-all')
end
end
end
end
......@@ -5,10 +5,10 @@ describe EpicsFinder do
let(:search_user) { create(:user) }
let(:group) { create(:group, :private) }
let(:another_group) { create(:group) }
let!(:epic1) { create(:epic, group: group, title: 'This is awesome epic', created_at: 1.week.ago) }
let!(:epic2) { create(:epic, group: group, created_at: 4.days.ago, author: user, start_date: 2.days.ago, end_date: 3.days.from_now) }
let!(:epic3) { create(:epic, group: group, description: 'not so awesome', start_date: 5.days.ago, end_date: 3.days.ago) }
let!(:epic4) { create(:epic, group: another_group) }
let!(:epic1) { create(:epic, :opened, group: group, title: 'This is awesome epic', created_at: 1.week.ago) }
let!(:epic2) { create(:epic, :opened, group: group, created_at: 4.days.ago, author: user, start_date: 2.days.ago, end_date: 3.days.from_now) }
let!(:epic3) { create(:epic, :closed, group: group, description: 'not so awesome', start_date: 5.days.ago, end_date: 3.days.ago) }
let!(:epic4) { create(:epic, :closed, group: another_group) }
describe '#execute' do
def epics(params = {})
......@@ -106,6 +106,12 @@ describe EpicsFinder do
end
end
context 'by state' do
it 'returns all epics with given state' do
expect(epics(state: :closed)).to contain_exactly(epic3)
end
end
context 'when subgroups are supported', :nested_groups do
let(:subgroup) { create(:group, :private, parent: group) }
let(:subgroup2) { create(:group, :private, parent: subgroup) }
......@@ -184,4 +190,17 @@ describe EpicsFinder do
expect(described_class.new(search_user, params).row_count).to eq(1)
end
end
describe '#count_by_state' do
before do
group.add_developer(search_user)
stub_licensed_features(epics: true)
end
it 'returns correct counts' do
results = described_class.new(search_user, group_id: group.id).count_by_state
expect(results).to eq('opened' => 2, 'closed' => 1, 'all' => 3)
end
end
end
......@@ -3212,6 +3212,12 @@ msgstr ""
msgid "Filter"
msgstr ""
msgid "Filter by %{issuable_type} that are currently closed."
msgstr ""
msgid "Filter by %{issuable_type} that are currently opened."
msgstr ""
msgid "Filter by commit message"
msgstr ""
......
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