diff --git a/app/assets/javascripts/content_editor/extensions/suggestions.js b/app/assets/javascripts/content_editor/extensions/suggestions.js index e0b6920cf4cedc76c831e98b4e546d0fb21ed4e9..36119209ef3192fc212542d03a1cf168a53e9406 100644 --- a/app/assets/javascripts/content_editor/extensions/suggestions.js +++ b/app/assets/javascripts/content_editor/extensions/suggestions.js @@ -54,6 +54,7 @@ function createSuggestionPlugin({ command: markdownLine.match(/\/\w+/)?.[0], cache, limit, + ...options, }) .search(query); }, @@ -158,13 +159,13 @@ export default Node.create({ }); return [ - createPlugin('@', 'reference', 'user', { limit: 10 }), + createPlugin('@', 'reference', 'user', { limit: 10, filterOnBackend: true }), createPlugin('#', 'reference', 'issue'), createPlugin('$', 'reference', 'snippet'), createPlugin('~', 'referenceLabel', 'label', { limit: 20 }), createPlugin('&', 'reference', 'epic'), createPlugin('!', 'reference', 'merge_request'), - createPlugin('[vulnerability:', 'reference', 'vulnerability'), + createPlugin('[vulnerability:', 'reference', 'vulnerability', { filterOnBackend: true }), createPlugin('%', 'reference', 'milestone'), createPlugin(':', 'emoji', 'emoji'), createPlugin('[[', 'link', 'wiki'), diff --git a/app/assets/javascripts/content_editor/services/autocomplete_helper.js b/app/assets/javascripts/content_editor/services/autocomplete_helper.js index 66ae4aa1125a994fdce3eb901f125ce02e437ba6..f6b1f7105554761d21cd88d7682c345b677a2937 100644 --- a/app/assets/javascripts/content_editor/services/autocomplete_helper.js +++ b/app/assets/javascripts/content_editor/services/autocomplete_helper.js @@ -80,27 +80,26 @@ export function createDataSource({ sorter = defaultSorter(searchFields), cache = true, limit = 15, + filterOnBackend = false, }) { - const fetchData = source ? async () => (await axios.get(source)).data : () => []; - let items = []; - - const sync = async function sync() { + const fetchData = async (query) => { try { - items = await fetchData(); + const queryOptions = filterOnBackend ? { params: { search: query } } : {}; + return source ? (await axios.get(source, queryOptions)).data : []; } catch { - items = []; + return []; } }; const cacheTimeoutFn = () => (cache ? 0 : Math.floor(Date.now() / 1e4)); - const init = memoize(sync, cacheTimeoutFn); + const memoizedFetchData = memoize(fetchData, cacheTimeoutFn); return { search: async (query) => { - await init(); + let results = filterOnBackend ? await fetchData(query) : await memoizedFetchData(); - let results = items.map(mapper); - if (filter) results = filter(items, query); + results = results.map(mapper); + if (filter) results = filter(results, query); if (query) { results = results.filter((item) => { @@ -209,8 +208,7 @@ export default class AutocompleteHelper { mapper: mappers[referenceType] || mappers.default, sorter: sorters[referenceType] || sorters.default, filter: filters[referenceType], - cache: config.cache, - limit: config.limit, + ...config, }); }; } diff --git a/spec/frontend/content_editor/services/autocomplete_helper_spec.js b/spec/frontend/content_editor/services/autocomplete_helper_spec.js index 0a4323024a1cb66a404e60ebc57fcd054c9f4946..98ade79eb8b3ef9d6b6aa23b1f96f8f707de88db 100644 --- a/spec/frontend/content_editor/services/autocomplete_helper_spec.js +++ b/spec/frontend/content_editor/services/autocomplete_helper_spec.js @@ -74,24 +74,45 @@ describe('createDataSource', () => { mock.restore(); }); - it('fetches data from source and filters based on query', async () => { - const data = [ - { name: 'abc', description: 'xyz' }, - { name: 'bcd', description: 'wxy' }, - { name: 'cde', description: 'vwx' }, - ]; - mock.onGet('/source').reply(HTTP_STATUS_OK, data); - - const dataSource = createDataSource({ + describe('on fetch success', () => { + const dataSourceParams = { source: '/source', searchFields: ['name', 'description'], + }; + + beforeEach(() => { + const data = [ + { name: 'abc', description: 'xyz' }, + { name: 'bcd', description: 'wxy' }, + { name: 'cde', description: 'vwx' }, + ]; + mock.onGet('/source').reply(HTTP_STATUS_OK, data); }); - const results = await dataSource.search('b'); - expect(results).toEqual([ - { name: 'bcd', description: 'wxy' }, - { name: 'abc', description: 'xyz' }, - ]); + it('fetches data from source and filters based on query', async () => { + const dataSource = createDataSource(dataSourceParams); + + const results = await dataSource.search('b'); + expect(results).toEqual([ + { name: 'bcd', description: 'wxy' }, + { name: 'abc', description: 'xyz' }, + ]); + }); + + describe('if filterOnBackend: true', () => { + it('fetches data from source, passing a `search` param', async () => { + const dataSource = createDataSource({ + ...dataSourceParams, + filterOnBackend: true, + }); + + const results = await dataSource.search('bcd'); + expect(mock.history.get[0].params).toEqual({ search: 'bcd' }); + + // results are still filtered out on frontend, on top of backend filtering + expect(results).toEqual([{ name: 'bcd', description: 'wxy' }]); + }); + }); }); it('handles source fetch errors', async () => {