Commit b9e80d44 authored by Andrew Fontaine's avatar Andrew Fontaine

Merge branch '322755-additions-to-issues-refactor' into 'master'

Make additions to group/project issues list refactor

See merge request gitlab-org/gitlab!76810
parents 926e5440 a520f3fc
...@@ -8,6 +8,7 @@ import { ...@@ -8,6 +8,7 @@ import {
GlSprintf, GlSprintf,
GlTooltipDirective, GlTooltipDirective,
} from '@gitlab/ui'; } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import fuzzaldrinPlus from 'fuzzaldrin-plus'; import fuzzaldrinPlus from 'fuzzaldrin-plus';
import getIssuesQuery from 'ee_else_ce/issues_list/queries/get_issues.query.graphql'; import getIssuesQuery from 'ee_else_ce/issues_list/queries/get_issues.query.graphql';
import getIssuesCountsQuery from 'ee_else_ce/issues_list/queries/get_issues_counts.query.graphql'; import getIssuesCountsQuery from 'ee_else_ce/issues_list/queries/get_issues_counts.query.graphql';
...@@ -138,6 +139,9 @@ export default { ...@@ -138,6 +139,9 @@ export default {
initialEmail: { initialEmail: {
default: '', default: '',
}, },
isAnonymousSearchDisabled: {
default: false,
},
isIssueRepositioningDisabled: { isIssueRepositioningDisabled: {
default: false, default: false,
}, },
...@@ -183,12 +187,22 @@ export default { ...@@ -183,12 +187,22 @@ export default {
sortKey = defaultSortKey; sortKey = defaultSortKey;
} }
const isSearchDisabled =
this.isAnonymousSearchDisabled &&
!this.isSignedIn &&
window.location.search.includes('search=');
if (isSearchDisabled) {
this.showAnonymousSearchingMessage();
}
return { return {
dueDateFilter: getDueDateValue(getParameterByName(PARAM_DUE_DATE)), dueDateFilter: getDueDateValue(getParameterByName(PARAM_DUE_DATE)),
exportCsvPathWithQuery: this.getExportCsvPathWithQuery(), exportCsvPathWithQuery: this.getExportCsvPathWithQuery(),
filterTokens: getFilterTokens(window.location.search), filterTokens: isSearchDisabled ? [] : getFilterTokens(window.location.search),
issues: [], issues: [],
issuesCounts: {}, issuesCounts: {},
issuesError: null,
pageInfo: {}, pageInfo: {},
pageParams: getInitialPageParams(sortKey), pageParams: getInitialPageParams(sortKey),
showBulkEditSidebar: false, showBulkEditSidebar: false,
...@@ -210,7 +224,8 @@ export default { ...@@ -210,7 +224,8 @@ export default {
this.exportCsvPathWithQuery = this.getExportCsvPathWithQuery(); this.exportCsvPathWithQuery = this.getExportCsvPathWithQuery();
}, },
error(error) { error(error) {
createFlash({ message: this.$options.i18n.errorFetchingIssues, captureError: true, error }); this.issuesError = this.$options.i18n.errorFetchingIssues;
Sentry.captureException(error);
}, },
skip() { skip() {
return !this.hasAnyIssues; return !this.hasAnyIssues;
...@@ -226,7 +241,8 @@ export default { ...@@ -226,7 +241,8 @@ export default {
return data[this.namespace] ?? {}; return data[this.namespace] ?? {};
}, },
error(error) { error(error) {
createFlash({ message: this.$options.i18n.errorFetchingCounts, captureError: true, error }); this.issuesError = this.$options.i18n.errorFetchingCounts;
Sentry.captureException(error);
}, },
skip() { skip() {
return !this.hasAnyIssues; return !this.hasAnyIssues;
...@@ -387,6 +403,8 @@ export default { ...@@ -387,6 +403,8 @@ export default {
tokens.push(...this.eeSearchTokens); tokens.push(...this.eeSearchTokens);
} }
tokens.sort((a, b) => a.title.localeCompare(b.title));
return tokens; return tokens;
}, },
showPaginationControls() { showPaginationControls() {
...@@ -518,7 +536,14 @@ export default { ...@@ -518,7 +536,14 @@ export default {
} }
this.state = state; this.state = state;
}, },
handleDismissAlert() {
this.issuesError = null;
},
handleFilter(filter) { handleFilter(filter) {
if (this.isAnonymousSearchDisabled && !this.isSignedIn) {
this.showAnonymousSearchingMessage();
return;
}
this.pageParams = getInitialPageParams(this.sortKey); this.pageParams = getInitialPageParams(this.sortKey);
this.filterTokens = filter; this.filterTokens = filter;
}, },
...@@ -569,7 +594,8 @@ export default { ...@@ -569,7 +594,8 @@ export default {
}); });
}) })
.catch((error) => { .catch((error) => {
createFlash({ message: this.$options.i18n.reorderError, captureError: true, error }); this.issuesError = this.$options.i18n.reorderError;
Sentry.captureException(error);
}); });
}, },
handleSort(sortKey) { handleSort(sortKey) {
...@@ -583,6 +609,12 @@ export default { ...@@ -583,6 +609,12 @@ export default {
} }
this.sortKey = sortKey; this.sortKey = sortKey;
}, },
showAnonymousSearchingMessage() {
createFlash({
message: this.$options.i18n.anonymousSearchingMessage,
type: FLASH_TYPES.NOTICE,
});
},
showIssueRepositioningMessage() { showIssueRepositioningMessage() {
createFlash({ createFlash({
message: this.$options.i18n.issueRepositioningMessage, message: this.$options.i18n.issueRepositioningMessage,
...@@ -607,6 +639,7 @@ export default { ...@@ -607,6 +639,7 @@ export default {
:sort-options="sortOptions" :sort-options="sortOptions"
:initial-sort-by="sortKey" :initial-sort-by="sortKey"
:issuables="issues" :issuables="issues"
:error="issuesError"
label-filter-param="label_name" label-filter-param="label_name"
:tabs="$options.IssuableListTabs" :tabs="$options.IssuableListTabs"
:current-tab="state" :current-tab="state"
...@@ -620,6 +653,7 @@ export default { ...@@ -620,6 +653,7 @@ export default {
:has-previous-page="pageInfo.hasPreviousPage" :has-previous-page="pageInfo.hasPreviousPage"
:url-params="urlParams" :url-params="urlParams"
@click-tab="handleClickTab" @click-tab="handleClickTab"
@dismiss-alert="handleDismissAlert"
@filter="handleFilter" @filter="handleFilter"
@next-page="handleNextPage" @next-page="handleNextPage"
@previous-page="handlePreviousPage" @previous-page="handlePreviousPage"
......
...@@ -66,6 +66,7 @@ export const availableSortOptionsJira = [ ...@@ -66,6 +66,7 @@ export const availableSortOptionsJira = [
]; ];
export const i18n = { export const i18n = {
anonymousSearchingMessage: __('You must sign in to search for specific terms.'),
calendarLabel: __('Subscribe to calendar'), calendarLabel: __('Subscribe to calendar'),
closed: __('CLOSED'), closed: __('CLOSED'),
closedMoved: __('CLOSED (MOVED)'), closedMoved: __('CLOSED (MOVED)'),
...@@ -136,6 +137,7 @@ export const DUE_DATE_VALUES = [ ...@@ -136,6 +137,7 @@ export const DUE_DATE_VALUES = [
DUE_DATE_NEXT_MONTH_AND_PREVIOUS_TWO_WEEKS, DUE_DATE_NEXT_MONTH_AND_PREVIOUS_TWO_WEEKS,
]; ];
export const BLOCKING_ISSUES_ASC = 'BLOCKING_ISSUES_ASC';
export const BLOCKING_ISSUES_DESC = 'BLOCKING_ISSUES_DESC'; export const BLOCKING_ISSUES_DESC = 'BLOCKING_ISSUES_DESC';
export const CREATED_ASC = 'CREATED_ASC'; export const CREATED_ASC = 'CREATED_ASC';
export const CREATED_DESC = 'CREATED_DESC'; export const CREATED_DESC = 'CREATED_DESC';
...@@ -157,42 +159,28 @@ export const UPDATED_DESC = 'UPDATED_DESC'; ...@@ -157,42 +159,28 @@ export const UPDATED_DESC = 'UPDATED_DESC';
export const WEIGHT_ASC = 'WEIGHT_ASC'; export const WEIGHT_ASC = 'WEIGHT_ASC';
export const WEIGHT_DESC = 'WEIGHT_DESC'; export const WEIGHT_DESC = 'WEIGHT_DESC';
const PRIORITY_ASC_SORT = 'priority_asc';
const CREATED_DATE_SORT = 'created_date';
const CREATED_ASC_SORT = 'created_asc';
const UPDATED_DESC_SORT = 'updated_desc';
const UPDATED_ASC_SORT = 'updated_asc';
const MILESTONE_SORT = 'milestone';
const MILESTONE_DUE_DESC_SORT = 'milestone_due_desc';
const DUE_DATE_DESC_SORT = 'due_date_desc';
const LABEL_PRIORITY_ASC_SORT = 'label_priority_asc';
const POPULARITY_ASC_SORT = 'popularity_asc';
const WEIGHT_DESC_SORT = 'weight_desc';
const BLOCKING_ISSUES_DESC_SORT = 'blocking_issues_desc';
const TITLE_ASC_SORT = 'title_asc';
const TITLE_DESC_SORT = 'title_desc';
export const urlSortParams = { export const urlSortParams = {
[PRIORITY_ASC]: PRIORITY_ASC_SORT, [PRIORITY_ASC]: 'priority',
[PRIORITY_DESC]: PRIORITY, [PRIORITY_DESC]: 'priority_desc',
[CREATED_ASC]: CREATED_ASC_SORT, [CREATED_ASC]: 'created_asc',
[CREATED_DESC]: CREATED_DATE_SORT, [CREATED_DESC]: 'created_date',
[UPDATED_ASC]: UPDATED_ASC_SORT, [UPDATED_ASC]: 'updated_asc',
[UPDATED_DESC]: UPDATED_DESC_SORT, [UPDATED_DESC]: 'updated_desc',
[MILESTONE_DUE_ASC]: MILESTONE_SORT, [MILESTONE_DUE_ASC]: 'milestone',
[MILESTONE_DUE_DESC]: MILESTONE_DUE_DESC_SORT, [MILESTONE_DUE_DESC]: 'milestone_due_desc',
[DUE_DATE_ASC]: DUE_DATE, [DUE_DATE_ASC]: 'due_date',
[DUE_DATE_DESC]: DUE_DATE_DESC_SORT, [DUE_DATE_DESC]: 'due_date_desc',
[POPULARITY_ASC]: POPULARITY_ASC_SORT, [POPULARITY_ASC]: 'popularity_asc',
[POPULARITY_DESC]: POPULARITY, [POPULARITY_DESC]: 'popularity',
[LABEL_PRIORITY_ASC]: LABEL_PRIORITY_ASC_SORT, [LABEL_PRIORITY_ASC]: 'label_priority',
[LABEL_PRIORITY_DESC]: LABEL_PRIORITY, [LABEL_PRIORITY_DESC]: 'label_priority_desc',
[RELATIVE_POSITION_ASC]: RELATIVE_POSITION, [RELATIVE_POSITION_ASC]: RELATIVE_POSITION,
[WEIGHT_ASC]: WEIGHT, [WEIGHT_ASC]: 'weight',
[WEIGHT_DESC]: WEIGHT_DESC_SORT, [WEIGHT_DESC]: 'weight_desc',
[BLOCKING_ISSUES_DESC]: BLOCKING_ISSUES_DESC_SORT, [BLOCKING_ISSUES_ASC]: 'blocking_issues_asc',
[TITLE_ASC]: TITLE_ASC_SORT, [BLOCKING_ISSUES_DESC]: 'blocking_issues_desc',
[TITLE_DESC]: TITLE_DESC_SORT, [TITLE_ASC]: 'title_asc',
[TITLE_DESC]: 'title_desc',
}; };
export const MAX_LIST_SIZE = 10; export const MAX_LIST_SIZE = 10;
......
...@@ -129,6 +129,7 @@ export function mountIssuesListApp() { ...@@ -129,6 +129,7 @@ export function mountIssuesListApp() {
hasMultipleIssueAssigneesFeature, hasMultipleIssueAssigneesFeature,
importCsvIssuesPath, importCsvIssuesPath,
initialEmail, initialEmail,
isAnonymousSearchDisabled,
isIssueRepositioningDisabled, isIssueRepositioningDisabled,
isProject, isProject,
isSignedIn, isSignedIn,
...@@ -162,6 +163,7 @@ export function mountIssuesListApp() { ...@@ -162,6 +163,7 @@ export function mountIssuesListApp() {
hasIssueWeightsFeature: parseBoolean(hasIssueWeightsFeature), hasIssueWeightsFeature: parseBoolean(hasIssueWeightsFeature),
hasIterationsFeature: parseBoolean(hasIterationsFeature), hasIterationsFeature: parseBoolean(hasIterationsFeature),
hasMultipleIssueAssigneesFeature: parseBoolean(hasMultipleIssueAssigneesFeature), hasMultipleIssueAssigneesFeature: parseBoolean(hasMultipleIssueAssigneesFeature),
isAnonymousSearchDisabled: parseBoolean(isAnonymousSearchDisabled),
isIssueRepositioningDisabled: parseBoolean(isIssueRepositioningDisabled), isIssueRepositioningDisabled: parseBoolean(isIssueRepositioningDisabled),
isProject: parseBoolean(isProject), isProject: parseBoolean(isProject),
isSignedIn: parseBoolean(isSignedIn), isSignedIn: parseBoolean(isSignedIn),
......
import { import {
API_PARAM, API_PARAM,
BLOCKING_ISSUES_ASC,
BLOCKING_ISSUES_DESC, BLOCKING_ISSUES_DESC,
CREATED_ASC, CREATED_ASC,
CREATED_DESC, CREATED_DESC,
...@@ -143,7 +144,7 @@ export const getSortOptions = (hasIssueWeightsFeature, hasBlockedIssuesFeature) ...@@ -143,7 +144,7 @@ export const getSortOptions = (hasIssueWeightsFeature, hasBlockedIssuesFeature)
id: sortOptions.length + 1, id: sortOptions.length + 1,
title: __('Blocking'), title: __('Blocking'),
sortDirection: { sortDirection: {
ascending: BLOCKING_ISSUES_DESC, ascending: BLOCKING_ISSUES_ASC,
descending: BLOCKING_ISSUES_DESC, descending: BLOCKING_ISSUES_DESC,
}, },
}); });
......
<script> <script>
import { GlKeysetPagination, GlSkeletonLoading, GlPagination } from '@gitlab/ui'; import { GlAlert, GlKeysetPagination, GlSkeletonLoading, GlPagination } from '@gitlab/ui';
import { uniqueId } from 'lodash'; import { uniqueId } from 'lodash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { updateHistory, setUrlParams } from '~/lib/utils/url_utility'; import { updateHistory, setUrlParams } from '~/lib/utils/url_utility';
...@@ -19,6 +19,7 @@ export default { ...@@ -19,6 +19,7 @@ export default {
tag: 'ul', tag: 'ul',
}, },
components: { components: {
GlAlert,
GlKeysetPagination, GlKeysetPagination,
GlSkeletonLoading, GlSkeletonLoading,
IssuableTabs, IssuableTabs,
...@@ -156,6 +157,11 @@ export default { ...@@ -156,6 +157,11 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
error: {
type: String,
required: false,
default: '',
},
}, },
data() { data() {
return { return {
...@@ -277,6 +283,7 @@ export default { ...@@ -277,6 +283,7 @@ export default {
@onFilter="$emit('filter', $event)" @onFilter="$emit('filter', $event)"
@onSort="$emit('sort', $event)" @onSort="$emit('sort', $event)"
/> />
<gl-alert v-if="error" variant="danger" @dismiss="$emit('dismiss-alert')">{{ error }}</gl-alert>
<issuable-bulk-edit-sidebar :expanded="showBulkEditSidebar"> <issuable-bulk-edit-sidebar :expanded="showBulkEditSidebar">
<template #bulk-edit-actions> <template #bulk-edit-actions>
<slot name="bulk-edit-actions" :checked-issuables="bulkEditIssuables"></slot> <slot name="bulk-edit-actions" :checked-issuables="bulkEditIssuables"></slot>
......
...@@ -214,6 +214,7 @@ module IssuesHelper ...@@ -214,6 +214,7 @@ module IssuesHelper
calendar_path: url_for(safe_params.merge(calendar_url_options)), calendar_path: url_for(safe_params.merge(calendar_url_options)),
empty_state_svg_path: image_path('illustrations/issues.svg'), empty_state_svg_path: image_path('illustrations/issues.svg'),
full_path: namespace.full_path, full_path: namespace.full_path,
is_anonymous_search_disabled: Feature.enabled?(:disable_anonymous_search, type: :ops).to_s,
is_issue_repositioning_disabled: issue_repositioning_disabled?.to_s, is_issue_repositioning_disabled: issue_repositioning_disabled?.to_s,
is_signed_in: current_user.present?.to_s, is_signed_in: current_user.present?.to_s,
jira_integration_path: help_page_url('integration/jira/issues', anchor: 'view-jira-issues'), jira_integration_path: help_page_url('integration/jira/issues', anchor: 'view-jira-issues'),
......
...@@ -14,6 +14,7 @@ Object { ...@@ -14,6 +14,7 @@ Object {
"currentTab": "opened", "currentTab": "opened",
"defaultPageSize": 2, "defaultPageSize": 2,
"enableLabelPermalinks": true, "enableLabelPermalinks": true,
"error": "",
"hasNextPage": false, "hasNextPage": false,
"hasPreviousPage": false, "hasPreviousPage": false,
"initialFilterValue": Array [], "initialFilterValue": Array [],
......
...@@ -124,22 +124,22 @@ describe('EE IssuesListApp component', () => { ...@@ -124,22 +124,22 @@ describe('EE IssuesListApp component', () => {
window.gon = originalGon; window.gon = originalGon;
}); });
it('renders all tokens', () => { it('renders all tokens alphabetically', () => {
const preloadedAuthors = [ const preloadedAuthors = [
{ ...mockCurrentUser, id: convertToGraphQLId('User', mockCurrentUser.id) }, { ...mockCurrentUser, id: convertToGraphQLId('User', mockCurrentUser.id) },
]; ];
expect(findIssuableList().props('searchTokens')).toMatchObject([ expect(findIssuableList().props('searchTokens')).toMatchObject([
{ type: TOKEN_TYPE_AUTHOR, preloadedAuthors },
{ type: TOKEN_TYPE_ASSIGNEE, preloadedAuthors }, { type: TOKEN_TYPE_ASSIGNEE, preloadedAuthors },
{ type: TOKEN_TYPE_MILESTONE }, { type: TOKEN_TYPE_AUTHOR, preloadedAuthors },
{ type: TOKEN_TYPE_LABEL },
{ type: TOKEN_TYPE_TYPE },
{ type: TOKEN_TYPE_RELEASE },
{ type: TOKEN_TYPE_MY_REACTION },
{ type: TOKEN_TYPE_CONFIDENTIAL }, { type: TOKEN_TYPE_CONFIDENTIAL },
{ type: TOKEN_TYPE_ITERATION },
{ type: TOKEN_TYPE_EPIC }, { type: TOKEN_TYPE_EPIC },
{ type: TOKEN_TYPE_ITERATION },
{ type: TOKEN_TYPE_LABEL },
{ type: TOKEN_TYPE_MILESTONE },
{ type: TOKEN_TYPE_MY_REACTION },
{ type: TOKEN_TYPE_RELEASE },
{ type: TOKEN_TYPE_TYPE },
{ type: TOKEN_TYPE_WEIGHT }, { type: TOKEN_TYPE_WEIGHT },
]); ]);
}); });
......
import { GlButton, GlEmptyState, GlLink } from '@gitlab/ui'; import { GlButton, GlEmptyState, GlLink } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import { mount, shallowMount } from '@vue/test-utils'; import { mount, shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter'; import AxiosMockAdapter from 'axios-mock-adapter';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
...@@ -47,6 +48,7 @@ import axios from '~/lib/utils/axios_utils'; ...@@ -47,6 +48,7 @@ import axios from '~/lib/utils/axios_utils';
import { scrollUp } from '~/lib/utils/scroll_utils'; import { scrollUp } from '~/lib/utils/scroll_utils';
import { joinPaths } from '~/lib/utils/url_utility'; import { joinPaths } from '~/lib/utils/url_utility';
jest.mock('@sentry/browser');
jest.mock('~/flash'); jest.mock('~/flash');
jest.mock('~/lib/utils/scroll_utils', () => ({ jest.mock('~/lib/utils/scroll_utils', () => ({
scrollUp: jest.fn().mockName('scrollUpMock'), scrollUp: jest.fn().mockName('scrollUpMock'),
...@@ -357,6 +359,27 @@ describe('CE IssuesListApp component', () => { ...@@ -357,6 +359,27 @@ describe('CE IssuesListApp component', () => {
expect(findIssuableList().props('initialFilterValue')).toEqual(filteredTokens); expect(findIssuableList().props('initialFilterValue')).toEqual(filteredTokens);
}); });
describe('when anonymous searching is performed', () => {
beforeEach(() => {
setWindowLocation(locationSearch);
wrapper = mountComponent({
provide: { isAnonymousSearchDisabled: true, isSignedIn: false },
});
});
it('is not set from url params', () => {
expect(findIssuableList().props('initialFilterValue')).toEqual([]);
});
it('shows an alert to tell the user they must be signed in to search', () => {
expect(createFlash).toHaveBeenCalledWith({
message: IssuesListApp.i18n.anonymousSearchingMessage,
type: FLASH_TYPES.NOTICE,
});
});
});
}); });
}); });
...@@ -505,11 +528,7 @@ describe('CE IssuesListApp component', () => { ...@@ -505,11 +528,7 @@ describe('CE IssuesListApp component', () => {
describe('when user is signed out', () => { describe('when user is signed out', () => {
beforeEach(() => { beforeEach(() => {
wrapper = mountComponent({ wrapper = mountComponent({ provide: { isSignedIn: false } });
provide: {
isSignedIn: false,
},
});
}); });
it('does not render My-Reaction or Confidential tokens', () => { it('does not render My-Reaction or Confidential tokens', () => {
...@@ -541,20 +560,20 @@ describe('CE IssuesListApp component', () => { ...@@ -541,20 +560,20 @@ describe('CE IssuesListApp component', () => {
window.gon = originalGon; window.gon = originalGon;
}); });
it('renders all tokens', () => { it('renders all tokens alphabetically', () => {
const preloadedAuthors = [ const preloadedAuthors = [
{ ...mockCurrentUser, id: convertToGraphQLId('User', mockCurrentUser.id) }, { ...mockCurrentUser, id: convertToGraphQLId('User', mockCurrentUser.id) },
]; ];
expect(findIssuableList().props('searchTokens')).toMatchObject([ expect(findIssuableList().props('searchTokens')).toMatchObject([
{ type: TOKEN_TYPE_AUTHOR, preloadedAuthors },
{ type: TOKEN_TYPE_ASSIGNEE, preloadedAuthors }, { type: TOKEN_TYPE_ASSIGNEE, preloadedAuthors },
{ type: TOKEN_TYPE_MILESTONE }, { type: TOKEN_TYPE_AUTHOR, preloadedAuthors },
{ type: TOKEN_TYPE_CONFIDENTIAL },
{ type: TOKEN_TYPE_LABEL }, { type: TOKEN_TYPE_LABEL },
{ type: TOKEN_TYPE_TYPE }, { type: TOKEN_TYPE_MILESTONE },
{ type: TOKEN_TYPE_RELEASE },
{ type: TOKEN_TYPE_MY_REACTION }, { type: TOKEN_TYPE_MY_REACTION },
{ type: TOKEN_TYPE_CONFIDENTIAL }, { type: TOKEN_TYPE_RELEASE },
{ type: TOKEN_TYPE_TYPE },
]); ]);
}); });
}); });
...@@ -574,13 +593,18 @@ describe('CE IssuesListApp component', () => { ...@@ -574,13 +593,18 @@ describe('CE IssuesListApp component', () => {
}); });
it('shows an error message', () => { it('shows an error message', () => {
expect(createFlash).toHaveBeenCalledWith({ expect(findIssuableList().props('error')).toBe(message);
captureError: true, expect(Sentry.captureException).toHaveBeenCalledWith(new Error('Network error: ERROR'));
error: new Error('Network error: ERROR'),
message,
});
}); });
}); });
it('clears error message when "dismiss-alert" event is emitted from IssuableList', () => {
wrapper = mountComponent({ issuesQueryResponse: jest.fn().mockRejectedValue(new Error()) });
findIssuableList().vm.$emit('dismiss-alert');
expect(findIssuableList().props('error')).toBeNull();
});
}); });
describe('events', () => { describe('events', () => {
...@@ -705,11 +729,10 @@ describe('CE IssuesListApp component', () => { ...@@ -705,11 +729,10 @@ describe('CE IssuesListApp component', () => {
await waitForPromises(); await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({ expect(findIssuableList().props('error')).toBe(IssuesListApp.i18n.reorderError);
message: IssuesListApp.i18n.reorderError, expect(Sentry.captureException).toHaveBeenCalledWith(
captureError: true, new Error('Request failed with status code 500'),
error: new Error('Request failed with status code 500'), );
});
}); });
}); });
}); });
...@@ -770,15 +793,37 @@ describe('CE IssuesListApp component', () => { ...@@ -770,15 +793,37 @@ describe('CE IssuesListApp component', () => {
}); });
describe('when "filter" event is emitted by IssuableList', () => { describe('when "filter" event is emitted by IssuableList', () => {
beforeEach(() => { it('updates IssuableList with url params', async () => {
wrapper = mountComponent(); wrapper = mountComponent();
findIssuableList().vm.$emit('filter', filteredTokens); findIssuableList().vm.$emit('filter', filteredTokens);
}); await nextTick();
it('updates IssuableList with url params', () => {
expect(findIssuableList().props('urlParams')).toMatchObject(urlParams); expect(findIssuableList().props('urlParams')).toMatchObject(urlParams);
}); });
describe('when anonymous searching is performed', () => {
beforeEach(() => {
wrapper = mountComponent({
provide: { isAnonymousSearchDisabled: true, isSignedIn: false },
});
findIssuableList().vm.$emit('filter', filteredTokens);
});
it('does not update IssuableList with url params ', async () => {
const defaultParams = { sort: 'created_date', state: 'opened' };
expect(findIssuableList().props('urlParams')).toEqual(defaultParams);
});
it('shows an alert to tell the user they must be signed in to search', () => {
expect(createFlash).toHaveBeenCalledWith({
message: IssuesListApp.i18n.anonymousSearchingMessage,
type: FLASH_TYPES.NOTICE,
});
});
});
}); });
}); });
}); });
import { GlKeysetPagination, GlSkeletonLoading, GlPagination } from '@gitlab/ui'; import { GlAlert, GlKeysetPagination, GlSkeletonLoading, GlPagination } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import VueDraggable from 'vuedraggable'; import VueDraggable from 'vuedraggable';
...@@ -36,6 +36,7 @@ const createComponent = ({ props = {}, data = {} } = {}) => ...@@ -36,6 +36,7 @@ const createComponent = ({ props = {}, data = {} } = {}) =>
describe('IssuableListRoot', () => { describe('IssuableListRoot', () => {
let wrapper; let wrapper;
const findAlert = () => wrapper.findComponent(GlAlert);
const findFilteredSearchBar = () => wrapper.findComponent(FilteredSearchBar); const findFilteredSearchBar = () => wrapper.findComponent(FilteredSearchBar);
const findGlKeysetPagination = () => wrapper.findComponent(GlKeysetPagination); const findGlKeysetPagination = () => wrapper.findComponent(GlKeysetPagination);
const findGlPagination = () => wrapper.findComponent(GlPagination); const findGlPagination = () => wrapper.findComponent(GlPagination);
...@@ -310,6 +311,30 @@ describe('IssuableListRoot', () => { ...@@ -310,6 +311,30 @@ describe('IssuableListRoot', () => {
hasPreviousPage: true, hasPreviousPage: true,
}); });
}); });
describe('alert', () => {
const error = 'oopsie!';
it('shows alert when there is an error', () => {
wrapper = createComponent({ props: { error } });
expect(findAlert().text()).toBe(error);
});
it('emits "dismiss-alert" event when dismissed', () => {
wrapper = createComponent({ props: { error } });
findAlert().vm.$emit('dismiss');
expect(wrapper.emitted('dismiss-alert')).toEqual([[]]);
});
it('does not render when there is no error', () => {
wrapper = createComponent();
expect(findAlert().exists()).toBe(false);
});
});
}); });
describe('events', () => { describe('events', () => {
......
...@@ -321,6 +321,7 @@ RSpec.describe IssuesHelper do ...@@ -321,6 +321,7 @@ RSpec.describe IssuesHelper do
has_any_issues: project_issues(project).exists?.to_s, has_any_issues: project_issues(project).exists?.to_s,
import_csv_issues_path: '#', import_csv_issues_path: '#',
initial_email: project.new_issuable_address(current_user, 'issue'), initial_email: project.new_issuable_address(current_user, 'issue'),
is_anonymous_search_disabled: 'true',
is_issue_repositioning_disabled: 'true', is_issue_repositioning_disabled: 'true',
is_project: 'true', is_project: 'true',
is_signed_in: current_user.present?.to_s, is_signed_in: current_user.present?.to_s,
...@@ -342,6 +343,10 @@ RSpec.describe IssuesHelper do ...@@ -342,6 +343,10 @@ RSpec.describe IssuesHelper do
end end
describe '#project_issues_list_data' do describe '#project_issues_list_data' do
before do
stub_feature_flags(disable_anonymous_search: true)
end
context 'when user is signed in' do context 'when user is signed in' do
it_behaves_like 'issues list data' do it_behaves_like 'issues list data' do
let(:current_user) { double.as_null_object } let(:current_user) { double.as_null_object }
......
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