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
> - 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.
> - [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
parameters:
......
import initEpicCreateApp from 'ee/epic/epic_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_';
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);
}
initEpicsList({
mountPointSelector: '#js-epics-list',
});
@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 {
opacity: 0.3;
pointer-events: none;
......
......@@ -71,14 +71,6 @@ html.group-epics-roadmap-html {
border-bottom-right-radius: 0;
}
.btn-sort-direction {
border-left: 0;
&:hover {
border-color: var(--gray-darkest, $gray-darkest);
}
}
@include media-breakpoint-down(xs) {
display: flex;
......
......@@ -21,7 +21,6 @@ class Groups::EpicsController < Groups::ApplicationController
before_action -> { check_rate_limit!(:issues_create, scope: current_user) }, only: [:create]
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)
end
......
......@@ -15,35 +15,6 @@ module EE
}.merge(super)
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
def issuable_reverse_sort_order_hash
{
......@@ -66,16 +37,5 @@ module EE
super
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
......@@ -20,32 +20,6 @@ module EpicsHelper
}
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)
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)
......
%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 @@
- page_title _("Epics")
- 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,
can_bulk_edit_epics: @can_bulk_update.to_s,
page: params[:page],
prev: params[:prev],
next: params[:next],
initial_state: params[:state],
initial_sort_by: params[:sort],
epics_count: { opened: issuables_count_for_state(:epic, :opened),
closed: issuables_count_for_state(:epic, :closed),
all: issuables_count_for_state(:epic, :all) },
epic_new_path: new_group_epic_url(@group),
list_epics_path: group_epics_path(@group),
group_full_path: @group.full_path,
labels_manage_path: group_labels_path(@group),
labels_fetch_path: group_labels_path(@group, format: :json),
group_milestones_path: group_milestones_path(@group, format: :json),
empty_state_path: image_path('illustrations/epics/list.svg'),
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'
#js-epics-list{ data: { can_create_epic: can?(current_user, :create_epic, @group).to_s,
can_bulk_edit_epics: @can_bulk_update.to_s,
page: params[:page],
prev: params[:prev],
next: params[:next],
initial_state: params[:state],
initial_sort_by: params[:sort],
epics_count: { opened: issuables_count_for_state(:epic, :opened),
closed: issuables_count_for_state(:epic, :closed),
all: issuables_count_for_state(:epic, :all) },
epic_new_path: new_group_epic_url(@group),
list_epics_path: group_epics_path(@group),
group_full_path: @group.full_path,
labels_manage_path: group_labels_path(@group),
labels_fetch_path: group_labels_path(@group, format: :json),
group_milestones_path: group_milestones_path(@group, format: :json),
empty_state_path: image_path('illustrations/epics/list.svg'),
is_signed_in: is_signed_in } }
%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
before do
stub_licensed_features(epics: true)
stub_feature_flags(unfiltered_epic_aggregates: false)
stub_feature_flags(vue_epics_list: false)
sign_in(user)
end
context 'when epics exist for the group' 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
context 'epics list' do
available_tokens = %w[Author Label My-Reaction]
before do
stub_feature_flags(vue_epics_list: true)
end
describe 'within a group' do
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') }
......
# 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
expect(helper.epic_new_app_data(group)).to match(hash_including(expected_data))
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
......@@ -15288,12 +15288,6 @@ msgstr ""
msgid "Filter by"
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."
msgstr ""
......@@ -33721,9 +33715,6 @@ msgstr ""
msgid "Something went wrong. Try again later."
msgstr ""
msgid "Sorry, no epics matched your search"
msgstr ""
msgid "Sorry, no projects matched your search"
msgstr ""
......@@ -37593,9 +37584,6 @@ msgstr ""
msgid "To widen your search, change or remove filters above."
msgstr ""
msgid "To widen your search, change or remove filters."
msgstr ""
msgid "To-Do List"
msgstr ""
......
......@@ -18,8 +18,6 @@ module QA
end
before do
Runtime::Feature.enable(:vue_epics_list, group: project.group)
Flow::Login.sign_in
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