Commit f977c348 authored by Kushal Pandya's avatar Kushal Pandya

Fix labels fetch & text search for epics list

- Use public API to fetch group labels
- Add missing plain text search support for epics list
- Fix label color styling in dropdown
- Prevent duplicate labels request
parent 83816c40
......@@ -44,7 +44,7 @@ const Api = {
projectMilestonesPath: '/api/:version/projects/:id/milestones',
projectIssuePath: '/api/:version/projects/:id/issues/:issue_iid',
mergeRequestsPath: '/api/:version/merge_requests',
groupLabelsPath: '/groups/:namespace_path/-/labels',
groupLabelsPath: '/api/:version/groups/:namespace_path/labels',
issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key',
issuableTemplatesPath: '/:namespace_path/:project_path/templates/:type',
projectTemplatePath: '/api/:version/projects/:id/templates/:type/:key',
......@@ -402,18 +402,29 @@ const Api = {
newLabel(namespacePath, projectPath, data, callback) {
let url;
let payload;
if (projectPath) {
url = Api.buildUrl(Api.projectLabelsPath)
.replace(':namespace_path', namespacePath)
.replace(':project_path', projectPath);
payload = {
label: data,
};
} else {
url = Api.buildUrl(Api.groupLabelsPath).replace(':namespace_path', namespacePath);
// groupLabelsPath uses public API which accepts
// `name` and `color` props.
payload = {
name: data.title,
color: data.color,
};
}
return axios
.post(url, {
label: data,
...payload,
})
.then((res) => callback(res.data))
.catch((e) => callback(e.response.data));
......
......@@ -46,7 +46,7 @@ export default {
},
activeLabel() {
return this.labels.find(
(label) => label.title.toLowerCase() === stripQuotes(this.currentValue),
(label) => this.getLabelName(label).toLowerCase() === stripQuotes(this.currentValue),
);
},
containerStyle() {
......@@ -69,6 +69,21 @@ export default {
},
},
methods: {
/**
* There's an inconsistency between private and public API
* for labels where label name is included in a different
* property;
*
* Private API => `label.title`
* Public API => `label.name`
*
* This method allows compatibility as there may be instances
* where `config.fetchLabels` provided externally may still be
* using either of the two APIs.
*/
getLabelName(label) {
return label.name || label.title;
},
fetchLabelBySearchTerm(searchTerm) {
this.loading = true;
this.config
......@@ -85,7 +100,7 @@ export default {
});
},
searchLabels: debounce(function debouncedSearch({ data }) {
this.fetchLabelBySearchTerm(data);
if (!this.loading) this.fetchLabelBySearchTerm(data);
}, DEBOUNCE_DELAY),
},
};
......@@ -100,7 +115,7 @@ export default {
>
<template #view-token="{ inputValue, cssClasses, listeners }">
<gl-token variant="search-value" :class="cssClasses" :style="containerStyle" v-on="listeners"
>~{{ activeLabel ? activeLabel.title : inputValue }}</gl-token
>~{{ activeLabel ? getLabelName(activeLabel) : inputValue }}</gl-token
>
</template>
<template #suggestions>
......@@ -114,13 +129,17 @@ export default {
<gl-dropdown-divider v-if="defaultLabels.length" />
<gl-loading-icon v-if="loading" />
<template v-else>
<gl-filtered-search-suggestion v-for="label in labels" :key="label.id" :value="label.title">
<div class="gl-display-flex">
<gl-filtered-search-suggestion
v-for="label in labels"
:key="label.id"
:value="getLabelName(label)"
>
<div class="gl-display-flex gl-align-items-center">
<span
:style="{ backgroundColor: label.color }"
class="gl-display-inline-block mr-2 p-2"
></span>
<div>{{ label.title }}</div>
<div>{{ getLabelName(label) }}</div>
</div>
</gl-filtered-search-suggestion>
</template>
......
......@@ -9,6 +9,7 @@ query groupEpics(
$labelName: [String!]
$milestoneTitle: String = ""
$confidential: Boolean
$search: String = ""
$sortBy: EpicSort
$firstPageSize: Int
$lastPageSize: Int
......@@ -22,6 +23,7 @@ query groupEpics(
labelName: $labelName
milestoneTitle: $milestoneTitle
confidential: $confidential
search: $search
sort: $sortBy
first: $firstPageSize
last: $lastPageSize
......
......@@ -264,18 +264,18 @@ describe('Api', () => {
it('fetches group labels', (done) => {
const options = { params: { search: 'foo' } };
const expectedGroup = 'gitlab-org';
const expectedUrl = `${dummyUrlRoot}/groups/${expectedGroup}/-/labels`;
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${expectedGroup}/labels`;
mock.onGet(expectedUrl).reply(httpStatus.OK, [
{
id: 1,
title: 'Foo Label',
name: 'Foo Label',
},
]);
Api.groupLabels(expectedGroup, options)
.then((res) => {
expect(res.length).toBe(1);
expect(res[0].title).toBe('Foo Label');
expect(res[0].name).toBe('Foo Label');
})
.then(done)
.catch(done.fail);
......@@ -593,7 +593,7 @@ describe('Api', () => {
});
describe('newLabel', () => {
it('creates a new label', (done) => {
it('creates a new project label', (done) => {
const namespace = 'some namespace';
const project = 'some project';
const labelData = { some: 'data' };
......@@ -618,26 +618,23 @@ describe('Api', () => {
});
});
it('creates a group label', (done) => {
it('creates a new group label', (done) => {
const namespace = 'group/subgroup';
const labelData = { some: 'data' };
const labelData = { name: 'Foo', color: '#000000' };
const expectedUrl = Api.buildUrl(Api.groupLabelsPath).replace(':namespace_path', namespace);
const expectedData = {
label: labelData,
};
mock.onPost(expectedUrl).reply((config) => {
expect(config.data).toBe(JSON.stringify(expectedData));
expect(config.data).toBe(JSON.stringify({ color: labelData.color }));
return [
httpStatus.OK,
{
name: 'test',
...labelData,
},
];
});
Api.newLabel(namespace, undefined, labelData, (response) => {
expect(response.name).toBe('test');
expect(response.name).toBe('Foo');
done();
});
});
......
......@@ -118,6 +118,22 @@ describe('LabelToken', () => {
wrapper = createComponent();
});
describe('getLabelName', () => {
it('returns value of `name` or `title` property present in provided label param', () => {
let mockLabel = {
title: 'foo',
};
expect(wrapper.vm.getLabelName(mockLabel)).toBe(mockLabel.title);
mockLabel = {
name: 'foo',
};
expect(wrapper.vm.getLabelName(mockLabel)).toBe(mockLabel.name);
});
});
describe('fetchLabelBySearchTerm', () => {
it('calls `config.fetchLabels` with provided searchTerm param', () => {
jest.spyOn(wrapper.vm.config, 'fetchLabels');
......
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