diff --git a/src/WfsFilterUtil/WfsFilterUtil.spec.ts b/src/WfsFilterUtil/WfsFilterUtil.spec.ts index aa9b0a2fa..aecc3b57c 100644 --- a/src/WfsFilterUtil/WfsFilterUtil.spec.ts +++ b/src/WfsFilterUtil/WfsFilterUtil.spec.ts @@ -3,20 +3,42 @@ import EqualTo from 'ol/format/filter/EqualTo'; import IsLike from 'ol/format/filter/IsLike'; import Or from 'ol/format/filter/Or'; -import WfsFilterUtil, { AttributeDetails, SearchConfig } from './WfsFilterUtil'; +import WfsFilterUtil, { AttributeSearchSettings, SearchConfig } from './WfsFilterUtil'; describe('WfsFilterUtil', () => { + const featureType = 'featureType'; + const attrName = 'testAttribute'; + const anotherAttrName = 'anotherTestAttribute'; + + const stringExactFalse: AttributeSearchSettings = { + matchCase: false, + type: 'string', + exactSearch: false + }; + const stringExactTrue: AttributeSearchSettings = { + matchCase: false, + type: 'string', + exactSearch: true + }; + const intExactTrue: AttributeSearchSettings = { + matchCase: false, + type: 'int', + exactSearch: true + }; + const intExactFalse: AttributeSearchSettings = { + matchCase: false, + type: 'int', + exactSearch: false + }; + const searchConfig: SearchConfig = { featureNS: 'test', - featureTypes: ['featureType'], + featureTypes: [featureType], featurePrefix: 'test', - attributeDetails: [{ - matchCase: false, - type: 'string', - exactSearch: false, - attributeName: 'test' - }] + attributeDetails: { + featureType: {} + } }; const stringSearchTerm = 'searchMe'; @@ -35,49 +57,60 @@ describe('WfsFilterUtil', () => { expect(WfsFilterUtil.createWfsFilter).toBeDefined(); }); - it ('returns null if no search attributes for the provided feature type are found', () => { - const got = WfsFilterUtil.createWfsFilter(stringSearchTerm, []); + it('returns null if no search attributes for the provided feature type are found', () => { + const got = WfsFilterUtil.createWfsFilter(featureType, stringSearchTerm, {}); expect(got).toBeNull(); }); - it ('returns simple LIKE filter if only one attribute is ' + - 'provided and exactSearch flag is false or not given', () => { - const got = WfsFilterUtil.createWfsFilter(stringSearchTerm, searchConfig?.attributeDetails); + it('returns simple LIKE filter if only one attribute is provided and ' + + 'exactSearch flag is false or not given', () => { + searchConfig.attributeDetails.featureType[attrName] = stringExactFalse; + const got = WfsFilterUtil.createWfsFilter(featureType, stringSearchTerm, searchConfig.attributeDetails); expect(got?.getTagName()).toBe('PropertyIsLike'); expect(got).toBeInstanceOf(IsLike); const isLikeFilter = got as IsLike; expect(isLikeFilter?.pattern).toEqual(`*${stringSearchTerm}*`); - expect(isLikeFilter?.propertyName).toEqual(searchConfig?.attributeDetails[0].attributeName); - expect(isLikeFilter?.matchCase).toEqual(searchConfig?.attributeDetails[0].matchCase); + expect(isLikeFilter?.propertyName).toEqual(attrName); + expect(isLikeFilter?.matchCase).toEqual(stringExactFalse.matchCase); }); - it ('returns simple EQUALTO filter if only one attribute is provided and exactSearch flag is true', () => { - const test: AttributeDetails = { - type: 'int', - attributeName: 'test', - exactSearch: true - }; - const got = WfsFilterUtil.createWfsFilter(digitSearchTerm, [test]); + it('returns simple EQUALTO filter if only one attribute is provided and exactSearch flag is true', () => { + searchConfig.attributeDetails.featureType[attrName] = stringExactTrue; + const got = WfsFilterUtil.createWfsFilter(featureType, stringSearchTerm, searchConfig.attributeDetails); + expect(got?.getTagName()).toBe('PropertyIsEqualTo'); + expect(got).toBeInstanceOf(EqualTo); + const equalToFilter = got as EqualTo; + expect(equalToFilter?.expression).toEqual(stringSearchTerm); + expect(equalToFilter?.propertyName).toEqual(attrName); + }); + + it('returns simple EQUALTO filter for numeric attributes if exactSearch flag is true', () => { + searchConfig.attributeDetails.featureType[attrName] = intExactTrue; + let got = WfsFilterUtil.createWfsFilter(featureType, digitSearchTerm, searchConfig.attributeDetails); expect(got?.getTagName()).toBe('PropertyIsEqualTo'); expect(got).toBeInstanceOf(EqualTo); const equalToFilter = got as EqualTo; expect(equalToFilter?.expression).toEqual(digitSearchTerm); - expect(equalToFilter?.propertyName).toEqual(test.attributeName); + expect(equalToFilter?.propertyName).toEqual(attrName); }); - it ('returns combined OR filter if more than one search attributes are provided', () => { - const test1: AttributeDetails = { - attributeName: 'test1', - type: 'string' - }; + it('returns simple LIKE filter for numeric attributes if exactSearch flag is false', () => { + searchConfig.attributeDetails.featureType[attrName] = intExactFalse; + let got = WfsFilterUtil.createWfsFilter(featureType, digitSearchTerm, searchConfig.attributeDetails); + expect(got?.getTagName()).toBe('PropertyIsLike'); + expect(got).toBeInstanceOf(IsLike); + const isLikeFilter = got as IsLike; + expect(isLikeFilter?.pattern).toEqual(`*${digitSearchTerm}*`); + expect(isLikeFilter?.propertyName).toEqual(attrName); + expect(isLikeFilter?.matchCase).toEqual(stringExactFalse.matchCase); + }); - const test2: AttributeDetails = { - attributeName: 'test2', - type: 'string' - }; + it('returns combined OR filter if more than one search attributes are provided', () => { + searchConfig.attributeDetails.featureType[attrName] = stringExactTrue; + searchConfig.attributeDetails.featureType[anotherAttrName] = stringExactFalse; - const got = WfsFilterUtil.createWfsFilter(digitSearchTerm, [test1, test2]); + const got = WfsFilterUtil.createWfsFilter(featureType, stringSearchTerm, searchConfig.attributeDetails); expect(got?.getTagName()).toBe('Or'); expect(got).toBeInstanceOf(Or); const orFilter = got as Or; @@ -90,21 +123,26 @@ describe('WfsFilterUtil', () => { expect(WfsFilterUtil.getCombinedRequests).toBeDefined(); }); - it('creates WFS filter for each feature type', () => { + it('tries to create WFS filter for each feature type', () => { const filterSpy = jest.spyOn(WfsFilterUtil, 'createWfsFilter'); const searchTerm: string = 'peter'; WfsFilterUtil.getCombinedRequests(searchConfig, searchTerm); - expect(filterSpy).toHaveBeenCalledTimes(searchConfig.attributeDetails.length); + expect(filterSpy).toHaveBeenCalledTimes(searchConfig.featureTypes!.length); filterSpy.mockRestore(); }); it('creates WFS GetFeature request body containing queries and filter for each feature type', () => { const filterSpy = jest.spyOn(WfsFilterUtil, 'createWfsFilter'); const searchTerm: string = 'peter'; + searchConfig.attributeDetails.featureType[attrName] = stringExactFalse; const got = WfsFilterUtil.getCombinedRequests(searchConfig, searchTerm) as Element; expect(got?.tagName).toBe('GetFeature'); - expect(filterSpy).toHaveBeenCalledTimes(searchConfig.attributeDetails.length); - expect(got?.getRootNode()?.firstChild?.textContent).toContain(`*${searchTerm}*`); + expect(got.querySelectorAll('Query').length).toBe(searchConfig.featureTypes!.length); + expect(filterSpy).toHaveBeenCalledTimes(searchConfig.featureTypes!.length); + got.querySelectorAll('Query').forEach(query => { + expect(query.children[2].tagName).toBe('Filter'); + expect(query.children[2].getElementsByTagName('Literal')[0].innerHTML).toBe(`*${searchTerm}*`); + }); }); }); }); diff --git a/src/WfsFilterUtil/WfsFilterUtil.ts b/src/WfsFilterUtil/WfsFilterUtil.ts index 5bf782159..4005fc828 100644 --- a/src/WfsFilterUtil/WfsFilterUtil.ts +++ b/src/WfsFilterUtil/WfsFilterUtil.ts @@ -3,13 +3,40 @@ import { equalTo, like, or } from 'ol/format/filter'; import OlFormatFilter from 'ol/format/filter/Filter'; import OlFormatWFS, { WriteGetFeatureOptions } from 'ol/format/WFS'; -export type AttributeDetails = { - attributeName: string; +export type AttributeSearchSettings = { type: 'number' | 'int' | 'string'; exactSearch?: boolean; matchCase?: boolean; }; +/** + * A nested object mapping feature types to an object of their attribute details. + * + * Example: + * ``` + * attributeDetails: { + * featType1: { + * attr1: { + * matchCase: true, + * type: 'number', + * exactSearch: false + * }, + * attr2: { + * matchCase: false, + * type: 'string', + * exactSearch: true + * } + * }, + * featType2: {...} + * } + * ``` + */ +export type AttributeDetails = { + [featureType: string]: { + [attributeName: string]: AttributeSearchSettings; + }; +}; + export type SearchConfig = { featureNS: string; featureTypes?: string[]; @@ -19,7 +46,7 @@ export type SearchConfig = { outputFormat?: string; srsName?: string; wfsFormatOptions?: string; - attributeDetails: AttributeDetails[]; + attributeDetails: AttributeDetails; }; /** @@ -43,25 +70,37 @@ class WfsFilterUtil { * @private */ static createWfsFilter( + featureType: string, searchTerm: string, - attributeDetails: AttributeDetails[] + attributeDetails: AttributeDetails ): OlFormatFilter | null { - if (attributeDetails.length === 0) { + + const details = attributeDetails[featureType]; + + if (!details) { return null; } - const propertyFilters = attributeDetails + const attributes = Object.keys(details); + + if (attributes.length === 0) { + return null; + } + + const propertyFilters = attributes .filter(attribute => { - const type = attribute.type; + const filterDetails = details[attribute]; + const type = filterDetails.type; return !(type && (type === 'int' || type === 'number') && searchTerm.match(/[^.\d]/)); }) - .map(attributeDetail => { - if (attributeDetail.exactSearch) { - return equalTo(attributeDetail.attributeName, searchTerm, attributeDetail.exactSearch); + .map(attribute => { + const filterDetails = details[attribute]; + if (filterDetails.exactSearch) { + return equalTo(attribute, searchTerm, filterDetails.exactSearch); } else { - return like(attributeDetail.attributeName, + return like(attribute, `*${searchTerm}*`, '*', '.', '!', - attributeDetail.matchCase ?? false); + filterDetails.matchCase ?? false); } }); if (Object.keys(propertyFilters).length > 1) { @@ -91,8 +130,8 @@ class WfsFilterUtil { } = searchConfig; const requests = featureTypes?.map((featureType: string): any => { - const filter = WfsFilterUtil.createWfsFilter(searchTerm, attributeDetails); - const propertyNames = attributeDetails.map(a => a.attributeName); + const filter = WfsFilterUtil.createWfsFilter(featureType, searchTerm, attributeDetails); + const propertyNames = Object.keys(attributeDetails[featureType]); const wfsFormatOpts: WriteGetFeatureOptions = { featureNS, featurePrefix, @@ -100,14 +139,17 @@ class WfsFilterUtil { geometryName, maxFeatures, outputFormat, - srsName, - propertyNames + srsName }; + + if (!_isNil(propertyNames)) { + wfsFormatOpts.propertyNames = propertyNames; + } if (!_isNil(filter)) { wfsFormatOpts.filter = filter; } - const wfsFormat: OlFormatWFS = new OlFormatWFS(wfsFormatOpts); + const wfsFormat: OlFormatWFS = new OlFormatWFS(wfsFormatOpts); return wfsFormat.writeGetFeature(wfsFormatOpts); });