Commit c15911af authored by Phil Hughes's avatar Phil Hughes

Merge branch 'kp-use-base-token-for-label-token' into 'master'

Refactor LabelToken to use BaseToken

See merge request gitlab-org/gitlab!61992
parents 7daf57fa 2a18d518
...@@ -21,7 +21,7 @@ export const DEFAULT_ITERATIONS = DEFAULT_NONE_ANY.concat([ ...@@ -21,7 +21,7 @@ export const DEFAULT_ITERATIONS = DEFAULT_NONE_ANY.concat([
{ value: FILTER_CURRENT, text: __(FILTER_CURRENT) }, { value: FILTER_CURRENT, text: __(FILTER_CURRENT) },
]); ]);
export const DEFAULT_LABELS = [{ value: 'No label', text: __('No label') }]; // eslint-disable-line @gitlab/require-i18n-strings export const DEFAULT_LABELS = [DEFAULT_LABEL_NONE, DEFAULT_LABEL_ANY];
export const DEFAULT_MILESTONES = DEFAULT_NONE_ANY.concat([ export const DEFAULT_MILESTONES = DEFAULT_NONE_ANY.concat([
{ value: 'Upcoming', text: __('Upcoming') }, // eslint-disable-line @gitlab/require-i18n-strings { value: 'Upcoming', text: __('Upcoming') }, // eslint-disable-line @gitlab/require-i18n-strings
......
...@@ -120,7 +120,7 @@ export default { ...@@ -120,7 +120,7 @@ export default {
}, DEBOUNCE_DELAY); }, DEBOUNCE_DELAY);
}, },
handleTokenValueSelected(activeTokenValue) { handleTokenValueSelected(activeTokenValue) {
if (this.isRecentTokenValuesEnabled) { if (this.isRecentTokenValuesEnabled && activeTokenValue) {
setTokenValueToRecentlyUsed(this.recentTokenValuesStorageKey, activeTokenValue); setTokenValueToRecentlyUsed(this.recentTokenValuesStorageKey, activeTokenValue);
} }
}, },
......
<script> <script>
import { import { GlToken, GlFilteredSearchSuggestion } from '@gitlab/ui';
GlToken,
GlFilteredSearchToken,
GlFilteredSearchSuggestion,
GlDropdownDivider,
GlLoadingIcon,
} from '@gitlab/ui';
import { debounce } from 'lodash';
import { deprecatedCreateFlash as createFlash } from '~/flash'; import { deprecatedCreateFlash as createFlash } from '~/flash';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { DEFAULT_LABELS, DEBOUNCE_DELAY } from '../constants'; import { DEFAULT_LABELS } from '../constants';
import { stripQuotes } from '../filtered_search_utils'; import { stripQuotes } from '../filtered_search_utils';
import BaseToken from './base_token.vue';
export default { export default {
components: { components: {
BaseToken,
GlToken, GlToken,
GlFilteredSearchToken,
GlFilteredSearchSuggestion, GlFilteredSearchSuggestion,
GlDropdownDivider,
GlLoadingIcon,
}, },
props: { props: {
config: { config: {
...@@ -32,43 +25,24 @@ export default { ...@@ -32,43 +25,24 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
active: {
type: Boolean,
required: true,
},
}, },
data() { data() {
return { return {
labels: this.config.initialLabels || [], labels: this.config.initialLabels || [],
defaultLabels: this.config.defaultLabels || DEFAULT_LABELS, defaultLabels: this.config.defaultLabels || DEFAULT_LABELS,
loading: true, loading: false,
}; };
}, },
computed: { methods: {
currentValue() { getActiveLabel(labels, currentValue) {
return this.value.data.toLowerCase(); return labels.find(
}, (label) => this.getLabelName(label).toLowerCase() === stripQuotes(currentValue),
activeLabel() {
return this.labels.find(
(label) => this.getLabelName(label).toLowerCase() === stripQuotes(this.currentValue),
); );
}, },
containerStyle() {
if (this.activeLabel) {
const { color, textColor } = convertObjectPropsToCamelCase(this.activeLabel);
return { backgroundColor: color, color: textColor };
}
return {};
},
},
watch: {
active: {
immediate: true,
handler(newValue) {
if (!newValue && !this.labels.length) {
this.fetchLabelBySearchTerm(this.value.data);
}
},
},
},
methods: {
/** /**
* There's an inconsistency between private and public API * There's an inconsistency between private and public API
* for labels where label name is included in a different * for labels where label name is included in a different
...@@ -84,6 +58,16 @@ export default { ...@@ -84,6 +58,16 @@ export default {
getLabelName(label) { getLabelName(label) {
return label.name || label.title; return label.name || label.title;
}, },
getContainerStyle(activeLabel) {
if (activeLabel) {
const { color: backgroundColor, textColor: color } = convertObjectPropsToCamelCase(
activeLabel,
);
return { backgroundColor, color };
}
return {};
},
fetchLabelBySearchTerm(searchTerm) { fetchLabelBySearchTerm(searchTerm) {
this.loading = true; this.loading = true;
this.config this.config
...@@ -99,50 +83,47 @@ export default { ...@@ -99,50 +83,47 @@ export default {
this.loading = false; this.loading = false;
}); });
}, },
searchLabels: debounce(function debouncedSearch({ data }) {
if (!this.loading) this.fetchLabelBySearchTerm(data);
}, DEBOUNCE_DELAY),
}, },
}; };
</script> </script>
<template> <template>
<gl-filtered-search-token <base-token
:config="config" :token-config="config"
v-bind="{ ...$props, ...$attrs }" :token-value="value"
v-on="$listeners" :token-active="active"
@input="searchLabels" :tokens-list-loading="loading"
:token-values="labels"
:fn-active-token-value="getActiveLabel"
:default-token-values="defaultLabels"
:recent-token-values-storage-key="config.recentTokenValuesStorageKey"
@fetch-token-values="fetchLabelBySearchTerm"
> >
<template #view-token="{ inputValue, cssClasses, listeners }"> <template
<gl-token variant="search-value" :class="cssClasses" :style="containerStyle" v-on="listeners" #view-token="{ viewTokenProps: { inputValue, cssClasses, listeners, activeTokenValue } }"
>~{{ activeLabel ? getLabelName(activeLabel) : inputValue }}</gl-token >
<gl-token
variant="search-value"
:class="cssClasses"
:style="getContainerStyle(activeTokenValue)"
v-on="listeners"
>~{{ activeTokenValue ? getLabelName(activeTokenValue) : inputValue }}</gl-token
> >
</template> </template>
<template #suggestions> <template #token-values-list="{ tokenValues }">
<gl-filtered-search-suggestion <gl-filtered-search-suggestion
v-for="label in defaultLabels" v-for="label in tokenValues"
:key="label.value" :key="label.id"
:value="label.value" :value="getLabelName(label)"
> >
{{ label.text }} <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>{{ getLabelName(label) }}</div>
</div>
</gl-filtered-search-suggestion> </gl-filtered-search-suggestion>
<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="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>{{ getLabelName(label) }}</div>
</div>
</gl-filtered-search-suggestion>
</template>
</template> </template>
</gl-filtered-search-token> </base-token>
</template> </template>
...@@ -30,7 +30,8 @@ export default { ...@@ -30,7 +30,8 @@ export default {
], ],
token: LabelToken, token: LabelToken,
unique: false, unique: false,
symbol: '~', // eslint-disable-next-line @gitlab/require-i18n-strings
defaultLabels: [{ value: 'No label', text: __('No label') }],
fetchLabels: this.fetchLabels, fetchLabels: this.fetchLabels,
}, },
{ {
......
...@@ -62,6 +62,7 @@ export default { ...@@ -62,6 +62,7 @@ export default {
symbol: '~', symbol: '~',
token: LabelToken, token: LabelToken,
operators: OPERATOR_IS_ONLY, operators: OPERATOR_IS_ONLY,
recentTokenValuesStorageKey: `${this.groupFullPath}-epics-recent-tokens-label_name`,
fetchLabels: (search = '') => { fetchLabels: (search = '') => {
const params = { const params = {
only_group_labels: true, only_group_labels: true,
......
...@@ -41,7 +41,7 @@ describe('EpicFilteredSearch', () => { ...@@ -41,7 +41,7 @@ describe('EpicFilteredSearch', () => {
], ],
token: LabelToken, token: LabelToken,
unique: false, unique: false,
symbol: '~', defaultLabels: [{ value: 'No label', text: 'No label' }],
fetchLabels: wrapper.vm.fetchLabels, fetchLabels: wrapper.vm.fetchLabels,
}, },
{ {
......
...@@ -185,6 +185,7 @@ describe('RoadmapFilters', () => { ...@@ -185,6 +185,7 @@ describe('RoadmapFilters', () => {
symbol: '~', symbol: '~',
token: LabelToken, token: LabelToken,
operators, operators,
recentTokenValuesStorageKey: 'gitlab-org-epics-recent-tokens-label_name',
fetchLabels: expect.any(Function), fetchLabels: expect.any(Function),
}, },
{ {
......
import { import {
GlFilteredSearchToken,
GlFilteredSearchSuggestion, GlFilteredSearchSuggestion,
GlFilteredSearchTokenSegment, GlFilteredSearchTokenSegment,
GlDropdownDivider, GlDropdownDivider,
...@@ -18,6 +17,7 @@ import { ...@@ -18,6 +17,7 @@ import {
DEFAULT_LABELS, DEFAULT_LABELS,
DEFAULT_NONE_ANY, DEFAULT_NONE_ANY,
} from '~/vue_shared/components/filtered_search_bar/constants'; } from '~/vue_shared/components/filtered_search_bar/constants';
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue'; import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue';
import { mockLabelToken } from '../mock_data'; import { mockLabelToken } from '../mock_data';
...@@ -25,6 +25,7 @@ import { mockLabelToken } from '../mock_data'; ...@@ -25,6 +25,7 @@ import { mockLabelToken } from '../mock_data';
jest.mock('~/flash'); jest.mock('~/flash');
const defaultStubs = { const defaultStubs = {
Portal: true, Portal: true,
BaseToken,
GlFilteredSearchSuggestionList: { GlFilteredSearchSuggestionList: {
template: '<div></div>', template: '<div></div>',
methods: { methods: {
...@@ -68,55 +69,17 @@ describe('LabelToken', () => { ...@@ -68,55 +69,17 @@ describe('LabelToken', () => {
wrapper.destroy(); wrapper.destroy();
}); });
describe('computed', () => {
beforeEach(async () => {
// Label title with spaces is always enclosed in quotations by component.
wrapper = createComponent({ value: { data: `"${mockRegularLabel.title}"` } });
wrapper.setData({
labels: mockLabels,
});
await wrapper.vm.$nextTick();
});
describe('currentValue', () => {
it('returns lowercase string for `value.data`', () => {
expect(wrapper.vm.currentValue).toBe('"foo label"');
});
});
describe('activeLabel', () => {
it('returns object for currently present `value.data`', () => {
expect(wrapper.vm.activeLabel).toEqual(mockRegularLabel);
});
});
describe('containerStyle', () => {
it('returns object containing `backgroundColor` and `color` properties based on `activeLabel` value', () => {
expect(wrapper.vm.containerStyle).toEqual({
backgroundColor: mockRegularLabel.color,
color: mockRegularLabel.textColor,
});
});
it('returns empty object when `activeLabel` is not set', async () => {
wrapper.setData({
labels: [],
});
await wrapper.vm.$nextTick();
expect(wrapper.vm.containerStyle).toEqual({});
});
});
});
describe('methods', () => { describe('methods', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent(); wrapper = createComponent();
}); });
describe('getActiveLabel', () => {
it('returns label object from labels array based on provided `currentValue` param', () => {
expect(wrapper.vm.getActiveLabel(mockLabels, 'foo label')).toEqual(mockRegularLabel);
});
});
describe('getLabelName', () => { describe('getLabelName', () => {
it('returns value of `name` or `title` property present in provided label param', () => { it('returns value of `name` or `title` property present in provided label param', () => {
let mockLabel = { let mockLabel = {
...@@ -187,8 +150,14 @@ describe('LabelToken', () => { ...@@ -187,8 +150,14 @@ describe('LabelToken', () => {
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
}); });
it('renders gl-filtered-search-token component', () => { it('renders base-token component', () => {
expect(wrapper.find(GlFilteredSearchToken).exists()).toBe(true); const baseTokenEl = wrapper.find(BaseToken);
expect(baseTokenEl.exists()).toBe(true);
expect(baseTokenEl.props()).toMatchObject({
tokenValues: mockLabels,
fnActiveTokenValue: wrapper.vm.getActiveLabel,
});
}); });
it('renders token item when value is selected', () => { it('renders token item when value is selected', () => {
......
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