Commit 8c075e07 authored by Andrew Fontaine's avatar Andrew Fontaine

Merge branch 'tbulva-search-autocomplete-icons' into 'master'

Search Autocomplete: Review how icons are generated

See merge request gitlab-org/gitlab!82724
parents 3ddc5181 85a3bea5
...@@ -12,7 +12,17 @@ import { mapState, mapGetters } from 'vuex'; ...@@ -12,7 +12,17 @@ import { mapState, mapGetters } from 'vuex';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import highlight from '~/lib/utils/highlight'; import highlight from '~/lib/utils/highlight';
import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants'; import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants';
import { GROUPS_CATEGORY, PROJECTS_CATEGORY, LARGE_AVATAR_PX, SMALL_AVATAR_PX } from '../constants'; import { truncateNamespace } from '~/lib/utils/text_utility';
import {
GROUPS_CATEGORY,
PROJECTS_CATEGORY,
MERGE_REQUEST_CATEGORY,
ISSUES_CATEGORY,
RECENT_EPICS_CATEGORY,
LARGE_AVATAR_PX,
SMALL_AVATAR_PX,
} from '../constants';
export default { export default {
name: 'HeaderSearchAutocompleteItems', name: 'HeaderSearchAutocompleteItems',
...@@ -40,7 +50,7 @@ export default { ...@@ -40,7 +50,7 @@ export default {
}, },
}, },
computed: { computed: {
...mapState(['search', 'loading', 'autocompleteError']), ...mapState(['search', 'loading', 'autocompleteError', 'searchContext']),
...mapGetters(['autocompleteGroupedSearchOptions']), ...mapGetters(['autocompleteGroupedSearchOptions']),
}, },
watch: { watch: {
...@@ -53,6 +63,13 @@ export default { ...@@ -53,6 +63,13 @@ export default {
}, },
}, },
methods: { methods: {
truncateNamespace(string) {
if (string.split(' / ').length > 2) {
return truncateNamespace(string);
}
return string;
},
highlightedName(val) { highlightedName(val) {
return highlight(val, this.search); return highlight(val, this.search);
}, },
...@@ -66,6 +83,35 @@ export default { ...@@ -66,6 +83,35 @@ export default {
isOptionFocused(data) { isOptionFocused(data) {
return this.currentFocusedOption?.html_id === data.html_id; return this.currentFocusedOption?.html_id === data.html_id;
}, },
isProjectsCategory(data) {
return data.category === PROJECTS_CATEGORY;
},
getEntityId(data) {
switch (data.category) {
case GROUPS_CATEGORY:
case RECENT_EPICS_CATEGORY:
return data.group_id || data.id || this.searchContext?.group?.id;
case PROJECTS_CATEGORY:
case ISSUES_CATEGORY:
case MERGE_REQUEST_CATEGORY:
return data.project_id || data.id || this.searchContext?.project?.id;
default:
return data.id;
}
},
getEntitytName(data) {
switch (data.category) {
case GROUPS_CATEGORY:
case RECENT_EPICS_CATEGORY:
return data.group_name || data.value || data.label || this.searchContext?.group?.name;
case PROJECTS_CATEGORY:
case ISSUES_CATEGORY:
case MERGE_REQUEST_CATEGORY:
return data.project_name || data.value || data.label || this.searchContext?.project?.name;
default:
return data.label;
}
},
}, },
AVATAR_SHAPE_OPTION_RECT, AVATAR_SHAPE_OPTION_RECT,
}; };
...@@ -92,12 +138,22 @@ export default { ...@@ -92,12 +138,22 @@ export default {
<gl-avatar <gl-avatar
v-if="data.avatar_url !== undefined" v-if="data.avatar_url !== undefined"
:src="data.avatar_url" :src="data.avatar_url"
:entity-id="data.id" :entity-id="getEntityId(data)"
:entity-name="data.label" :entity-name="getEntitytName(data)"
:size="avatarSize(data)" :size="avatarSize(data)"
:shape="$options.AVATAR_SHAPE_OPTION_RECT" :shape="$options.AVATAR_SHAPE_OPTION_RECT"
/> />
<span v-safe-html="highlightedName(data.label)"></span> <span class="gl-display-flex gl-flex-direction-column">
<span
v-safe-html="highlightedName(data.value || data.label)"
class="gl-text-gray-900"
></span>
<span
v-if="data.value"
v-safe-html="truncateNamespace(data.label)"
class="gl-font-sm gl-text-gray-500"
></span>
</span>
</div> </div>
</gl-dropdown-item> </gl-dropdown-item>
</div> </div>
......
...@@ -20,6 +20,12 @@ export const GROUPS_CATEGORY = 'Groups'; ...@@ -20,6 +20,12 @@ export const GROUPS_CATEGORY = 'Groups';
export const PROJECTS_CATEGORY = 'Projects'; export const PROJECTS_CATEGORY = 'Projects';
export const ISSUES_CATEGORY = 'Recent issues';
export const MERGE_REQUEST_CATEGORY = 'Recent merge requests';
export const RECENT_EPICS_CATEGORY = 'Recent epics';
export const LARGE_AVATAR_PX = 32; export const LARGE_AVATAR_PX = 32;
export const SMALL_AVATAR_PX = 16; export const SMALL_AVATAR_PX = 16;
......
...@@ -260,6 +260,7 @@ module SearchHelper ...@@ -260,6 +260,7 @@ module SearchHelper
{ {
category: "Groups", category: "Groups",
id: group.id, id: group.id,
value: "#{search_result_sanitize(group.name)}",
label: "#{search_result_sanitize(group.full_name)}", label: "#{search_result_sanitize(group.full_name)}",
url: group_path(group), url: group_path(group),
avatar_url: group.avatar_url || '' avatar_url: group.avatar_url || ''
...@@ -311,7 +312,9 @@ module SearchHelper ...@@ -311,7 +312,9 @@ module SearchHelper
id: mr.id, id: mr.id,
label: search_result_sanitize(mr.title), label: search_result_sanitize(mr.title),
url: merge_request_path(mr), url: merge_request_path(mr),
avatar_url: mr.project.avatar_url || '' avatar_url: mr.project.avatar_url || '',
project_id: mr.target_project_id,
project_name: mr.target_project.name
} }
end end
end end
...@@ -325,7 +328,9 @@ module SearchHelper ...@@ -325,7 +328,9 @@ module SearchHelper
id: i.id, id: i.id,
label: search_result_sanitize(i.title), label: search_result_sanitize(i.title),
url: issue_path(i), url: issue_path(i),
avatar_url: i.project.avatar_url || '' avatar_url: i.project.avatar_url || '',
project_id: i.project_id,
project_name: i.project.name
} }
end end
end end
......
...@@ -154,7 +154,9 @@ module EE ...@@ -154,7 +154,9 @@ module EE
id: e.id, id: e.id,
label: search_result_sanitize(e.title), label: search_result_sanitize(e.title),
url: epic_path(e), url: epic_path(e),
avatar_url: e.group.avatar_url || '' avatar_url: e.group.avatar_url || '',
group_id: e.group_id,
group_name: e.group&.name
} }
end end
end end
......
...@@ -8,6 +8,9 @@ import { ...@@ -8,6 +8,9 @@ import {
LARGE_AVATAR_PX, LARGE_AVATAR_PX,
PROJECTS_CATEGORY, PROJECTS_CATEGORY,
SMALL_AVATAR_PX, SMALL_AVATAR_PX,
ISSUES_CATEGORY,
MERGE_REQUEST_CATEGORY,
RECENT_EPICS_CATEGORY,
} from '~/header_search/constants'; } from '~/header_search/constants';
import { import {
MOCK_GROUPED_AUTOCOMPLETE_OPTIONS, MOCK_GROUPED_AUTOCOMPLETE_OPTIONS,
...@@ -50,7 +53,12 @@ describe('HeaderSearchAutocompleteItems', () => { ...@@ -50,7 +53,12 @@ describe('HeaderSearchAutocompleteItems', () => {
const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem); const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
const findGlDropdownDividers = () => wrapper.findAllComponents(GlDropdownDivider); const findGlDropdownDividers = () => wrapper.findAllComponents(GlDropdownDivider);
const findFirstDropdownItem = () => findDropdownItems().at(0); const findFirstDropdownItem = () => findDropdownItems().at(0);
const findDropdownItemTitles = () => findDropdownItems().wrappers.map((w) => w.text()); const findDropdownItemTitles = () =>
findDropdownItems().wrappers.map((w) => w.findAll('span').at(1).text());
const findDropdownItemSubTitles = () =>
findDropdownItems()
.wrappers.filter((w) => w.findAll('span').length > 2)
.map((w) => w.findAll('span').at(2).text());
const findDropdownItemLinks = () => findDropdownItems().wrappers.map((w) => w.attributes('href')); const findDropdownItemLinks = () => findDropdownItems().wrappers.map((w) => w.attributes('href'));
const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findGlAvatar = () => wrapper.findComponent(GlAvatar); const findGlAvatar = () => wrapper.findComponent(GlAvatar);
...@@ -95,10 +103,17 @@ describe('HeaderSearchAutocompleteItems', () => { ...@@ -95,10 +103,17 @@ describe('HeaderSearchAutocompleteItems', () => {
}); });
it('renders titles correctly', () => { it('renders titles correctly', () => {
const expectedTitles = MOCK_SORTED_AUTOCOMPLETE_OPTIONS.map((o) => o.label); const expectedTitles = MOCK_SORTED_AUTOCOMPLETE_OPTIONS.map((o) => o.value || o.label);
expect(findDropdownItemTitles()).toStrictEqual(expectedTitles); expect(findDropdownItemTitles()).toStrictEqual(expectedTitles);
}); });
it('renders sub-titles correctly', () => {
const expectedSubTitles = MOCK_SORTED_AUTOCOMPLETE_OPTIONS.filter((o) => o.value).map(
(o) => o.label,
);
expect(findDropdownItemSubTitles()).toStrictEqual(expectedSubTitles);
});
it('renders links correctly', () => { it('renders links correctly', () => {
const expectedLinks = MOCK_SORTED_AUTOCOMPLETE_OPTIONS.map((o) => o.url); const expectedLinks = MOCK_SORTED_AUTOCOMPLETE_OPTIONS.map((o) => o.url);
expect(findDropdownItemLinks()).toStrictEqual(expectedLinks); expect(findDropdownItemLinks()).toStrictEqual(expectedLinks);
...@@ -106,15 +121,30 @@ describe('HeaderSearchAutocompleteItems', () => { ...@@ -106,15 +121,30 @@ describe('HeaderSearchAutocompleteItems', () => {
}); });
describe.each` describe.each`
item | showAvatar | avatarSize item | showAvatar | avatarSize | searchContext | entityId | entityName
${{ data: [{ category: PROJECTS_CATEGORY, avatar_url: null }] }} | ${true} | ${String(LARGE_AVATAR_PX)} ${{ data: [{ category: PROJECTS_CATEGORY, avatar_url: null }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${{ project: { id: 29 } }} | ${'29'} | ${''}
${{ data: [{ category: GROUPS_CATEGORY, avatar_url: '/123' }] }} | ${true} | ${String(LARGE_AVATAR_PX)} ${{ data: [{ category: GROUPS_CATEGORY, avatar_url: '/123' }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${{ group: { id: 12 } }} | ${'12'} | ${''}
${{ data: [{ category: 'Help', avatar_url: '' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} ${{ data: [{ category: 'Help', avatar_url: '' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${null} | ${'0'} | ${''}
${{ data: [{ category: 'Settings' }] }} | ${false} | ${false} ${{ data: [{ category: 'Settings' }] }} | ${false} | ${false} | ${null} | ${false} | ${false}
`('GlAvatar', ({ item, showAvatar, avatarSize }) => { ${{ data: [{ category: GROUPS_CATEGORY, avatar_url: null }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${{ group: { id: 1, name: 'test1' } }} | ${'1'} | ${'test1'}
${{ data: [{ category: PROJECTS_CATEGORY, avatar_url: null }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${{ project: { id: 2, name: 'test2' } }} | ${'2'} | ${'test2'}
${{ data: [{ category: ISSUES_CATEGORY, avatar_url: null }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${{ project: { id: 3, name: 'test3' } }} | ${'3'} | ${'test3'}
${{ data: [{ category: MERGE_REQUEST_CATEGORY, avatar_url: null }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${{ project: { id: 4, name: 'test4' } }} | ${'4'} | ${'test4'}
${{ data: [{ category: RECENT_EPICS_CATEGORY, avatar_url: null }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${{ group: { id: 5, name: 'test5' } }} | ${'5'} | ${'test5'}
${{ data: [{ category: GROUPS_CATEGORY, avatar_url: null, group_id: 6, group_name: 'test6' }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${null} | ${'6'} | ${'test6'}
${{ data: [{ category: PROJECTS_CATEGORY, avatar_url: null, project_id: 7, project_name: 'test7' }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${null} | ${'7'} | ${'test7'}
${{ data: [{ category: ISSUES_CATEGORY, avatar_url: null, project_id: 8, project_name: 'test8' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${null} | ${'8'} | ${'test8'}
${{ data: [{ category: MERGE_REQUEST_CATEGORY, avatar_url: null, project_id: 9, project_name: 'test9' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${null} | ${'9'} | ${'test9'}
${{ data: [{ category: RECENT_EPICS_CATEGORY, avatar_url: null, group_id: 10, group_name: 'test10' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${null} | ${'10'} | ${'test10'}
${{ data: [{ category: GROUPS_CATEGORY, avatar_url: null, group_id: 11, group_name: 'test11' }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${{ group: { id: 1, name: 'test1' } }} | ${'11'} | ${'test11'}
${{ data: [{ category: PROJECTS_CATEGORY, avatar_url: null, project_id: 12, project_name: 'test12' }] }} | ${true} | ${String(LARGE_AVATAR_PX)} | ${{ project: { id: 2, name: 'test2' } }} | ${'12'} | ${'test12'}
${{ data: [{ category: ISSUES_CATEGORY, avatar_url: null, project_id: 13, project_name: 'test13' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${{ project: { id: 3, name: 'test3' } }} | ${'13'} | ${'test13'}
${{ data: [{ category: MERGE_REQUEST_CATEGORY, avatar_url: null, project_id: 14, project_name: 'test14' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${{ project: { id: 4, name: 'test4' } }} | ${'14'} | ${'test14'}
${{ data: [{ category: RECENT_EPICS_CATEGORY, avatar_url: null, group_id: 15, group_name: 'test15' }] }} | ${true} | ${String(SMALL_AVATAR_PX)} | ${{ group: { id: 5, name: 'test5' } }} | ${'15'} | ${'test15'}
`('GlAvatar', ({ item, showAvatar, avatarSize, searchContext, entityId, entityName }) => {
describe(`when category is ${item.data[0].category} and avatar_url is ${item.data[0].avatar_url}`, () => { describe(`when category is ${item.data[0].category} and avatar_url is ${item.data[0].avatar_url}`, () => {
beforeEach(() => { beforeEach(() => {
createComponent({}, { autocompleteGroupedSearchOptions: () => [item] }); createComponent({ searchContext }, { autocompleteGroupedSearchOptions: () => [item] });
}); });
it(`should${showAvatar ? '' : ' not'} render`, () => { it(`should${showAvatar ? '' : ' not'} render`, () => {
...@@ -124,6 +154,16 @@ describe('HeaderSearchAutocompleteItems', () => { ...@@ -124,6 +154,16 @@ describe('HeaderSearchAutocompleteItems', () => {
it(`should set avatarSize to ${avatarSize}`, () => { it(`should set avatarSize to ${avatarSize}`, () => {
expect(findGlAvatar().exists() && findGlAvatar().attributes('size')).toBe(avatarSize); expect(findGlAvatar().exists() && findGlAvatar().attributes('size')).toBe(avatarSize);
}); });
it(`should set avatar entityId to ${entityId}`, () => {
expect(findGlAvatar().exists() && findGlAvatar().attributes('entityid')).toBe(entityId);
});
it(`should set avatar entityName to ${entityName}`, () => {
expect(findGlAvatar().exists() && findGlAvatar().attributes('entityname')).toBe(
entityName,
);
});
}); });
}); });
}); });
......
...@@ -96,19 +96,22 @@ export const MOCK_AUTOCOMPLETE_OPTIONS_RES = [ ...@@ -96,19 +96,22 @@ export const MOCK_AUTOCOMPLETE_OPTIONS_RES = [
{ {
category: 'Projects', category: 'Projects',
id: 1, id: 1,
label: 'MockProject1', label: 'Gitlab Org / MockProject1',
value: 'MockProject1',
url: 'project/1', url: 'project/1',
}, },
{ {
category: 'Groups', category: 'Groups',
id: 1, id: 1,
label: 'MockGroup1', label: 'Gitlab Org / MockGroup1',
value: 'MockGroup1',
url: 'group/1', url: 'group/1',
}, },
{ {
category: 'Projects', category: 'Projects',
id: 2, id: 2,
label: 'MockProject2', label: 'Gitlab Org / MockProject2',
value: 'MockProject2',
url: 'project/2', url: 'project/2',
}, },
{ {
...@@ -123,21 +126,24 @@ export const MOCK_AUTOCOMPLETE_OPTIONS = [ ...@@ -123,21 +126,24 @@ export const MOCK_AUTOCOMPLETE_OPTIONS = [
category: 'Projects', category: 'Projects',
html_id: 'autocomplete-Projects-0', html_id: 'autocomplete-Projects-0',
id: 1, id: 1,
label: 'MockProject1', label: 'Gitlab Org / MockProject1',
value: 'MockProject1',
url: 'project/1', url: 'project/1',
}, },
{ {
category: 'Groups', category: 'Groups',
html_id: 'autocomplete-Groups-1', html_id: 'autocomplete-Groups-1',
id: 1, id: 1,
label: 'MockGroup1', label: 'Gitlab Org / MockGroup1',
value: 'MockGroup1',
url: 'group/1', url: 'group/1',
}, },
{ {
category: 'Projects', category: 'Projects',
html_id: 'autocomplete-Projects-2', html_id: 'autocomplete-Projects-2',
id: 2, id: 2,
label: 'MockProject2', label: 'Gitlab Org / MockProject2',
value: 'MockProject2',
url: 'project/2', url: 'project/2',
}, },
{ {
...@@ -157,7 +163,8 @@ export const MOCK_GROUPED_AUTOCOMPLETE_OPTIONS = [ ...@@ -157,7 +163,8 @@ export const MOCK_GROUPED_AUTOCOMPLETE_OPTIONS = [
html_id: 'autocomplete-Projects-0', html_id: 'autocomplete-Projects-0',
id: 1, id: 1,
label: 'MockProject1', label: 'Gitlab Org / MockProject1',
value: 'MockProject1',
url: 'project/1', url: 'project/1',
}, },
{ {
...@@ -165,7 +172,8 @@ export const MOCK_GROUPED_AUTOCOMPLETE_OPTIONS = [ ...@@ -165,7 +172,8 @@ export const MOCK_GROUPED_AUTOCOMPLETE_OPTIONS = [
html_id: 'autocomplete-Projects-2', html_id: 'autocomplete-Projects-2',
id: 2, id: 2,
label: 'MockProject2', label: 'Gitlab Org / MockProject2',
value: 'MockProject2',
url: 'project/2', url: 'project/2',
}, },
], ],
...@@ -178,7 +186,8 @@ export const MOCK_GROUPED_AUTOCOMPLETE_OPTIONS = [ ...@@ -178,7 +186,8 @@ export const MOCK_GROUPED_AUTOCOMPLETE_OPTIONS = [
html_id: 'autocomplete-Groups-1', html_id: 'autocomplete-Groups-1',
id: 1, id: 1,
label: 'MockGroup1', label: 'Gitlab Org / MockGroup1',
value: 'MockGroup1',
url: 'group/1', url: 'group/1',
}, },
], ],
...@@ -202,21 +211,24 @@ export const MOCK_SORTED_AUTOCOMPLETE_OPTIONS = [ ...@@ -202,21 +211,24 @@ export const MOCK_SORTED_AUTOCOMPLETE_OPTIONS = [
category: 'Projects', category: 'Projects',
html_id: 'autocomplete-Projects-0', html_id: 'autocomplete-Projects-0',
id: 1, id: 1,
label: 'MockProject1', label: 'Gitlab Org / MockProject1',
value: 'MockProject1',
url: 'project/1', url: 'project/1',
}, },
{ {
category: 'Projects', category: 'Projects',
html_id: 'autocomplete-Projects-2', html_id: 'autocomplete-Projects-2',
id: 2, id: 2,
label: 'MockProject2', label: 'Gitlab Org / MockProject2',
value: 'MockProject2',
url: 'project/2', url: 'project/2',
}, },
{ {
category: 'Groups', category: 'Groups',
html_id: 'autocomplete-Groups-1', html_id: 'autocomplete-Groups-1',
id: 1, id: 1,
label: 'MockGroup1', label: 'Gitlab Org / MockGroup1',
value: 'MockGroup1',
url: 'group/1', url: 'group/1',
}, },
{ {
......
...@@ -71,7 +71,7 @@ RSpec.describe SearchHelper do ...@@ -71,7 +71,7 @@ RSpec.describe SearchHelper do
create(:group).add_owner(user) create(:group).add_owner(user)
result = search_autocomplete_opts("gro").first result = search_autocomplete_opts("gro").first
expect(result.keys).to match_array(%i[category id label url avatar_url]) expect(result.keys).to match_array(%i[category id value label url avatar_url])
end end
it 'includes the users recently viewed issues', :aggregate_failures do it 'includes the users recently viewed issues', :aggregate_failures do
......
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