changed $expand filter matching from left-match to contains match (#2327)

* changed $expand filter matching from left-match to contains match

* changelog

* pure substring match is too generous (a regression test checked for that).  Tightened the match up to word boundaries.

* CLean up to avoid intermittent test failure

Co-authored-by: jamesagnew <jamesagnew@gmail.com>
This commit is contained in:
Ken Stevens 2021-01-29 12:37:54 -05:00 committed by GitHub
parent d11515599a
commit ba63f65115
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 78 additions and 11 deletions

View File

@ -0,0 +1,6 @@
---
type: fix
issue: 2327
title: "The $expand filter parameter was not matching the ValueSet display value in all cases. E.g. a ValueSet
with name 'abc def ghi' would match 'abc def' and 'def' but not 'def ghi'. This has been corrected so the ValueSet
will match the filter if any substring of the ValueSet display value matches the $expand filter."

View File

@ -186,6 +186,7 @@ import static org.apache.commons.lang3.StringUtils.isEmpty;
import static org.apache.commons.lang3.StringUtils.isNoneBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.lowerCase;
import static org.apache.commons.lang3.StringUtils.startsWithIgnoreCase;
public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
public static final int DEFAULT_FETCH_SIZE = 250;
@ -644,27 +645,44 @@ public abstract class BaseTermReadSvcImpl implements ITermReadSvc {
}
}
public boolean applyFilter(final String theInput, final String thePrefixToken) {
public boolean applyFilter(final String theDisplay, final String theFilterDisplay) {
//-- safety check only, no need to apply filter
if (theInput == null || thePrefixToken == null)
if (theDisplay == null || theFilterDisplay == null)
return true;
// -- sentence case
if (org.apache.commons.lang3.StringUtils.startsWithIgnoreCase(theInput, thePrefixToken))
if (startsWithIgnoreCase(theDisplay, theFilterDisplay))
return true;
//-- token case
// return true only e.g. the input is 'Body height', thePrefixToken is "he", or 'bo'
StringTokenizer tok = new StringTokenizer(theInput);
while (tok.hasMoreTokens()) {
if (org.apache.commons.lang3.StringUtils.startsWithIgnoreCase(tok.nextToken(), thePrefixToken))
return true;
}
if (startsWithByWordBoundaries(theDisplay, theFilterDisplay)) return true;
return false;
}
private boolean startsWithByWordBoundaries(String theDisplay, String theFilterDisplay) {
// return true only e.g. the input is 'Body height', theFilterDisplay is "he", or 'bo'
StringTokenizer tok = new StringTokenizer(theDisplay);
List<String> tokens = new ArrayList<>();
while (tok.hasMoreTokens()) {
String token = tok.nextToken();
if (startsWithIgnoreCase(token, theFilterDisplay))
return true;
tokens.add(token);
}
// Allow to search by the end of the phrase. E.g. "working proficiency" will match "Limited working proficiency"
for (int start = 0; start <= tokens.size() - 1; ++ start) {
for (int end = start + 1; end <= tokens.size(); ++end) {
String sublist = String.join(" ", tokens.subList(start, end));
if (startsWithIgnoreCase(sublist, theFilterDisplay))
return true;
}
}
return false;
}
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void expandValueSet(ValueSetExpansionOptions theExpansionOptions, ValueSet theValueSetToExpand, IValueSetConceptAccumulator theValueSetCodeAccumulator) {

View File

@ -0,0 +1,43 @@
package ca.uhn.fhir.jpa.term;
import org.junit.jupiter.api.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class BaseTermReadSvcImplTest {
private final TermReadSvcR5 mySvc = new TermReadSvcR5();
@Test
void applyFilterMatchWords() {
assertTrue(mySvc.applyFilter("abc def", "abc def"));
assertTrue(mySvc.applyFilter("abc def", "abc"));
assertTrue(mySvc.applyFilter("abc def", "def"));
assertTrue(mySvc.applyFilter("abc def ghi", "abc def ghi"));
assertTrue(mySvc.applyFilter("abc def ghi", "abc def"));
assertTrue(mySvc.applyFilter("abc def ghi", "def ghi"));
}
@Test
void applyFilterSentenceStart() {
assertTrue(mySvc.applyFilter("manifold", "man"));
assertTrue(mySvc.applyFilter("manifest destiny", "man"));
assertTrue(mySvc.applyFilter("deep sight", "deep sigh"));
assertTrue(mySvc.applyFilter("sink cottage", "sink cot"));
}
@Test
void applyFilterSentenceEnd() {
assertFalse(mySvc.applyFilter("rescue", "cue"));
assertFalse(mySvc.applyFilter("very picky", "icky"));
}
@Test
void applyFilterSubwords() {
assertFalse(mySvc.applyFilter("splurge", "urge"));
assertFalse(mySvc.applyFilter("sink cottage", "ink cot"));
assertFalse(mySvc.applyFilter("sink cottage", "ink cottage"));
assertFalse(mySvc.applyFilter("clever jump startle", "lever jump star"));
}
}