diff --git a/__tests__/integration/filters/boolean_filter/set_filter.test.ts b/__tests__/integration/filters/boolean_filter/set_filter.test.ts deleted file mode 100644 index 7fe64d4..0000000 --- a/__tests__/integration/filters/boolean_filter/set_filter.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {setUp} from '../../utils'; -import {ESRequest} from 'types'; - -describe('Filters', () => { - describe('Boolean Filter', () => { - describe('setFilter', () => { - it.skip('calls client search with all filters', () => { - const {manager, client} = setUp(); - manager.filters.boolean.setFilter('test_filed', {state: true}); - - client.search({} as ESRequest); - // console.log((client.search as jest.Mock).mock.calls); - expect(client.search).toHaveBeenCalledWith(); - }); - }); - }); -}); diff --git a/__tests__/integration/filters/set_filter.test.ts b/__tests__/integration/filters/set_filter.test.ts new file mode 100644 index 0000000..2694050 --- /dev/null +++ b/__tests__/integration/filters/set_filter.test.ts @@ -0,0 +1,51 @@ +import {setUp} from '../utils'; +import waitForExpect from 'wait-for-expect'; + +describe('Filters', () => { + describe('setFilter', () => { + it('calls client search with filter that was set', async () => { + const {manager, client} = setUp(); + await manager.getFieldNamesAndTypes(); + + manager.filters.boolean.setFilter('boolean_field', {state: true}); + + expect(client.search).toHaveBeenCalledWith({ + _source: {}, + aggs: {}, + query: {bool: {should: [{term: {boolean_field: true}}]}}, + size: 10, + sort: ['_score', '_doc'], + track_scores: true + }); + }); + + it('adds existing filters to the search request when set', async () => { + const {manager, client} = setUp(); + + await manager.getFieldNamesAndTypes(); + + manager.filters.boolean.setFilter('boolean_field', {state: true}); + manager.filters.range.setFilter('double_field', {greaterThan: 0, lessThanEqual: 10}); + + await waitForExpect(() => { + expect(manager._sideEffectQueue.length).toEqual(0); + }); + + expect(client.search).toHaveBeenCalledWith({ + query: { + bool: { + should: [ + {term: {boolean_field: true}}, + {range: {double_field: {gt: 0, lte: 10}}} + ] + } + }, + aggs: {}, + _source: {}, + size: 0, + track_scores: false, + sort: ['_score', '_doc'] + }); + }); + }); +}); diff --git a/__tests__/integration/suggestions/set_suggestion.test.ts b/__tests__/integration/suggestions/set_suggestion.test.ts new file mode 100644 index 0000000..f65d5f6 --- /dev/null +++ b/__tests__/integration/suggestions/set_suggestion.test.ts @@ -0,0 +1,15 @@ +import {setUp} from '../utils'; + +describe('Suggestion', () => { + describe('Prefix', () => { + describe('setSuggestion', () => { + it.skip('calls client search with all search info', async () => { + const {manager, client} = setUp(); + await manager.getFieldNamesAndTypes(); + manager.suggestions.prefix.setSearch('text_field', 'test_prefix_search'); + + expect(client.search).toHaveBeenCalledWith(); + }); + }); + }); +}); diff --git a/__tests__/integration/utils.ts b/__tests__/integration/utils.ts index 7f59248..2adea32 100644 --- a/__tests__/integration/utils.ts +++ b/__tests__/integration/utils.ts @@ -1,13 +1,66 @@ -import {Manager, IClient, History, HistoryLocation, CurrentLocationStateObserver} from '../../src'; +import { + Manager, + IClient, + History, + HistoryLocation, + CurrentLocationStateObserver, + IFiltersOptions, + ESRequest, + ESResponse, + ESHit, + ESMappingType +} from '../../src'; -export const setUp = () => { +export const fakeResponse = (params?: Partial) => { + // tslint:disable-next-line + return { + took: (params && params.took) || 20, + timed_out: (params && params.timed_out) || false, + _shards: (params && params._shards) || {total: 5, successful: 4, skipped: 1, failed: 0}, + hits: (params && params.hits) || {total: 200, max_score: 1, hits: [fakeResponseHit()]}, + aggregations: (params && params.aggregations) || {} + } as ESResponse; +}; + +export const fakeResponseHit = (params?: Partial) => { + // tslint:disable-next-line + return { + _index: (params && params._index) || 'awesome_index', + _type: (params && params._type) || 'test type', + _id: (params && params._id) || '1234', + _score: (params && params._score) || 22, + _source: (params && params._source) || {}, + sort: (params && params.sort) || ['_score', '_doc'] + } as ESHit; +}; + +export const fakeMapping = () => { + return { + boolean_field: 'boolean', + double_field: 'double', + integer_field: 'integer', + keyword_field: 'keyword', + text_field: 'text', + float_field: 'float' + } as Record; +}; +export const setUp = (options?: { + response?: ESResponse; + mapping?: Record; + filters?: IFiltersOptions; +}) => { // mock ES client const client: IClient = { - search: jest.fn(), - mapping: jest.fn() + search: jest.fn((_request: ESRequest) => + Promise.resolve((options && options.response) || fakeResponse()) + ), + mapping: jest.fn(() => Promise.resolve((options && options.mapping) || fakeMapping())) }; // manager using mock client - const manager = new Manager(client); + const manager = new Manager(client, { + filters: (options && options.filters) || {}, + queryThrottleInMS: 0 + }); // simple history persister instead of localStorage const historyPersister = { diff --git a/package-lock.json b/package-lock.json index e914548..d7d1873 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@social-native/snpkg-client-elasticsearch", - "version": "3.4.0", + "version": "3.6.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -11624,6 +11624,12 @@ "xml-name-validator": "^3.0.0" } }, + "wait-for-expect": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/wait-for-expect/-/wait-for-expect-3.0.2.tgz", + "integrity": "sha512-cfS1+DZxuav1aBYbaO/kE06EOS8yRw7qOFoD3XtjTkYvCvh3zUvNST8DXK/nPaeqIzIv3P3kL3lRJn8iwOiSag==", + "dev": true + }, "walkdir": { "version": "0.0.12", "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.0.12.tgz", diff --git a/package.json b/package.json index c7b2151..53f19d4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@social-native/snpkg-client-elasticsearch", - "version": "3.6.1", + "version": "3.6.2", "description": "", "main": "dist/index.cjs.js", "module": "dist/index.es.js", @@ -18,7 +18,8 @@ "watch": "rollup -cw", "prettier": "./node_modules/.bin/prettier \"src/**/*\" --write", "lint": "tslint -t stylish --project \"tsconfig.json\"", - "test": "jest", + "test": "jest --runInBand", + "test:clear-cache": "jest --clearCache", "test:watch": "npm run test -- --watchAll --runInBand", "type-check:watch": "npm run type-check -- --watch", "type-check": "tsc --noEmit", @@ -93,6 +94,7 @@ "tslint-eslint-rules": "^5.4.0", "tslint-immutable": "^5.5.2", "typescript": "^3.8.3", - "victory": "^34.0.0" + "victory": "^34.0.0", + "wait-for-expect": "^3.0.2" } } diff --git a/src/filters/base.ts b/src/filters/base.ts index 9d821e1..cc23d57 100644 --- a/src/filters/base.ts +++ b/src/filters/base.ts @@ -301,6 +301,11 @@ class BaseFilter { set(this.fieldFilters, { [field]: filter diff --git a/src/filters/range_filter.ts b/src/filters/range_filter.ts index 8a53285..ae1a76e 100644 --- a/src/filters/range_filter.ts +++ b/src/filters/range_filter.ts @@ -86,7 +86,7 @@ const convertLesserRanges = (filter: RangeFieldFilter) => { if (isLessThanFilter(filter)) { return {lt: filter.lessThan}; } else if (isLessThanEqualFilter(filter)) { - return {gt: filter.lessThanEqual}; + return {lte: filter.lessThanEqual}; } else { return undefined; } @@ -319,6 +319,7 @@ class RangeFilterClass extends BaseFilter< if (!this.fieldFilters) { return request; } + // tslint:disable-next-line return objKeys(this.fieldConfigs).reduce((acc, rangeFieldName) => { if (!this.fieldFilters) { diff --git a/src/manager.ts b/src/manager.ts index 97f409e..4e7ee15 100644 --- a/src/manager.ts +++ b/src/manager.ts @@ -148,7 +148,9 @@ class Manager< this.pageSize = (options && options.pageSize) || DEFAULT_MANAGER_OPTIONS.pageSize; this.queryThrottleInMS = - (options && options.queryThrottleInMS) || DEFAULT_MANAGER_OPTIONS.queryThrottleInMS; + options && options.queryThrottleInMS !== undefined + ? options.queryThrottleInMS + : DEFAULT_MANAGER_OPTIONS.queryThrottleInMS; this._pageCursorInfo = {}; this.currentPage = 0; // set to 0 b/c there are no results on init this.indexFieldNamesAndTypes = {}; @@ -354,7 +356,7 @@ class Manager< }); const params = effectRequest.params || []; - if (effectRequest.throttle) { + if (effectRequest.throttle && effectRequest.throttle > 0) { await Timeout.set(effectRequest.throttle); } @@ -671,8 +673,14 @@ class Manager< runInAction(() => { if (response && response.hits && response.hits.hits) { this.results = response.hits.hits; + } else { + this.results = []; } }); + } else { + runInAction(() => { + this.results = []; + }); } runInAction(() => { this.rawESResponse = response; @@ -1036,7 +1044,9 @@ class Manager< this._extractSuggestionStateFromResponse(response); // Timeout used as the debounce time. - await Timeout.set(this.queryThrottleInMS); + if (this.queryThrottleInMS > 0) { + await Timeout.set(this.queryThrottleInMS); + } } catch (e) { throw e; } // No cursor change b/c only dealing with filters @@ -1060,7 +1070,9 @@ class Manager< this._extractSuggestionStateFromResponse(response); // Timeout used as the debounce time. - await Timeout.set(this.queryThrottleInMS); + if (this.queryThrottleInMS > 0) { + await Timeout.set(this.queryThrottleInMS); + } } catch (e) { throw e; } // No cursor change b/c only dealing with filters @@ -1078,7 +1090,9 @@ class Manager< this._extractUnfilteredAggsStateFromResponse(formattedResponse); // Timeout used as the debounce time. - await Timeout.set(this.queryThrottleInMS); + if (this.queryThrottleInMS > 0) { + await Timeout.set(this.queryThrottleInMS); + } } catch (e) { throw e; } finally { @@ -1094,6 +1108,7 @@ class Manager< effectRequest, BLANK_ES_REQUEST ); + const response = await this.client.search(removeEmptyArrays(request)); // Save the results @@ -1103,7 +1118,9 @@ class Manager< this._extractFilteredAggsStateFromResponse(response); // Timeout used as the debounce time. - await Timeout.set(this.queryThrottleInMS); + if (this.queryThrottleInMS > 0) { + await Timeout.set(this.queryThrottleInMS); + } } catch (e) { throw e; } finally { @@ -1157,7 +1174,9 @@ class Manager< this._extractFilteredAggsStateFromResponse(response); // Timeout used as the debounce time. - await Timeout.set(this.queryThrottleInMS); + if (this.queryThrottleInMS > 0) { + await Timeout.set(this.queryThrottleInMS); + } } catch (e) { throw e; } // No cursor change b/c only dealing with filters @@ -1181,7 +1200,9 @@ class Manager< this._extractUnfilteredAggsStateFromResponse(response); // Timeout used as the debounce time. - await Timeout.set(this.queryThrottleInMS); + if (this.queryThrottleInMS > 0) { + await Timeout.set(this.queryThrottleInMS); + } } catch (e) { throw e; } // No cursor change b/c only dealing with filters @@ -1196,7 +1217,9 @@ class Manager< this._extractUnfilteredAggsStateFromResponse(response); // Timeout used as the debounce time. - await Timeout.set(this.queryThrottleInMS); + if (this.queryThrottleInMS > 0) { + await Timeout.set(this.queryThrottleInMS); + } } catch (e) { throw e; } // no cursor change b/c that should already be handled by the initial request in the batch @@ -1211,7 +1234,9 @@ class Manager< this._extractFilteredAggsStateFromResponse(response); // Timeout used as the debounce time. - await Timeout.set(this.queryThrottleInMS); + if (this.queryThrottleInMS > 0) { + await Timeout.set(this.queryThrottleInMS); + } } catch (e) { throw e; } // no cursor change b/c that should already be handled by the initial request in the batch diff --git a/src/suggestions/base.ts b/src/suggestions/base.ts index 09b4d8c..b9c50d8 100644 --- a/src/suggestions/base.ts +++ b/src/suggestions/base.ts @@ -255,6 +255,12 @@ class BaseSuggestion } public setSearch = (field: Fields, searchTerm: string) => { + if (this.fieldConfigs[field] === undefined) { + throw new Error( + `${field} search field doesnt exist. Either add it explicitly (example for range filters: https://github.com/social-native/snpkg-client-elasticsearch#instantiate-a-manager-with-specific-config-options-for-a-range-filter) or run an introspection query (via manager.getFieldNamesAndTypes())` + ); + } + runInAction(() => { set(this.fieldSearches, { [field]: searchTerm