Commit bf4b3ad9 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '276189-remove-vue_epics_list-ff' into 'master'

Remove `vue_epics_list` and Epics list legacy code

See merge request gitlab-org/gitlab!79404
parents 270c9225 0c516d3b
...@@ -164,6 +164,7 @@ than 1000. The cached value is rounded to thousands or millions and updated ever ...@@ -164,6 +164,7 @@ than 1000. The cached value is rounded to thousands or millions and updated ever
> - Sorting by epic titles [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/331625) in GitLab 14.1. > - Sorting by epic titles [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/331625) in GitLab 14.1.
> - Searching by milestone and confidentiality [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/268372) in GitLab 14.2 [with a flag](../../../administration/feature_flags.md) named `vue_epics_list`. Disabled by default. > - Searching by milestone and confidentiality [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/268372) in GitLab 14.2 [with a flag](../../../administration/feature_flags.md) named `vue_epics_list`. Disabled by default.
> - [Enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/276189) in GitLab 14.7. > - [Enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/276189) in GitLab 14.7.
> - [Feature flag `vue_epics_list`](https://gitlab.com/gitlab-org/gitlab/-/issues/327320) removed in GitLab 14.8.
You can search for an epic from the list of epics using filtered search bar based on following You can search for an epic from the list of epics using filtered search bar based on following
parameters: parameters:
......
import initEpicCreateApp from 'ee/epic/epic_bundle';
import initEpicsList from 'ee/epics_list/epics_list_bundle'; import initEpicsList from 'ee/epics_list/epics_list_bundle';
import FilteredSearchTokenKeysEpics from 'ee/filtered_search/filtered_search_token_keys_epics';
import { initBulkUpdateSidebar } from '~/issuable/bulk_update_sidebar';
import initFilteredSearch from '~/pages/search/init_filtered_search';
const EPIC_BULK_UPDATE_PREFIX = 'epic_'; initEpicsList({
mountPointSelector: '#js-epics-list',
if (gon.features.vueEpicsList) { });
initEpicsList({
mountPointSelector: '#js-epics-list',
});
} else {
initFilteredSearch({
page: 'epics',
isGroup: true,
isGroupDecendent: true,
useDefaultState: true,
filteredSearchTokenKeys: FilteredSearchTokenKeysEpics,
stateFiltersSelector: '.epics-state-filters',
});
initEpicCreateApp(true);
initBulkUpdateSidebar(EPIC_BULK_UPDATE_PREFIX);
}
@import 'page_bundles/mixins_and_variables_and_functions'; @import 'page_bundles/mixins_and_variables_and_functions';
@include media-breakpoint-down(sm) {
.epics-other-filters {
.epics-sort-btn i {
position: absolute;
top: 11px;
right: 6px;
}
.btn-sort-direction {
flex: 0 1 auto;
}
}
}
.is-ghost { .is-ghost {
opacity: 0.3; opacity: 0.3;
pointer-events: none; pointer-events: none;
......
...@@ -71,14 +71,6 @@ html.group-epics-roadmap-html { ...@@ -71,14 +71,6 @@ html.group-epics-roadmap-html {
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
} }
.btn-sort-direction {
border-left: 0;
&:hover {
border-color: var(--gray-darkest, $gray-darkest);
}
}
@include media-breakpoint-down(xs) { @include media-breakpoint-down(xs) {
display: flex; display: flex;
......
...@@ -21,7 +21,6 @@ class Groups::EpicsController < Groups::ApplicationController ...@@ -21,7 +21,6 @@ class Groups::EpicsController < Groups::ApplicationController
before_action -> { check_rate_limit!(:issues_create, scope: current_user) }, only: [:create] before_action -> { check_rate_limit!(:issues_create, scope: current_user) }, only: [:create]
before_action do before_action do
push_frontend_feature_flag(:vue_epics_list, @group, type: :development, default_enabled: :yaml)
push_frontend_feature_flag(:improved_emoji_picker, @group, type: :development, default_enabled: :yaml) push_frontend_feature_flag(:improved_emoji_picker, @group, type: :development, default_enabled: :yaml)
end end
......
...@@ -15,35 +15,6 @@ module EE ...@@ -15,35 +15,6 @@ module EE
}.merge(super) }.merge(super)
end end
def epics_sort_options_hash
{
sort_value_created_date => sort_title_created_date,
sort_value_oldest_created => sort_title_created_date,
sort_value_recently_created => sort_title_created_date,
sort_value_oldest_updated => sort_title_recently_updated,
sort_value_recently_updated => sort_title_recently_updated,
sort_value_start_date_later => sort_title_start_date,
sort_value_start_date_soon => sort_title_start_date,
sort_value_end_date_later => sort_title_end_date,
sort_value_end_date => sort_title_end_date,
sort_value_title => sort_title_title,
sort_value_title_desc => sort_title_title
}
end
# This method is used to find the opposite ordering parameter for the sort button in the UI.
# Hash key is the descending sorting order and the sort value is the opposite of it for the same field.
# For example: created_at_asc => created_at_desc
def epics_ordering_options_hash
{
sort_value_oldest_created => sort_value_recently_created,
sort_value_oldest_updated => sort_value_recently_updated,
sort_value_start_date_soon => sort_value_start_date_later,
sort_value_end_date => sort_value_end_date_later,
sort_value_title => sort_value_title_desc
}
end
override :issuable_reverse_sort_order_hash override :issuable_reverse_sort_order_hash
def issuable_reverse_sort_order_hash def issuable_reverse_sort_order_hash
{ {
...@@ -66,16 +37,5 @@ module EE ...@@ -66,16 +37,5 @@ module EE
super super
end end
end end
# Creates a button with the opposite ordering for the current field in UI.
def sort_order_button(sort)
opposite_sorting_param = epics_ordering_options_hash[sort] || epics_ordering_options_hash.key(sort)
sort_icon = sort.end_with?('desc') ? 'sort-highest' : 'sort-lowest'
link_to sprite_icon(sort_icon),
page_filter_path(sort: opposite_sorting_param),
class: "btn gl-button btn-default btn-icon has-tooltip qa-reverse-sort btn-sort-direction",
title: _("Sort direction")
end
end end
end end
...@@ -20,32 +20,6 @@ module EpicsHelper ...@@ -20,32 +20,6 @@ module EpicsHelper
} }
end end
def epic_endpoint_query_params(opts)
opts[:data] ||= {}
opts[:data][:endpoint_query_params] = {
only_group_labels: true,
include_ancestor_groups: true,
include_descendant_groups: true
}.to_json
opts
end
def epic_timeframe(epic)
short_format = '%b %d'
long_format = '%b %d, %Y'
if epic.start_date.present? && epic.end_date.present?
start_date_format = epic.start_date.year == epic.end_date.year ? short_format : long_format
"#{epic.start_date.strftime(start_date_format)}#{epic.end_date.strftime(long_format)}"
elsif epic.start_date.present?
s_('GroupRoadmap|%{dateWord} – No end date') % { dateWord: epic.start_date.strftime(long_format) }
elsif epic.end_date.present?
s_("GroupRoadmap|No start date – %{dateWord}") % { dateWord: epic.end_date.strftime(long_format) }
end
end
def award_emoji_epics_api_path(epic) def award_emoji_epics_api_path(epic)
if Feature.enabled?(:improved_emoji_picker, epic.group, default_enabled: :yaml) if Feature.enabled?(:improved_emoji_picker, epic.group, default_enabled: :yaml)
api_v4_groups_epics_award_emoji_path(id: epic.group.id, epic_iid: epic.iid) api_v4_groups_epics_award_emoji_path(id: epic.group.id, epic_iid: epic.iid)
......
%li{ id: dom_id(epic), data: { labels: epic.label_ids, id: epic.id } }
.issuable-info-container
- if @can_bulk_update
.issue-check.hidden
- checkbox_id = dom_id(epic, "selected")
%label.gl-sr-only{ for: checkbox_id }= epic.title
= check_box_tag checkbox_id, nil, false, 'data-id' => epic.id, class: "selected-issuable"
.issuable-main-info
.issue-title.title
%span.issue-title-text{ data: { qa_selector: 'epic_title_text' } }
- if epic.confidential?
%span.has-tooltip{ title: _('Confidential') }
= confidential_icon(epic)
= link_to epic.title, epic_path(epic)
.issuable-info
%span.issuable-reference
= epic.to_reference(@group)
%span.issuable-authored.d-none.d-sm-inline-block
&middot;
created #{time_ago_with_tooltip(epic.created_at, placement: 'bottom')}
by #{link_to_member(@group, epic.author, avatar: false)}
= gitlab_team_member_badge(epic.author)
- if epic.start_date? || epic.end_date?
&nbsp;
%span.issuable-dates.d-inline-flex.align-items-center.align-top
= sprite_icon('calendar', size: 14, css_class: 'mr-1')
%span= epic_timeframe(epic)
- if epic.labels.any?
&nbsp;
- epic.labels.each do |label|
= render_label(label.present(issuable_subject: @group), tooltip: true, link: group_epics_path(@group, label_name: [label.name]), small: true)
.issuable-meta
%ul.controls
- if epic.closed?
%li.issuable-status
= _('CLOSED')
= render 'shared/issuable_meta_data', issuable: epic
.float-right.issuable-updated-at.d-none.d-sm-inline-block
%span
= _('updated %{time_ago}').html_safe % { time_ago: time_ago_with_tooltip(epic.updated_at, placement: 'bottom', html_class: 'issue_update_ago') }
...@@ -3,40 +3,21 @@ ...@@ -3,40 +3,21 @@
- page_title _("Epics") - page_title _("Epics")
- is_signed_in = current_user.present?.to_s - is_signed_in = current_user.present?.to_s
- if Feature.enabled?(:vue_epics_list, @group, default_enabled: :yaml) #js-epics-list{ data: { can_create_epic: can?(current_user, :create_epic, @group).to_s,
#js-epics-list{ data: { can_create_epic: can?(current_user, :create_epic, @group).to_s, can_bulk_edit_epics: @can_bulk_update.to_s,
can_bulk_edit_epics: @can_bulk_update.to_s, page: params[:page],
page: params[:page], prev: params[:prev],
prev: params[:prev], next: params[:next],
next: params[:next], initial_state: params[:state],
initial_state: params[:state], initial_sort_by: params[:sort],
initial_sort_by: params[:sort], epics_count: { opened: issuables_count_for_state(:epic, :opened),
epics_count: { opened: issuables_count_for_state(:epic, :opened), closed: issuables_count_for_state(:epic, :closed),
closed: issuables_count_for_state(:epic, :closed), all: issuables_count_for_state(:epic, :all) },
all: issuables_count_for_state(:epic, :all) }, epic_new_path: new_group_epic_url(@group),
epic_new_path: new_group_epic_url(@group), list_epics_path: group_epics_path(@group),
list_epics_path: group_epics_path(@group), group_full_path: @group.full_path,
group_full_path: @group.full_path, labels_manage_path: group_labels_path(@group),
labels_manage_path: group_labels_path(@group), labels_fetch_path: group_labels_path(@group, format: :json),
labels_fetch_path: group_labels_path(@group, format: :json), group_milestones_path: group_milestones_path(@group, format: :json),
group_milestones_path: group_milestones_path(@group, format: :json), empty_state_path: image_path('illustrations/epics/list.svg'),
empty_state_path: image_path('illustrations/epics/list.svg'), is_signed_in: is_signed_in } }
is_signed_in: is_signed_in } }
- else
.top-area
= render 'shared/issuable/epic_nav', type: :epics
.nav-controls
- if @can_bulk_update
= render_if_exists 'shared/issuable/bulk_update_button', type: :epics
- if can?(current_user, :create_epic, @group)
= link_to _('New epic'), new_group_epic_path(@group), class: 'btn btn-confirm gl-button', data: { qa_selector: 'new_epic_button' }
= render 'shared/epic/search_bar', type: :epics
- if @can_bulk_update
= render_if_exists 'shared/issuable/group_bulk_update_sidebar', group: @group, type: :epics
- if @epics.to_a.any?
= render 'shared/epics'
- else
= render 'shared/empty_states/epics'
%ul.content-list.issuable-list
= render partial: 'groups/epics/epic', collection: @epics
= paginate_collection @epics
- has_filters_applied = params[:label_name].present? || params[:author_username].present? || params[:search].present?
.row.empty-state
.col-12
.svg-content
= image_tag 'illustrations/epics/list.svg'
.col-12
.text-content
%h4
- if has_filters_applied
= _('Sorry, no epics matched your search')
- else
= _('Epics let you manage your portfolio of projects more efficiently and with less effort')
%p
- if has_filters_applied
= _('To widen your search, change or remove filters.')
- else
= _('Track groups of issues that share a theme, across projects and milestones')
- if can?(current_user, :create_epic, @group)
.text-center
#new-epic-app{ data: { endpoint: request.url } }
- type = local_assigns.fetch(:type)
- hide_sort_dropdown = local_assigns.fetch(:hide_sort_dropdown, false)
- hide_extra_sort_options = local_assigns.fetch(:hide_extra_sort_options, false)
.epics-filters
.epics-details-filters.filtered-search-block.d-flex.flex-column.flex-md-row.row-content-block.second-block
= form_tag page_filter_path, method: :get, class: 'flex-fill filter-form js-filter-form' do
- if params[:search].present?
= hidden_field_tag :search, params[:search]
- if @can_bulk_update
.check-all-holder.gl-display-none.gl-sm-display-block.hidden.gl-float-left.gl-mr-5.gl-line-height-36
- checkbox_id = 'check-all-issues'
%label.gl-sr-only{ for: checkbox_id }= _('Select all')
= check_box_tag checkbox_id, nil, false, class: "check-all-issues left"
.epics-other-filters.filtered-search-wrapper.d-flex.flex-column.flex-md-row
.filtered-search-box
= dropdown_tag(_('Recent searches'),
options: { wrapper_class: "filtered-search-history-dropdown-wrapper",
toggle_class: "gl-button btn btn-default filtered-search-history-dropdown-toggle-button",
dropdown_class: "filtered-search-history-dropdown",
content_class: "filtered-search-history-dropdown-content" }) do
.js-filtered-search-history-dropdown{ data: { full_path: search_history_storage_prefix } }
.filtered-search-box-input-container.droplab-dropdown
.scroll-container
%ul.tokens-container.list-unstyled
%li.input-token
%input.form-control.filtered-search{ epic_endpoint_query_params(search_filter_input_options(type)) }
#js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item{ data: {hint: "#{'{{hint}}'}", tag: "#{'{{tag}}'}", action: "#{'{{hint === \'search\' ? \'submit\' : \'\' }}'}" } }
%button.gl-button.btn.btn-link{ type: 'button' }
-# Encapsulate static class name `{{icon}}` inside #{} to bypass
-# haml lint's ClassAttributeWithStaticValue
%svg
%use{ 'xlink:href': "#{'{{icon}}'}" }
%span.js-filter-hint
{{formattedKey}}
#js-dropdown-operator.filtered-search-input-dropdown-menu.dropdown-menu
%ul.filter-dropdown{ data: { dropdown: true, dynamic: true } }
%li.filter-dropdown-item{ data: { value: "{{ title }}" } }
%button.gl-button.btn.btn-link{ type: 'button' }
{{ title }}
%span.btn-helptext
{{ help }}
#js-dropdown-author.filtered-search-input-dropdown-menu.dropdown-menu
- if current_user
%ul{ data: { dropdown: true } }
= render 'shared/issuable/user_dropdown_item',
user: current_user
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
= render 'shared/issuable/user_dropdown_item',
user: User.new(username: '{{username}}', name: '{{name}}'),
avatar: { lazy: true, url: '{{avatar_url}}' }
#js-dropdown-label.filtered-search-input-dropdown-menu.dropdown-menu
%ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'none' } }
%button.gl-button.btn.btn-link{ type: 'button' }
= _("No label")
%li.divider.droplab-item-ignore
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item{ type: 'button' }
%button.gl-button.btn.btn-link
%span.dropdown-label-box{ style: 'background: {{color}}' }
%span.label-title.js-data-value
{{ title }}
#js-dropdown-my-reaction.filtered-search-input-dropdown-menu.dropdown-menu
%ul{ data: { dropdown: true } }
%li.filter-dropdown-item{ data: { value: 'None' } }
%button.gl-button.btn.btn-link{ type: 'button' }
= _('None')
%li.filter-dropdown-item{ data: { value: 'Any' } }
%button.btn.btn-link{ type: 'button' }
= _('Any')
%li.divider.droplab-item-ignore
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
%li.filter-dropdown-item
%button.btn.btn-link{ type: 'button' }
%gl-emoji
%span.js-data-value.gl-ml-3
{{ name }}
%button.clear-search.hidden{ type: 'button' }
= sprite_icon('close', size: 16, css_class: 'clear-search-icon')
- unless hide_sort_dropdown
.filter-dropdown-container
= render 'shared/epic/sort_dropdown', hide_extra_sort_options: hide_extra_sort_options
- hide_extra_sort_options = local_assigns.fetch(:hide_extra_sort_options, false)
- sorted_by = epics_sort_options_hash[@sort]
.dropdown.gl-new-dropdown.gl-ml-3
.btn-group.d-flex.d-md-inline-flex
.btn-group
%button.btn.gl-button.btn-default.epics-sort-btn{ type: 'button', data: { toggle: 'dropdown' } }
%span.gl-new-dropdown-button-text
= sorted_by
= sprite_icon('chevron-down', css_class: 'gl-button-icon dropdown-chevron gl-icon s16')
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort
.gl-new-dropdown-inner
%li
- if !hide_extra_sort_options
= sortable_item(sort_title_created_date, page_filter_path(sort: sort_value_recently_created), sorted_by)
= sortable_item(sort_title_recently_updated, page_filter_path(sort: sort_value_recently_updated), sorted_by)
= sortable_item(sort_title_start_date, page_filter_path(sort: sort_value_start_date_soon), sorted_by)
= sortable_item(sort_title_end_date, page_filter_path(sort: sort_value_end_date), sorted_by)
= sortable_item(sort_title_title, page_filter_path(sort: sort_value_title), sorted_by)
= sort_order_button(@sort)
- type = local_assigns.fetch(:type, :epics)
- page_context_word = type.to_s.humanize(capitalize: false)
- display_count = local_assigns.fetch(:display_count, true)
= gl_tabs_nav({ class: 'epics-state-filters gl-border-b-0 gl-flex-grow-1' }) do
= gl_tab_link_to page_filter_path(state: 'opened'), item_active: params[:state] == 'opened', id: 'state-opened', title: (_("Filter by %{issuable_type} that are currently open.") % { issuable_type: page_context_word }), data: { state: 'opened' } do
#{issuables_state_counter_text(type, :opened, display_count)}
= gl_tab_link_to page_filter_path(state: 'closed'), item_active: params[:state] == 'closed', 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)
---
name: vue_epics_list
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46769
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/276189
milestone: '13.9'
type: development
group: group::product planning
default_enabled: true
...@@ -12,181 +12,13 @@ RSpec.describe 'epics list', :js do ...@@ -12,181 +12,13 @@ RSpec.describe 'epics list', :js do
before do before do
stub_licensed_features(epics: true) stub_licensed_features(epics: true)
stub_feature_flags(unfiltered_epic_aggregates: false) stub_feature_flags(unfiltered_epic_aggregates: false)
stub_feature_flags(vue_epics_list: false)
sign_in(user) sign_in(user)
end end
context 'when epics exist for the group' do context 'epics list' do
let!(:epic1) { create(:epic, group: group, end_date: 10.days.ago) }
let!(:epic2) { create(:epic, group: group, start_date: 2.days.ago) }
let!(:epic3) { create(:epic, group: group, start_date: 10.days.ago, end_date: 5.days.ago) }
before 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 open.')
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')
end
it 'shows epic updated date and comment count' do
page.within('.issuable-list') do
page.within('li:nth-child(1) .issuable-meta') do
expect(find('.issuable-updated-at')).to have_content('updated just now')
expect(find('.issuable-comments')).to have_content('0')
end
end
end
it 'shows epic start and/or end dates when present' do
page.within('.issuable-list') do
expect(find("li[data-id='#{epic1.id}'] .issuable-info .issuable-dates")).to have_content("No start date – #{epic1.end_date.strftime('%b %d, %Y')}")
expect(find("li[data-id='#{epic2.id}'] .issuable-info .issuable-dates")).to have_content("#{epic2.start_date.strftime('%b %d, %Y')} – No end date")
end
end
it 'renders the filtered search bar correctly' do
page.within('.content-wrapper .content') do
expect(page).to have_css('.epics-filters')
end
end
it 'sorts by created_at DESC by default' do
expect(page).to have_button('Created date')
page.within('.content-wrapper .content') do
expect(find('.top-area')).to have_content('All 3')
page.within('.issuable-list') do
page.within('li:nth-child(1) .issuable-main-info') do
expect(page).to have_content(epic3.title)
end
page.within('li:nth-child(2) .issuable-main-info') do
expect(page).to have_content(epic2.title)
end
page.within('li:nth-child(3) .issuable-main-info') do
expect(page).to have_content(epic1.title)
end
end
end
end
it 'sorts by the selected value and stores the selection for epic list' do
page.within('.epics-other-filters') do
click_button 'Created date'
sort_options = find('ul.dropdown-menu-sort li').all('a').collect(&:text)
expect(sort_options[0]).to eq('Created date')
expect(sort_options[1]).to eq('Updated date')
expect(sort_options[2]).to eq('Start date')
expect(sort_options[3]).to eq('Due date')
click_link 'Updated date'
end
expect(page).to have_button('Updated date')
page.within('.content-wrapper .content') do
expect(find('.top-area')).to have_content('All 3')
page.within('.issuable-list') do
page.within('li:nth-child(1) .issuable-main-info') do
expect(page).to have_content(epic3.title)
end
page.within('li:nth-child(2) .issuable-main-info') do
expect(page).to have_content(epic2.title)
end
page.within('li:nth-child(3) .issuable-main-info') do
expect(page).to have_content(epic1.title)
end
end
end
visit group_epics_path(group)
expect(page).to have_button('Updated date')
end
it 'renders the epic detail correctly after clicking the link' do
page.within('.content-wrapper .content .issuable-list') do
click_link(epic1.title)
end
wait_for_requests
expect(page.find('.issuable-details h2.title')).to have_content(epic1.title)
end
end
context 'when closed epics exist for the group' do
let!(:epic1) { create(:epic, :closed, group: group, end_date: 10.days.ago) }
before do
visit group_epics_path(group)
end
it 'shows epic status, updated date and comment count' do
page.within('.epics-state-filters') do
click_link 'Closed'
end
page.within('.issuable-list') do
page.within('li:nth-child(1) .issuable-meta') do
expect(find('.issuable-status')).to have_content('CLOSED')
expect(find('.issuable-updated-at')).to have_content('updated just now')
expect(find('.issuable-comments')).to have_content('0')
end
end
end
end
context 'when no epics exist for the group' 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
context 'vue epics list' do
available_tokens = %w[Author Label My-Reaction] available_tokens = %w[Author Label My-Reaction]
before do
stub_feature_flags(vue_epics_list: true)
end
describe 'within a group' do describe 'within a group' do
let!(:epic1) { create(:epic, group: group, start_date: '2020-12-15', end_date: '2021-1-15') } let!(:epic1) { create(:epic, group: group, start_date: '2020-12-15', end_date: '2021-1-15') }
let!(:epic2) { create(:epic, group: group, start_date: '2020-12-15') } let!(:epic2) { create(:epic, group: group, start_date: '2020-12-15') }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'epics list', :js do
include FilteredSearchHelpers
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group, :public) }
let_it_be(:label) { create(:group_label, group: group, title: 'bug') }
let_it_be(:epic) { create(:epic, group: group, start_date: 10.days.ago, due_date: 5.days.ago) }
let(:filtered_search) { find('.filtered-search') }
let(:filter_author_dropdown) { find("#js-dropdown-author .filter-dropdown") }
let(:filter_label_dropdown) { find("#js-dropdown-label .filter-dropdown") }
let(:js_dropdown_my_reaction) { '#js-dropdown-my-reaction' }
let(:filter_emoji_dropdown) { find("#js-dropdown-my-reaction .filter-dropdown") }
let_it_be(:award_emoji_star) { create(:award_emoji, name: 'star', user: user, awardable: epic) }
before do
stub_licensed_features(epics: true)
stub_feature_flags(vue_epics_list: false)
sign_in(user)
visit group_epics_path(group)
end
context 'editing author token' do
before do
input_filtered_search('author:=@root', submit: false)
first('.tokens-container .filtered-search-token').click
end
it 'converts keyword into visual token' do
page.within('.tokens-container') do
expect(page).to have_selector('.js-visual-token')
expect(page).to have_content('Author')
end
end
it 'opens author dropdown' do
expect(page).to have_css('#js-dropdown-author', visible: true)
end
it 'makes value editable' do
expect_filtered_search_input('@root')
end
it 'filters value' do
filtered_search.send_keys(:backspace)
expect(page).to have_css('#js-dropdown-author .filter-dropdown .filter-dropdown-item', count: 1)
end
end
context 'editing label token' do
before do
input_filtered_search("label:=~#{label.title}", submit: false)
first('.tokens-container .filtered-search-token').click
end
it 'converts keyword into visual token' do
page.within('.tokens-container') do
expect(page).to have_selector('.js-visual-token')
expect(page).to have_content('Label')
end
end
it 'opens label dropdown' do
expect(filter_label_dropdown.find('.filter-dropdown-item', text: label.title)).to be_visible
expect(page).to have_css('#js-dropdown-label', visible: true)
end
it 'makes value editable' do
expect_filtered_search_input("~#{label.title}")
end
it 'filters value' do
expect(filter_label_dropdown.find('.filter-dropdown-item', text: label.title)).to be_visible
filtered_search.send_keys(:backspace)
filter_label_dropdown.find('.filter-dropdown-item')
expect(page.all('#js-dropdown-label .filter-dropdown .filter-dropdown-item').size).to eq(1)
end
end
context 'editing reaction emoji token' do
before_all do
create_list(:award_emoji, 2, user: user, name: 'thumbsup')
create_list(:award_emoji, 1, user: user, name: 'thumbsdown')
create_list(:award_emoji, 3, user: user, name: 'star')
end
context 'when user is not logged in' do
it 'does not open when the search bar has my-reaction=' do
filtered_search.set('my-reaction=')
expect(page).not_to have_css(js_dropdown_my_reaction)
end
end
context 'when user is logged in' do
before_all do
group.add_maintainer(user)
end
it 'opens when the search bar has my-reaction=' do
filtered_search.set('my-reaction:=')
expect(page).to have_css(js_dropdown_my_reaction, visible: true)
end
it 'loads all the emojis when opened' do
input_filtered_search('my-reaction:=', submit: false, extra_space: false)
expect_filtered_search_dropdown_results(filter_emoji_dropdown, 3)
end
it 'shows the most populated emoji at top of dropdown' do
input_filtered_search('my-reaction:=', submit: false, extra_space: false)
expect(first("#{js_dropdown_my_reaction} .filter-dropdown li")).to have_content(award_emoji_star.name)
end
end
end
end
...@@ -21,68 +21,4 @@ RSpec.describe EpicsHelper, type: :helper do ...@@ -21,68 +21,4 @@ RSpec.describe EpicsHelper, type: :helper do
expect(helper.epic_new_app_data(group)).to match(hash_including(expected_data)) expect(helper.epic_new_app_data(group)).to match(hash_including(expected_data))
end end
end end
describe '#epic_endpoint_query_params' do
let(:endpoint_data) do
{
only_group_labels: true,
include_ancestor_groups: true,
include_descendant_groups: true
}
end
it 'includes Epic specific options in JSON format' do
opts = epic_endpoint_query_params({})
expect(opts[:data][:endpoint_query_params]).to eq(endpoint_data.to_json)
end
it 'includes data provided in param' do
opts = epic_endpoint_query_params(data: { default_param: true })
expect(opts[:data]).to eq({ default_param: true }.merge(endpoint_query_params: endpoint_data.to_json))
end
end
describe '#epic_timeframe' do
let(:epic) { build(:epic, start_date: start_date, end_date: end_date) }
subject { epic_timeframe(epic) }
context 'when both dates are from the same year' do
let(:start_date) { Date.new(2018, 7, 22) }
let(:end_date) { Date.new(2018, 8, 15) }
it 'returns start date with year omitted and end date with year' do
is_expected.to eq('Jul 22 – Aug 15, 2018')
end
end
context 'when both dates are from different years' do
let(:start_date) { Date.new(2018, 7, 22) }
let(:end_date) { Date.new(2019, 7, 22) }
it 'returns start date with year omitted and end date with year' do
is_expected.to eq('Jul 22, 2018 – Jul 22, 2019')
end
end
context 'when only start date is present' do
let(:start_date) { Date.new(2018, 7, 22) }
let(:end_date) { nil }
it 'returns start date with year' do
is_expected.to eq('Jul 22, 2018 – No end date')
end
end
context 'when only end date is present' do
let(:start_date) { nil }
let(:end_date) { Date.new(2018, 7, 22) }
it 'returns end date with year' do
is_expected.to eq('No start date – Jul 22, 2018')
end
end
end
end end
...@@ -15288,12 +15288,6 @@ msgstr "" ...@@ -15288,12 +15288,6 @@ msgstr ""
msgid "Filter by" msgid "Filter by"
msgstr "" msgstr ""
msgid "Filter by %{issuable_type} that are currently closed."
msgstr ""
msgid "Filter by %{issuable_type} that are currently open."
msgstr ""
msgid "Filter by %{page_context_word} that are currently open." msgid "Filter by %{page_context_word} that are currently open."
msgstr "" msgstr ""
...@@ -33721,9 +33715,6 @@ msgstr "" ...@@ -33721,9 +33715,6 @@ msgstr ""
msgid "Something went wrong. Try again later." msgid "Something went wrong. Try again later."
msgstr "" msgstr ""
msgid "Sorry, no epics matched your search"
msgstr ""
msgid "Sorry, no projects matched your search" msgid "Sorry, no projects matched your search"
msgstr "" msgstr ""
...@@ -37593,9 +37584,6 @@ msgstr "" ...@@ -37593,9 +37584,6 @@ msgstr ""
msgid "To widen your search, change or remove filters above." msgid "To widen your search, change or remove filters above."
msgstr "" msgstr ""
msgid "To widen your search, change or remove filters."
msgstr ""
msgid "To-Do List" msgid "To-Do List"
msgstr "" msgstr ""
......
...@@ -18,8 +18,6 @@ module QA ...@@ -18,8 +18,6 @@ module QA
end end
before do before do
Runtime::Feature.enable(:vue_epics_list, group: project.group)
Flow::Login.sign_in Flow::Login.sign_in
end 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