diff --git a/x-pack/plugins/code/server/search/document_search_client.test.ts b/x-pack/plugins/code/server/search/document_search_client.test.ts index b09aabc7bd76..2f3d80a53ddc 100644 --- a/x-pack/plugins/code/server/search/document_search_client.test.ts +++ b/x-pack/plugins/code/server/search/document_search_client.test.ts @@ -22,14 +22,15 @@ function initSearchClient() { } const mockSearchResults = [ - // 1. The first response is a valid DocumentSearchResult with 1 doc + // 1. The first response is a valid DocumentSearchResult with 2 docs { took: 1, hits: { total: { - value: 1, + value: 2, }, hits: [ + // File content matching { _source: { repoUri: 'github.com/Microsoft/TypeScript-Node-Starter', @@ -45,20 +46,34 @@ const mockSearchResults = [ ], }, }, + // File path matching + { + _source: { + repoUri: 'github.com/Microsoft/TypeScript-Node-Starter', + path: 'src/types/string.d.ts', + content: + 'no query in content;\nno query in content;\nno query in content;\nno query in content;\nno query in content;\n', + language: 'typescript', + qnames: ['express-flash'], + }, + highlight: { + content: [], + }, + }, ], }, aggregations: { repoUri: { buckets: [ { - 'github.com/Microsoft/TypeScript-Node-Starter': 1, + 'github.com/Microsoft/TypeScript-Node-Starter': 2, }, ], }, language: { buckets: [ { - typescript: 1, + typescript: 2, }, ], }, @@ -105,12 +120,12 @@ beforeEach(() => { initSearchClient(); }); -test('Repository search', async () => { +test('Document search', async () => { // 1. The first response should have 1 result. const responseWithResult = await docSearchClient.search({ query: 'string', page: 1 }); expect(responseWithResult).toEqual( expect.objectContaining({ - total: 1, + total: 2, totalPage: 1, page: 1, query: 'string', @@ -136,15 +151,29 @@ test('Repository search', async () => { language: 'typescript', hits: 1, }, + { + uri: 'github.com/Microsoft/TypeScript-Node-Starter', + filePath: 'src/types/string.d.ts', + compositeContent: { + // Content is shorted + content: 'no query in content;\nno query in content;\nno query in content;\n', + // Line mapping data is populated + lineMapping: ['1', '2', '3', '..'], + // Highlight ranges are calculated + ranges: [], + }, + language: 'typescript', + hits: 0, + }, ], repoAggregations: [ { - 'github.com/Microsoft/TypeScript-Node-Starter': 1, + 'github.com/Microsoft/TypeScript-Node-Starter': 2, }, ], langAggregations: [ { - typescript: 1, + typescript: 2, }, ], }) @@ -156,12 +185,12 @@ test('Repository search', async () => { expect(responseWithEmptyResult.total).toEqual(0); }); -test('Repository suggest', async () => { +test('Document suggest', async () => { // 1. The first response should have 1 result. const responseWithResult = await docSearchClient.suggest({ query: 'string', page: 1 }); expect(responseWithResult).toEqual( expect.objectContaining({ - total: 1, + total: 2, totalPage: 1, page: 1, query: 'string', @@ -178,6 +207,18 @@ test('Repository suggest', async () => { language: 'typescript', hits: 0, }, + { + uri: 'github.com/Microsoft/TypeScript-Node-Starter', + filePath: 'src/types/string.d.ts', + // compositeContent field is intended to leave empty. + compositeContent: { + content: '', + lineMapping: [], + ranges: [], + }, + language: 'typescript', + hits: 0, + }, ], }) ); diff --git a/x-pack/plugins/code/server/search/document_search_client.ts b/x-pack/plugins/code/server/search/document_search_client.ts index 93513a7c71d9..d6193e2a9f62 100644 --- a/x-pack/plugins/code/server/search/document_search_client.ts +++ b/x-pack/plugins/code/server/search/document_search_client.ts @@ -31,6 +31,7 @@ const MAX_HIT_NUMBER = 5; export class DocumentSearchClient extends AbstractSearchClient { private HIGHLIGHT_TAG = '_@_'; + private LINE_SEPARATOR = '\n'; constructor(protected readonly client: EsClient, protected readonly log: Logger) { super(client, log); @@ -302,24 +303,29 @@ export class DocumentSearchClient extends AbstractSearchClient { } private getSourceContent(hitsContent: SourceHit[], doc: Document) { + const docInLines = doc.content.split(this.LINE_SEPARATOR); + let slicedRanges: LineRange[] = []; if (hitsContent.length === 0) { - return { - content: '', - lineMapping: [], - ranges: [], - }; + // Always add a placeholder range of the first line so that for filepath + // matching search result, we will render some file content. + slicedRanges = [ + { + startLine: 0, + endLine: 0, + }, + ]; + } else { + const slicedHighlights = hitsContent.slice(0, MAX_HIT_NUMBER); + slicedRanges = slicedHighlights.map(hit => ({ + startLine: hit.range.startLoc.line, + endLine: hit.range.endLoc.line, + })); } - const slicedHighlights = hitsContent.slice(0, MAX_HIT_NUMBER); - const slicedRanges: LineRange[] = slicedHighlights.map(hit => ({ - startLine: hit.range.startLoc.line, - endLine: hit.range.endLoc.line, - })); - const expandedRanges = expandRanges(slicedRanges, HIT_MERGE_LINE_INTERVAL); const mergedRanges = mergeRanges(expandedRanges); const lineMapping = new LineMapping(); - const result = extractSourceContent(mergedRanges, doc.content.split('\n'), lineMapping); + const result = extractSourceContent(mergedRanges, docInLines, lineMapping); const ranges: IRange[] = hitsContent .filter(hit => lineMapping.hasLine(hit.range.startLoc.line)) .map(hit => ({ @@ -329,7 +335,7 @@ export class DocumentSearchClient extends AbstractSearchClient { endLineNumber: lineMapping.lineNumber(hit.range.endLoc.line), })); return { - content: result.join('\n'), + content: result.join(this.LINE_SEPARATOR), lineMapping: lineMapping.toStringArray(), ranges, };