Skip to content

Commit

Permalink
feat: Implement the support for _filter (#244)
Browse files Browse the repository at this point in the history
* feat: initial commit for _filter support

* refactor: add edge cases and support for BT operator

string encoding to "" since fhir expects that in _filter

* test: add unit testcases , add tests and get the filter prefix method

* test: add more tests

* test: add more testcases

* docs: update docs

* test: add test cases

* docs: fix indentation

* refactor: fixes

* docs: update docs

* refactor: address review comments

* refactor: address review comments

* refactor: review comments

* refactor: review comments

* docs: review comments

* refactor: address review comments

* style: review comments

* style: eslint issues

* refactor: review comments
  • Loading branch information
AmirthavalliG authored Mar 31, 2021
1 parent 9cc9159 commit 3fc0634
Show file tree
Hide file tree
Showing 9 changed files with 513 additions and 84 deletions.
73 changes: 70 additions & 3 deletions docs/tutorials/3 Advanced Bindings.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,10 @@ A FHIR® operation can also return a collection of versions of a specific FHIR®
```
### Step 3.9 Filtering in FHIR® Resources
`sap.fhir.model.r4.FHIRFilter`, which extends `sap.ui.model.Filter`, and necessary helper classes support the special syntax that the FHIR® query language uses because of the different search parameter data types. The following example shows how these classes are used.
For more information refer the following [FHIR®-Specific Filters](https://www.hl7.org/fhir/search.html#prefix) and [FHIR®- SearchModifiers](https://www.hl7.org/fhir/search.html#modifiers).
`sap.fhir.model.r4.FHIRFilter`, which extends `sap.ui.model.Filter`, and necessary helper classes support the special syntax that the FHIR® query language uses because of the different search parameter data types.
#### 1. Simple Filtering
For more information regarding simple filtering and the operators to be used refer the following [FHIR®-Specific Filters](https://www.hl7.org/fhir/search.html#prefix) and [FHIR®- SearchModifiers](https://www.hl7.org/fhir/search.html#modifiers). The following example shows how these classes are used.
*Example: Filtering a Binding with FHIR®-Specific Filters and SearchModifier :missing*
```javascript
Expand Down Expand Up @@ -249,4 +251,69 @@ oBinding.filter(
})
]
);
```
```
#### 2. Complex Filtering
FHIR® specifies the use of `_filter` search expression parameter to support complex combination queries.
To ensure complex filtering support is enabled the model is initialised with `filtering: {complex: true}` property
```json
"": {
"type": "sap.fhir.model.r4.FHIRModel",
"dataSource": "fhir",
"settings":{
"filtering": {
"complex": true
}
}
}
```
For more information regarding `_filter` refer the following [FHIR® Filters](https://www.hl7.org/fhir/search_filter.html).
It's necessary to initialise the filter with valueType as "string" if the value is of type string. This is to ensure that the value is encoded with "".
*Example: Find all patients whose names are either "Habibi" or "Damon"
`( name eq "Habibi" or name eq "Damon" )`*
```javascript

sap.ui.define([ ...
"sap/ui/model/Filter",
"sap/fhir/model/r4/FHIRFilter",
"sap/fhir/model/r4/FHIRFilterOperator",
"sap/fhir/model/r4/FHIRFilterType",
...
]
...
oBinding.filter(
[
new FHIRFilter({
path : "name",
operator : FHIRFilterOperator.EQ,
valueType : FHIRFilterType.string,
value1: "Habibi"
}),
new Filter({
path : "name",
operator : FHIRFilterOperator.EQ,
valueType : FHIRFilterType.string,
value1: "Damon"
})
], false
);
```
*Example: Find all patients whose names starts with either "Da" and ends with "on" and whose birthdate is 25-12-1987 `( ( name sw "Da" and name ew "on" ) and birthdate eq 1987-12-25 )`*
```javascript

sap.ui.define([...
"sap/ui/model/Filter",
"sap/fhir/model/r4/FHIRFilter",
"sap/fhir/model/r4/FHIRFilterComplexOperator",
"sap/fhir/model/r4/FHIRFilterType",
...
]
...
var oNameFilter = new FHIRFilter({ path: "name", operator: FHIRFilterComplexOperator.StartsWith, value1: "Da", valueType: FHIRFilterType.string });
var oNameFilter1 = new FHIRFilter({ path: "name", operator: FHIRFilterComplexOperator.EndsWith, value1: "on", valueType: FHIRFilterType.string });
var oCombinedFilter = new sap.ui.model.Filter([oNameFilter, oNameFilter1], true);
var oBirthDateFilter = new FHIRFilter({ path: "birthdate", operator: FHIRFilterComplexOperator.EQ, value1: "1987-12-25" });
oBinding.filter([oCombinedFilter, oBirthDateFilter], true);

```
71 changes: 71 additions & 0 deletions src/sap/fhir/model/r4/FHIRFilterComplexOperator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*!
* ${copyright}
*/

// Provides class sap.fhir.model.r4.FHIRFilterComplexOperator
sap.ui.define(["sap/fhir/model/r4/FHIRFilterOperator"], function (FHIRFilterOperator) {

"use strict";

/**
* Operators for the FHIR Complex Filter. Documentation https://www.hl7.org/fhir/search_filter.html#ops
*
* @enum {string}
* @public
* @alias sap.fhir.model.r4.FHIRFilterComplexOperator
* @extends sap.ui.model.FHIRFilterOperator
*/
var FHIRFilterComplexOperator = {
/**
* The set is empty or not (value is false or true)
*
* @public
*/
PR: "pr",

/**
* If a (implied) date period in the set overlaps with the implied period in the value
*
* @public
*/
PO: "po",

/**
* If the value subsumes a concept in the set
*
* @public
*/
SS: "ss",

/**
* If the value is subsumed by a concept in the set
*
* @public
*/
SB: "sb",

/**
* If one of the concepts is in the nominated value set by URI, either a relative, literal or logical vs
*
* @public
*/
IN: "in",

/**
* If none of the concepts is in the nominated value set by URI, either a relative, literal or logical vs
*
* @public
*/
NI: "ni",

/**
* If one of the references in set points to the given URL
*
* @public
*/
RE: "re"
};

// merge the FHIR FilterOperator object into the FHIRFilterComplexOperator
return Object.assign(FHIRFilterComplexOperator, FHIRFilterOperator);
});
4 changes: 2 additions & 2 deletions src/sap/fhir/model/r4/FHIRFilterOperator.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/

// Provides class sap.fhir.model.r4.FHIRFilterOperator
sap.ui.define(["sap/ui/model/FilterOperator"], function(FilterOperator) {
sap.ui.define(["sap/ui/model/FilterOperator"], function (FilterOperator) {

"use strict";

Expand All @@ -21,7 +21,7 @@ sap.ui.define(["sap/ui/model/FilterOperator"], function(FilterOperator) {
*
* @public
*/
Missing : "Missing",
Missing: "Missing",
/**
* starts-after
* e.g.: sa2013-03-14
Expand Down
116 changes: 108 additions & 8 deletions src/sap/fhir/model/r4/FHIRFilterOperatorUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
*/

// Provides class sap.fhir.model.r4.FHIRFilterOperatorUtils
sap.ui.define(["sap/fhir/model/r4/FHIRFilterOperator", "sap/fhir/model/r4/FHIRFilterType"], function(FHIRFilterOperator, FHIRFilterType) {
sap.ui.define([
"sap/fhir/model/r4/FHIRFilterOperator",
"sap/fhir/model/r4/FHIRFilterType",
"sap/fhir/model/r4/FHIRFilterComplexOperator"
], function (FHIRFilterOperator, FHIRFilterType, FHIRFilterComplexOperator) {

"use strict";

Expand All @@ -28,7 +32,7 @@ sap.ui.define(["sap/fhir/model/r4/FHIRFilterOperator", "sap/fhir/model/r4/FHIRFi
* @protected
* @since 1.0.0
*/
FHIRFilterOperatorUtils.getFHIRSearchParameterModifier = function(oFilter) {
FHIRFilterOperatorUtils.getFHIRSearchParameterModifier = function (oFilter) {
var sFHIRSearchModifier = "";
if (this.isSearchParameterModifiable(oFilter) || FHIRFilterOperator.Missing === oFilter.sOperator) {
switch (oFilter.sOperator) {
Expand Down Expand Up @@ -59,7 +63,7 @@ sap.ui.define(["sap/fhir/model/r4/FHIRFilterOperator", "sap/fhir/model/r4/FHIRFi
* @public
* @since 1.0.0
*/
FHIRFilterOperatorUtils.isSearchParameterModifiable = function(oFilter) {
FHIRFilterOperatorUtils.isSearchParameterModifiable = function (oFilter) {
return oFilter.sValueType !== FHIRFilterType.date && oFilter.sValueType !== FHIRFilterType.number && (typeof oFilter.oValue1 === "string" || Array.isArray(oFilter.oValue1));
};

Expand All @@ -71,19 +75,19 @@ sap.ui.define(["sap/fhir/model/r4/FHIRFilterOperator", "sap/fhir/model/r4/FHIRFi
* @public
* @since 1.0.0
*/
FHIRFilterOperatorUtils.isSearchParameterPrefixable = function(oFilter) {
FHIRFilterOperatorUtils.isSearchParameterPrefixable = function (oFilter) {
return !(typeof oFilter.oValue1 === "string" || Array.isArray(oFilter.oValue1)) || oFilter.sValueType === FHIRFilterType.date || !isNaN(oFilter.oValue1);
};

/**
* Parses the JS filter value to an FHIR filter value
* Parses the JS filter value to a FHIR filter value
*
* @param {any} oValue The value of a filter object
* @returns {string} Formatted FHIR filter value
* @public
* @since 1.0.0
*/
FHIRFilterOperatorUtils.getFilterValue = function(oValue) {
FHIRFilterOperatorUtils.getFilterValue = function (oValue) {
var sValue = oValue;
if (oValue instanceof Date) {
sValue = oValue.toISOString();
Expand All @@ -92,14 +96,14 @@ sap.ui.define(["sap/fhir/model/r4/FHIRFilterOperator", "sap/fhir/model/r4/FHIRFi
};

/**
* Transforms the UI5 filter operator to an FHIR valid search prefix based on the given UI5 <code>oFilter</code>
* Transforms the UI5 filter operator to a FHIR valid search prefix based on the given UI5 <code>oFilter</code>
*
* @param {sap.ui.model.Filter} oFilter The given filter
* @returns {string} The date FHIR search prefix
* @protected
* @since 1.0.0
*/
FHIRFilterOperatorUtils.getFHIRSearchPrefix = function(oFilter) {
FHIRFilterOperatorUtils.getFHIRSearchPrefix = function (oFilter) {
var sFHIRSearchPrefix;
if (this.isSearchParameterPrefixable(oFilter)) {
switch (oFilter.sOperator) {
Expand Down Expand Up @@ -137,5 +141,101 @@ sap.ui.define(["sap/fhir/model/r4/FHIRFilterOperator", "sap/fhir/model/r4/FHIRFi
return sFHIRSearchPrefix;
};

/**
* Transforms the UI5 filter operator to a FHIR valid filter prefix based on the given UI5 <code>oFilter</code>
*
* @param {sap.ui.model.Filter} oFilter The given filter
* @returns {string} The FHIR filter prefix
* @protected
* @since 2.1.0
*/
FHIRFilterOperatorUtils.getFHIRFilterPrefix = function (oFilter) {
var sFHIRFilterPrefix;
switch (oFilter.sOperator) {
case FHIRFilterComplexOperator.EQ:
sFHIRFilterPrefix = "eq";
break;
case FHIRFilterComplexOperator.NE:
sFHIRFilterPrefix = "ne";
break;
case FHIRFilterComplexOperator.GT:
sFHIRFilterPrefix = "gt";
break;
case FHIRFilterComplexOperator.GE:
sFHIRFilterPrefix = "ge";
break;
case FHIRFilterComplexOperator.LT:
sFHIRFilterPrefix = "lt";
break;
case FHIRFilterComplexOperator.LE:
sFHIRFilterPrefix = "le";
break;
case FHIRFilterComplexOperator.SA:
sFHIRFilterPrefix = "sa";
break;
case FHIRFilterComplexOperator.EB:
sFHIRFilterPrefix = "eb";
break;
case FHIRFilterComplexOperator.AP:
sFHIRFilterPrefix = "ap";
break;
case FHIRFilterComplexOperator.StartsWith:
sFHIRFilterPrefix = "sw";
break;
case FHIRFilterComplexOperator.EndsWith:
sFHIRFilterPrefix = "ew";
break;
case FHIRFilterComplexOperator.Contains:
sFHIRFilterPrefix = "co";
break;
case FHIRFilterComplexOperator.PR:
sFHIRFilterPrefix = "pr";
break;
case FHIRFilterComplexOperator.PO:
sFHIRFilterPrefix = "po";
break;
case FHIRFilterComplexOperator.SS:
sFHIRFilterPrefix = "ss";
break;
case FHIRFilterComplexOperator.SB:
sFHIRFilterPrefix = "sb";
break;
case FHIRFilterComplexOperator.IN:
sFHIRFilterPrefix = "in";
break;
case FHIRFilterComplexOperator.NI:
sFHIRFilterPrefix = "ni";
break;
case FHIRFilterComplexOperator.RE:
sFHIRFilterPrefix = "re";
break;
default:
break;
}
return sFHIRFilterPrefix;
};

/**
* Parses the JS filter value to a FHIR filter value
*
* @param {string} sFilterValue The value type of a filter object
* @param {any} vValue The value of a filter object
* @returns {string} Formatted FHIR filter value
* @public
* @since 2.1.0
*/
FHIRFilterOperatorUtils.getFilterValueForComplexFilter = function (sFilterValue, vValue) {
var isStringFilterType = sFilterValue && sFilterValue === FHIRFilterType.string ? true : false;
var sValue;
if (isStringFilterType) {
// special handling for string parameter as per fhir
// given eq "peter"
sValue = "\"" + vValue + "\"";
} else {
sValue = vValue;
}
return sValue;
};

return FHIRFilterOperatorUtils;
});
6 changes: 4 additions & 2 deletions src/sap/fhir/model/r4/FHIRModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ sap.ui.define([
* @param {object} [mParameters.defaultQueryParameters={}] The default query parameters to be passed on resource type specific requests and not resource instance specific requests (e.g /Patient?_total:accurate&_format:json). It should be of type key:value pairs. e.g. {'_total':'accurate'} -> http://hl7.org/fhir/http.html#parameters
* @param {string} [mParameters.Prefer='return=minimal'] The FHIR server won't return the changed resource by an POST/PUT request -> https://www.hl7.org/fhir/http.html#2.21.0.5.2
* @param {boolean} [mParameters.x-csrf-token=false] The model handles the csrf token between the browser and the FHIR server
* @param {object} [mParameters.filtering={}] The filtering options
* @param {boolean} [mParameters.filtering.complex=false}] The default filtering type. If <code>true</code>, all search parameters would be modelled via {@link https://www.hl7.org/fhir/search_filter.html _filter}
* @throws {Error} If no service URL is given, if the given service URL does not end with a forward slash
* @author SAP SE
* @public
Expand Down Expand Up @@ -89,8 +91,8 @@ sap.ui.define([
this.sDefaultFullUrlType = (mParameters && mParameters.defaultSubmitMode && mParameters.defaultSubmitMode !== SubmitMode.Direct && mParameters.defaultFullUrlType) ? mParameters.defaultFullUrlType : "uuid";
this.oDefaultUri = this.sDefaultFullUrlType === "url" ? new Url() : new Uuid();
this.iSizeLimit = 10;
if (mParameters && mParameters.simpleFiltering === false){
throw new Error("Complex filtering not supported");
if (mParameters && mParameters.filtering && mParameters.filtering.complex === true){
this.iSupportedFilterDepth = undefined;
} else {
this.iSupportedFilterDepth = 2;
}
Expand Down
Loading

0 comments on commit 3fc0634

Please sign in to comment.