Update elasticsearch translator to handle phrase searches. (#27720)

* Update elasticsearch translator to handle phrase searches.

Signed-off-by: Sydney Achinger <sydneynicoleachinger@spotify.com>

* Add helpful regex comment

Co-authored-by: John Philip <johnphilip283@gmail.com>
Signed-off-by: Sydney Achinger <78113809+squid-ney@users.noreply.github.com>

---------

Signed-off-by: Sydney Achinger <sydneynicoleachinger@spotify.com>
Signed-off-by: Sydney Achinger <78113809+squid-ney@users.noreply.github.com>
Co-authored-by: John Philip <johnphilip283@gmail.com>
This commit is contained in:
Sydney Achinger
2024-11-26 15:26:30 -05:00
committed by GitHub
parent 73aa0d17d5
commit 991c9fec75
3 changed files with 92 additions and 21 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-search-backend-module-elasticsearch': patch
---
Update the ElasticSearchSearchEngine translator to handle phrase searches.
@@ -185,12 +185,11 @@ describe('ElasticSearchSearchEngine', () => {
expect(queryBody).toEqual({
query: {
bool: {
must: {
should: {
multi_match: {
query: 'testTerm',
fields: ['*'],
fuzziness: 'auto',
minimum_should_match: 1,
},
},
filter: {
@@ -205,6 +204,56 @@ describe('ElasticSearchSearchEngine', () => {
});
});
it('should return translated query with phrase terms', async () => {
const translatorUnderTest = inspectableSearchEngine.getTranslator();
const actualTranslatedQuery = translatorUnderTest({
types: ['indexName'],
term: '"test phrase" anotherTerm "another phrase"',
filters: {},
}) as ElasticSearchConcreteQuery;
expect(actualTranslatedQuery).toMatchObject({
documentTypes: ['indexName'],
elasticSearchQuery: expect.any(Object),
});
const queryBody = actualTranslatedQuery.elasticSearchQuery;
expect(queryBody).toEqual({
query: {
bool: {
should: [
{
multi_match: {
query: 'test phrase',
fields: ['*'],
type: 'phrase',
},
},
{
multi_match: {
query: 'another phrase',
fields: ['*'],
type: 'phrase',
},
},
{
multi_match: {
query: 'anotherTerm',
fields: ['*'],
fuzziness: 'auto',
},
},
],
filter: [],
},
},
from: 0,
size: 25,
});
});
it('should pass page cursor', async () => {
const translatorUnderTest = inspectableSearchEngine.getTranslator();
@@ -225,12 +274,11 @@ describe('ElasticSearchSearchEngine', () => {
query: {
bool: {
filter: [],
must: {
should: {
multi_match: {
query: 'testTerm',
fields: ['*'],
fuzziness: 'auto',
minimum_should_match: 1,
},
},
},
@@ -264,12 +312,11 @@ describe('ElasticSearchSearchEngine', () => {
expect(queryBody).toEqual({
query: {
bool: {
must: {
should: {
multi_match: {
query: 'testTerm',
fields: ['*'],
fuzziness: 'auto',
minimum_should_match: 1,
},
},
filter: [
@@ -320,12 +367,11 @@ describe('ElasticSearchSearchEngine', () => {
expect(queryBody).toEqual({
query: {
bool: {
must: {
should: {
multi_match: {
query: 'testTerm',
fields: ['*'],
fuzziness: 'auto',
minimum_should_match: 1,
},
},
filter: {
@@ -382,12 +428,11 @@ describe('ElasticSearchSearchEngine', () => {
query: {
bool: {
filter: [],
must: {
should: {
multi_match: {
query: 'testTerm',
fields: ['*'],
fuzziness: 'auto',
minimum_should_match: 1,
},
},
},
@@ -708,12 +753,11 @@ describe('ElasticSearchSearchEngine', () => {
body: {
query: {
bool: {
must: {
should: {
multi_match: {
query: 'testTerm',
fields: ['*'],
fuzziness: 'auto',
minimum_should_match: 1,
},
},
filter: [],
@@ -747,7 +791,7 @@ describe('ElasticSearchSearchEngine', () => {
body: {
query: {
bool: {
must: {
should: {
match_all: {},
},
filter: [],
@@ -782,7 +826,7 @@ describe('ElasticSearchSearchEngine', () => {
body: {
query: {
bool: {
must: {
should: {
match_all: {},
},
filter: [],
@@ -246,18 +246,40 @@ export class ElasticSearchSearchEngine implements SearchEngine {
'Failed to add filters to query. Unrecognized filter type',
);
});
const esbQuery = isBlank(term)
? esb.matchAllQuery()
: esb
.multiMatchQuery(['*'], term)
.fuzziness('auto')
.minimumShouldMatch(1);
const esbQueries = [];
// https://regex101.com/r/Lr0MqS/1
const phraseTerms = term.match(/"[^"]*"/g);
if (isBlank(term)) {
const esbQuery = esb.matchAllQuery();
esbQueries.push(esbQuery);
} else if (phraseTerms && phraseTerms.length > 0) {
let restTerm = term;
for (const phraseTerm of phraseTerms) {
restTerm = restTerm.replace(phraseTerm, '');
const esbPhraseQuery = esb
.multiMatchQuery(['*'], phraseTerm.replace(/"/g, ''))
.type('phrase');
esbQueries.push(esbPhraseQuery);
}
if (restTerm?.length > 0) {
const esbRestQuery = esb
.multiMatchQuery(['*'], restTerm.trim())
.fuzziness('auto');
esbQueries.push(esbRestQuery);
}
} else {
const esbQuery = esb.multiMatchQuery(['*'], term).fuzziness('auto');
esbQueries.push(esbQuery);
}
const pageSize = query.pageLimit || 25;
const { page } = decodePageCursor(pageCursor);
let esbRequestBodySearch = esb
.requestBodySearch()
.query(esb.boolQuery().filter(filter).must([esbQuery]))
.query(esb.boolQuery().filter(filter).should(esbQueries))
.from(page * pageSize)
.size(pageSize);