Commit d8521127 authored by Nick Thomas's avatar Nick Thomas

Merge branch 'ce-6878-add-epic-select-dropdown' into 'master'

Add `searchBy` helper & `SidebarItemEpicsSelect` placeholder component

See merge request gitlab-org/gitlab-ce!31859
parents 03bf3271 6044b3ed
...@@ -22,6 +22,8 @@ export default Vue.extend({ ...@@ -22,6 +22,8 @@ export default Vue.extend({
components: { components: {
AssigneeTitle, AssigneeTitle,
Assignees, Assignees,
SidebarEpicsSelect: () =>
import('ee_component/sidebar/components/sidebar_item_epics_select.vue'),
RemoveBtn, RemoveBtn,
Subscriptions, Subscriptions,
TimeTracker, TimeTracker,
......
...@@ -731,6 +731,66 @@ export const NavigationType = { ...@@ -731,6 +731,66 @@ export const NavigationType = {
TYPE_RESERVED: 255, TYPE_RESERVED: 255,
}; };
/**
* Method to perform case-insensitive search for a string
* within multiple properties and return object containing
* properties in case there are multiple matches or `null`
* if there's no match.
*
* Eg; Suppose we want to allow user to search using for a string
* within `iid`, `title`, `url` or `reference` props of a target object;
*
* const objectToSearch = {
* "iid": 1,
* "title": "Error omnis quos consequatur ullam a vitae sed omnis libero cupiditate. &3",
* "url": "/groups/gitlab-org/-/epics/1",
* "reference": "&1",
* };
*
* Following is how we call searchBy and the return values it will yield;
*
* - `searchBy('omnis', objectToSearch);`: This will return `{ title: ... }` as our
* query was found within title prop we only return that.
* - `searchBy('1', objectToSearch);`: This will return `{ "iid": ..., "reference": ..., "url": ... }`.
* - `searchBy('https://gitlab.com/groups/gitlab-org/-/epics/1', objectToSearch);`:
* This will return `{ "url": ... }`.
* - `searchBy('foo', objectToSearch);`: This will return `null` as no property value
* matched with our query.
*
* You can learn more about behaviour of this method by referring to tests
* within `spec/javascripts/lib/utils/common_utils_spec.js`.
*
* @param {string} query String to search for
* @param {object} searchSpace Object containing properties to search in for `query`
*/
export const searchBy = (query = '', searchSpace = {}) => {
const targetKeys = searchSpace !== null ? Object.keys(searchSpace) : [];
if (!query || !targetKeys.length) {
return null;
}
const normalizedQuery = query.toLowerCase();
const matches = targetKeys
.filter(item => {
const searchItem = `${searchSpace[item]}`.toLowerCase();
return (
searchItem.indexOf(normalizedQuery) > -1 ||
normalizedQuery.indexOf(searchItem) > -1 ||
normalizedQuery === searchItem
);
})
.reduce((acc, prop) => {
const match = acc;
match[prop] = searchSpace[prop];
return acc;
}, {});
return Object.keys(matches).length ? matches : null;
};
/** /**
* Checks if the given Label has a special syntax `::` in * Checks if the given Label has a special syntax `::` in
* it's title. * it's title.
......
...@@ -65,6 +65,8 @@ Example response: ...@@ -65,6 +65,8 @@ Example response:
"title": "Accusamus iste et ullam ratione voluptatem omnis debitis dolor est.", "title": "Accusamus iste et ullam ratione voluptatem omnis debitis dolor est.",
"description": "Molestias dolorem eos vitae expedita impedit necessitatibus quo voluptatum.", "description": "Molestias dolorem eos vitae expedita impedit necessitatibus quo voluptatum.",
"state": "opened", "state": "opened",
"web_edit_url": "http://localhost:3001/groups/test/-/epics/4",
"reference": "&4",
"author": { "author": {
"id": 10, "id": 10,
"name": "Lu Mayer", "name": "Lu Mayer",
...@@ -118,6 +120,8 @@ Example response: ...@@ -118,6 +120,8 @@ Example response:
"title": "Ea cupiditate dolores ut vero consequatur quasi veniam voluptatem et non.", "title": "Ea cupiditate dolores ut vero consequatur quasi veniam voluptatem et non.",
"description": "Molestias dolorem eos vitae expedita impedit necessitatibus quo voluptatum.", "description": "Molestias dolorem eos vitae expedita impedit necessitatibus quo voluptatum.",
"state": "opened", "state": "opened",
"web_edit_url": "http://localhost:3001/groups/test/-/epics/5",
"reference": "&5",
"author":{ "author":{
"id": 7, "id": 7,
"name": "Pamella Huel", "name": "Pamella Huel",
...@@ -182,6 +186,8 @@ Example response: ...@@ -182,6 +186,8 @@ Example response:
"title": "Epic", "title": "Epic",
"description": "Epic description", "description": "Epic description",
"state": "opened", "state": "opened",
"web_edit_url": "http://localhost:3001/groups/test/-/epics/6",
"reference": "&6",
"author": { "author": {
"name" : "Alexandra Bashirian", "name" : "Alexandra Bashirian",
"avatar_url" : null, "avatar_url" : null,
...@@ -247,6 +253,8 @@ Example response: ...@@ -247,6 +253,8 @@ Example response:
"title": "New Title", "title": "New Title",
"description": "Epic description", "description": "Epic description",
"state": "opened", "state": "opened",
"web_edit_url": "http://localhost:3001/groups/test/-/epics/6",
"reference": "&6",
"author": { "author": {
"name" : "Alexandra Bashirian", "name" : "Alexandra Bashirian",
"avatar_url" : null, "avatar_url" : null,
......
...@@ -895,6 +895,45 @@ describe('common_utils', () => { ...@@ -895,6 +895,45 @@ describe('common_utils', () => {
}); });
}); });
describe('searchBy', () => {
const searchSpace = {
iid: 1,
reference: '&1',
title: 'Error omnis quos consequatur ullam a vitae sed omnis libero cupiditate.',
url: '/groups/gitlab-org/-/epics/1',
};
it('returns null when `query` or `searchSpace` params are empty/undefined', () => {
expect(commonUtils.searchBy('omnis', null)).toBeNull();
expect(commonUtils.searchBy('', searchSpace)).toBeNull();
expect(commonUtils.searchBy()).toBeNull();
});
it('returns object with matching props based on `query` & `searchSpace` params', () => {
// String `omnis` is found only in `title` prop so return just that
expect(commonUtils.searchBy('omnis', searchSpace)).toEqual(
jasmine.objectContaining({
title: searchSpace.title,
}),
);
// String `1` is found in both `iid` and `reference` props so return both
expect(commonUtils.searchBy('1', searchSpace)).toEqual(
jasmine.objectContaining({
iid: searchSpace.iid,
reference: searchSpace.reference,
}),
);
// String `/epics/1` is found in `url` prop so return just that
expect(commonUtils.searchBy('/epics/1', searchSpace)).toEqual(
jasmine.objectContaining({
url: searchSpace.url,
}),
);
});
});
describe('isScopedLabel', () => { describe('isScopedLabel', () => {
it('returns true when `::` is present in title', () => { it('returns true when `::` is present in title', () => {
expect(commonUtils.isScopedLabel({ title: 'foo::bar' })).toBe(true); expect(commonUtils.isScopedLabel({ title: 'foo::bar' })).toBe(true);
......
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