diff --git a/.changeset/cyan-drinks-dream.md b/.changeset/cyan-drinks-dream.md new file mode 100644 index 0000000000..f07d83bd90 --- /dev/null +++ b/.changeset/cyan-drinks-dream.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-node': patch +--- + +Improved the quality of free text searches in LunrSearchEngine. diff --git a/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts b/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts index 969e579a53..f27d00097f 100644 --- a/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts +++ b/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts @@ -191,6 +191,72 @@ describe('LunrSearchEngine', () => { }); }); + it('should perform search query with trailing punctuation and return search results on match (trimming)', async () => { + const mockDocuments = [ + { + title: 'testTitle', + text: 'Hello World.', + location: 'test/location', + }, + ]; + + // Mock indexing of 1 document + testLunrSearchEngine.index('test-index', mockDocuments); + + // Perform search query + const mockedSearchResult = await testLunrSearchEngine.query({ + term: 'World', + filters: {}, + pageCursor: '', + }); + + // Should return 1 result as we are mocking the indexing of 1 document with match on the title field + expect(mockedSearchResult).toMatchObject({ + results: [ + { + document: { + title: 'testTitle', + text: 'Hello World.', + location: 'test/location', + }, + }, + ], + }); + }); + + it('should perform search query by similar words and return search results on match (stemming)', async () => { + const mockDocuments = [ + { + title: 'testTitle', + text: 'Searching', + location: 'test/location', + }, + ]; + + // Mock indexing of 1 document + testLunrSearchEngine.index('test-index', mockDocuments); + + // Perform search query + const mockedSearchResult = await testLunrSearchEngine.query({ + term: 'Search', + filters: {}, + pageCursor: '', + }); + + // Should return 1 result as we are mocking the indexing of 1 document with match on the title field + expect(mockedSearchResult).toMatchObject({ + results: [ + { + document: { + title: 'testTitle', + text: 'Searching', + location: 'test/location', + }, + }, + ], + }); + }); + it('should perform search query and return search results on match with filters', async () => { const mockDocuments = [ { diff --git a/plugins/search-backend-node/src/engines/LunrSearchEngine.ts b/plugins/search-backend-node/src/engines/LunrSearchEngine.ts index be51738920..18f2058583 100644 --- a/plugins/search-backend-node/src/engines/LunrSearchEngine.ts +++ b/plugins/search-backend-node/src/engines/LunrSearchEngine.ts @@ -90,6 +90,10 @@ export class LunrSearchEngine implements SearchEngine { index(type: string, documents: IndexableDocument[]): void { const lunrBuilder = new lunr.Builder(); + + lunrBuilder.pipeline.add(lunr.trimmer, lunr.stopWordFilter, lunr.stemmer); + lunrBuilder.searchPipeline.add(lunr.stemmer); + // Make this lunr index aware of all relevant fields. Object.keys(documents[0]).forEach(field => { lunrBuilder.field(field);