Commit 85fc352f authored by Paul Slaughter's avatar Paul Slaughter

Merge branch '323311-add-current-selected-label-in-filter-tab-on-jira-issues-list' into 'master'

Add current selected label in filter tab on Jira issues list

See merge request gitlab-org/gitlab!65817
parents 336e17b4 9c2bacff
...@@ -37,6 +37,7 @@ export const SortDirection = { ...@@ -37,6 +37,7 @@ export const SortDirection = {
ascending: 'ascending', ascending: 'ascending',
}; };
export const FILTERED_SEARCH_LABELS = 'labels';
export const FILTERED_SEARCH_TERM = 'filtered-search-term'; export const FILTERED_SEARCH_TERM = 'filtered-search-term';
export const TOKEN_TITLE_AUTHOR = __('Author'); export const TOKEN_TITLE_AUTHOR = __('Author');
......
...@@ -117,6 +117,29 @@ export default { ...@@ -117,6 +117,29 @@ export default {
!this.preloadedTokenIds.includes(tokenValue[this.valueIdentifier]), !this.preloadedTokenIds.includes(tokenValue[this.valueIdentifier]),
); );
}, },
showDefaultSuggestions() {
return this.defaultSuggestions.length;
},
showRecentSuggestions() {
return this.isRecentSuggestionsEnabled && this.recentSuggestions.length && !this.searchKey;
},
showPreloadedSuggestions() {
return this.preloadedSuggestions.length && !this.searchKey;
},
showAvailableSuggestions() {
return this.availableSuggestions.length;
},
showSuggestions() {
// These conditions must match the template under `#suggestions` slot
// See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65817#note_632619411
return (
this.showDefaultSuggestions ||
this.showRecentSuggestions ||
this.showPreloadedSuggestions ||
this.suggestionsLoading ||
this.showAvailableSuggestions
);
},
}, },
watch: { watch: {
active: { active: {
...@@ -168,8 +191,8 @@ export default { ...@@ -168,8 +191,8 @@ export default {
<template #view="viewTokenProps"> <template #view="viewTokenProps">
<slot name="view" :view-token-props="{ ...viewTokenProps, activeTokenValue }"></slot> <slot name="view" :view-token-props="{ ...viewTokenProps, activeTokenValue }"></slot>
</template> </template>
<template #suggestions> <template v-if="showSuggestions" #suggestions>
<template v-if="defaultSuggestions.length"> <template v-if="showDefaultSuggestions">
<gl-filtered-search-suggestion <gl-filtered-search-suggestion
v-for="token in defaultSuggestions" v-for="token in defaultSuggestions"
:key="token.value" :key="token.value"
...@@ -179,13 +202,13 @@ export default { ...@@ -179,13 +202,13 @@ export default {
</gl-filtered-search-suggestion> </gl-filtered-search-suggestion>
<gl-dropdown-divider /> <gl-dropdown-divider />
</template> </template>
<template v-if="isRecentSuggestionsEnabled && recentSuggestions.length && !searchKey"> <template v-if="showRecentSuggestions">
<gl-dropdown-section-header>{{ __('Recently used') }}</gl-dropdown-section-header> <gl-dropdown-section-header>{{ __('Recently used') }}</gl-dropdown-section-header>
<slot name="suggestions-list" :suggestions="recentSuggestions"></slot> <slot name="suggestions-list" :suggestions="recentSuggestions"></slot>
<gl-dropdown-divider /> <gl-dropdown-divider />
</template> </template>
<slot <slot
v-if="preloadedSuggestions.length && !searchKey" v-if="showPreloadedSuggestions"
name="suggestions-list" name="suggestions-list"
:suggestions="preloadedSuggestions" :suggestions="preloadedSuggestions"
></slot> ></slot>
......
...@@ -10,6 +10,14 @@ import { ...@@ -10,6 +10,14 @@ import {
AvailableSortOptions, AvailableSortOptions,
DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZE,
} from '~/issuable_list/constants'; } from '~/issuable_list/constants';
import {
FILTERED_SEARCH_LABELS,
FILTERED_SEARCH_TERM,
OPERATOR_IS_ONLY,
TOKEN_TITLE_LABEL,
} from '~/vue_shared/components/filtered_search_bar/constants';
import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue';
import { ISSUES_LIST_FETCH_ERROR } from '../constants'; import { ISSUES_LIST_FETCH_ERROR } from '../constants';
import getJiraIssuesQuery from '../graphql/queries/get_jira_issues.query.graphql'; import getJiraIssuesQuery from '../graphql/queries/get_jira_issues.query.graphql';
import JiraIssuesListEmptyState from './jira_issues_list_empty_state.vue'; import JiraIssuesListEmptyState from './jira_issues_list_empty_state.vue';
...@@ -117,16 +125,48 @@ export default { ...@@ -117,16 +125,48 @@ export default {
}, },
}, },
methods: { methods: {
getFilteredSearchValue() { getFilteredSearchTokens() {
return [ return [
{ {
type: 'filtered-search-term', type: FILTERED_SEARCH_LABELS,
value: { icon: 'labels',
data: this.filterParams.search || '', symbol: '~',
title: TOKEN_TITLE_LABEL,
unique: false,
token: LabelToken,
operators: OPERATOR_IS_ONLY,
defaultLabels: [],
fetchLabels: () => {
return Promise.resolve([]);
}, },
}, },
]; ];
}, },
getFilteredSearchValue() {
const { labels, search } = this.filterParams || {};
const filteredSearchValue = [];
if (labels) {
filteredSearchValue.push(
...labels.map((label) => ({
type: FILTERED_SEARCH_LABELS,
value: { data: label },
})),
);
}
if (search) {
filteredSearchValue.push({
type: FILTERED_SEARCH_TERM,
value: {
data: search,
},
});
}
return filteredSearchValue;
},
onJiraIssuesQueryError(error) { onJiraIssuesQueryError(error) {
createFlash({ createFlash({
message: error.message, message: error.message,
...@@ -147,11 +187,21 @@ export default { ...@@ -147,11 +187,21 @@ export default {
}, },
onIssuableListFilter(filters = []) { onIssuableListFilter(filters = []) {
const filterParams = {}; const filterParams = {};
const labels = [];
const plainText = []; const plainText = [];
filters.forEach((filter) => { filters.forEach((filter) => {
if (filter.type === 'filtered-search-term' && filter.value.data) { if (!filter.value.data) return;
switch (filter.type) {
case FILTERED_SEARCH_LABELS:
labels.push(filter.value.data);
break;
case FILTERED_SEARCH_TERM:
plainText.push(filter.value.data); plainText.push(filter.value.data);
break;
default:
break;
} }
}); });
...@@ -159,6 +209,10 @@ export default { ...@@ -159,6 +209,10 @@ export default {
filterParams.search = plainText.join(' '); filterParams.search = plainText.join(' ');
} }
if (labels.length) {
filterParams.labels = labels;
}
this.filterParams = filterParams; this.filterParams = filterParams;
}, },
}, },
...@@ -171,7 +225,7 @@ export default { ...@@ -171,7 +225,7 @@ export default {
:tabs="$options.IssuableListTabs" :tabs="$options.IssuableListTabs"
:current-tab="currentState" :current-tab="currentState"
:search-input-placeholder="s__('Integrations|Search Jira issues')" :search-input-placeholder="s__('Integrations|Search Jira issues')"
:search-tokens="[]" :search-tokens="getFilteredSearchTokens()"
:sort-options="$options.AvailableSortOptions" :sort-options="$options.AvailableSortOptions"
:initial-filter-value="getFilteredSearchValue()" :initial-filter-value="getFilteredSearchValue()"
:initial-sort-by="sortedBy" :initial-sort-by="sortedBy"
......
...@@ -8,14 +8,7 @@ Object { ...@@ -8,14 +8,7 @@ Object {
"enableLabelPermalinks": true, "enableLabelPermalinks": true,
"hasNextPage": false, "hasNextPage": false,
"hasPreviousPage": false, "hasPreviousPage": false,
"initialFilterValue": Array [ "initialFilterValue": Array [],
Object {
"type": "filtered-search-term",
"value": Object {
"data": "",
},
},
],
"initialSortBy": "created_desc", "initialSortBy": "created_desc",
"isManualOrdering": false, "isManualOrdering": false,
"issuableSymbol": "#", "issuableSymbol": "#",
...@@ -107,7 +100,24 @@ Object { ...@@ -107,7 +100,24 @@ Object {
"previousPage": 0, "previousPage": 0,
"recentSearchesStorageKey": "jira_issues", "recentSearchesStorageKey": "jira_issues",
"searchInputPlaceholder": "Search Jira issues", "searchInputPlaceholder": "Search Jira issues",
"searchTokens": Array [], "searchTokens": Array [
Object {
"defaultLabels": Array [],
"fetchLabels": [Function],
"icon": "labels",
"operators": Array [
Object {
"description": "is",
"value": "=",
},
],
"symbol": "~",
"title": "Label",
"token": "LabelTokenMock",
"type": "labels",
"unique": false,
},
],
"showBulkEditSidebar": false, "showBulkEditSidebar": false,
"showPaginationControls": true, "showPaginationControls": true,
"sortOptions": Array [ "sortOptions": Array [
......
...@@ -23,6 +23,10 @@ jest.mock('~/issuable_list/constants', () => ({ ...@@ -23,6 +23,10 @@ jest.mock('~/issuable_list/constants', () => ({
IssuableListTabs: jest.requireActual('~/issuable_list/constants').IssuableListTabs, IssuableListTabs: jest.requireActual('~/issuable_list/constants').IssuableListTabs,
AvailableSortOptions: jest.requireActual('~/issuable_list/constants').AvailableSortOptions, AvailableSortOptions: jest.requireActual('~/issuable_list/constants').AvailableSortOptions,
})); }));
jest.mock(
'~/vue_shared/components/filtered_search_bar/tokens/label_token.vue',
() => 'LabelTokenMock',
);
const resolvedValue = { const resolvedValue = {
headers: { headers: {
...@@ -49,7 +53,12 @@ describe('JiraIssuesListRoot', () => { ...@@ -49,7 +53,12 @@ describe('JiraIssuesListRoot', () => {
let wrapper; let wrapper;
let mock; let mock;
const mockSearchTerm = 'test issue';
const mockLabel = 'ecosystem';
const findIssuableList = () => wrapper.findComponent(IssuableList); const findIssuableList = () => wrapper.findComponent(IssuableList);
const createLabelFilterEvent = (data) => ({ type: 'labels', value: { data } });
const createSearchFilterEvent = (data) => ({ type: 'filtered-search-term', value: { data } });
const createComponent = ({ const createComponent = ({
apolloProvider = createMockApolloProvider(), apolloProvider = createMockApolloProvider(),
...@@ -109,12 +118,15 @@ describe('JiraIssuesListRoot', () => { ...@@ -109,12 +118,15 @@ describe('JiraIssuesListRoot', () => {
}); });
describe('with `initialFilterParams` prop', () => { describe('with `initialFilterParams` prop', () => {
const mockSearchTerm = 'foo';
beforeEach(async () => { beforeEach(async () => {
jest.spyOn(axios, 'get').mockResolvedValue(resolvedValue); jest.spyOn(axios, 'get').mockResolvedValue(resolvedValue);
createComponent({ initialFilterParams: { search: mockSearchTerm } }); createComponent({
initialFilterParams: {
labels: [mockLabel],
search: mockSearchTerm,
},
});
await waitForPromises(); await waitForPromises();
}); });
...@@ -122,6 +134,7 @@ describe('JiraIssuesListRoot', () => { ...@@ -122,6 +134,7 @@ describe('JiraIssuesListRoot', () => {
const issuableList = findIssuableList(); const issuableList = findIssuableList();
expect(issuableList.props('initialFilterValue')).toEqual([ expect(issuableList.props('initialFilterValue')).toEqual([
{ type: 'labels', value: { data: mockLabel } },
{ type: 'filtered-search-term', value: { data: mockSearchTerm } }, { type: 'filtered-search-term', value: { data: mockSearchTerm } },
]); ]);
expect(issuableList.props('urlParams').search).toBe(mockSearchTerm); expect(issuableList.props('urlParams').search).toBe(mockSearchTerm);
...@@ -215,32 +228,31 @@ describe('JiraIssuesListRoot', () => { ...@@ -215,32 +228,31 @@ describe('JiraIssuesListRoot', () => {
expect(issuableList.props('initialSortBy')).toBe(mockSortBy); expect(issuableList.props('initialSortBy')).toBe(mockSortBy);
}); });
it('filter event sets `filterParams` value and calls fetchIssues', async () => { it.each`
const mockFilterTerm = 'foo'; desc | input | expected
${'with label and search'} | ${[createLabelFilterEvent(mockLabel), createSearchFilterEvent(mockSearchTerm)]} | ${{ labels: [mockLabel], search: mockSearchTerm }}
${'with multiple lables'} | ${[createLabelFilterEvent('label1'), createLabelFilterEvent('label2')]} | ${{ labels: ['label1', 'label2'], search: undefined }}
${'with multiple searches'} | ${[createSearchFilterEvent('foo bar'), createSearchFilterEvent('lorem')]} | ${{ labels: undefined, search: 'foo bar lorem' }}
`(
'$desc, filter event sets "filterParams" value and calls fetchIssues',
async ({ input, expected }) => {
const issuableList = findIssuableList(); const issuableList = findIssuableList();
issuableList.vm.$emit('filter', [ issuableList.vm.$emit('filter', input);
{
type: 'filtered-search-term',
value: {
data: mockFilterTerm,
},
},
]);
await waitForPromises(); await waitForPromises();
expect(axios.get).toHaveBeenCalledWith(mockProvide.issuesFetchPath, { expect(axios.get).toHaveBeenCalledWith(mockProvide.issuesFetchPath, {
params: { params: {
labels: undefined,
page: 1, page: 1,
per_page: 2, per_page: 2,
search: mockFilterTerm,
sort: 'created_desc', sort: 'created_desc',
state: 'opened', state: 'opened',
with_labels_details: true, with_labels_details: true,
...expected,
}, },
}); });
}); },
);
}); });
}); });
......
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