]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-11134 Change the doc search ranking algorithm
authorStas Vilchik <stas.vilchik@sonarsource.com>
Fri, 24 Aug 2018 09:14:35 +0000 (11:14 +0200)
committerSonarTech <sonartech@sonarsource.com>
Mon, 27 Aug 2018 18:21:57 +0000 (20:21 +0200)
server/sonar-web/src/main/js/apps/documentation/components/SearchResultEntry.tsx
server/sonar-web/src/main/js/apps/documentation/components/SearchResults.tsx
server/sonar-web/src/main/js/apps/documentation/components/__tests__/SearchResultEntry-test.tsx
server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/SearchResultEntry-test.tsx.snap
server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/SearchResults-test.tsx.snap

index 613c390d788d24918fb2d98a5b99a689ff70ca40..436dfb56e95e2fc66cfc84319739707df20802a1 100644 (file)
@@ -23,8 +23,9 @@ import { Link } from 'react-router';
 import { highlightMarks, cutWords, DocumentationEntry } from '../utils';
 
 export interface SearchResult {
-  page: DocumentationEntry;
   highlights: { [field: string]: [number, number][] };
+  longestTerm: string;
+  page: DocumentationEntry;
 }
 
 interface Props {
index 30cfdc9fd947a3b805e344b59749c0da17dc20c1..b42ba425fd9e3d3d88633afc42a91a7fedc0c621 100644 (file)
@@ -19,6 +19,7 @@
  */
 import * as React from 'react';
 import lunr, { LunrIndex } from 'lunr';
+import { sortBy } from 'lodash';
 import SearchResultEntry, { SearchResult } from './SearchResultEntry';
 import { DocumentationEntry } from '../utils';
 
@@ -51,21 +52,34 @@ export default class SearchResults extends React.PureComponent<Props> {
       .map(match => {
         const page = this.props.pages.find(page => page.relativeName === match.ref);
         const highlights: { [field: string]: [number, number][] } = {};
+        let longestTerm = '';
 
+        // remember the longest term that matches the query *exactly*
         Object.keys(match.matchData.metadata).forEach(term => {
+          if (
+            query.toLowerCase().includes(term.toLowerCase()) &&
+            longestTerm.length < term.length
+          ) {
+            longestTerm = term;
+          }
+
           Object.keys(match.matchData.metadata[term]).forEach(fieldName => {
             const { position: positions } = match.matchData.metadata[term][fieldName];
             highlights[fieldName] = [...(highlights[fieldName] || []), ...positions];
           });
         });
 
-        return { page, highlights };
+        return { page, highlights, longestTerm };
       })
       .filter(result => result.page) as SearchResult[];
 
+    // re-order results by the length of the longest matched term
+    // the longer term is the more chances the result is more relevant
+    const sortedResults = sortBy(results, result => -result.longestTerm.length);
+
     return (
       <>
-        {results.map(result => (
+        {sortedResults.map(result => (
           <SearchResultEntry
             active={result.page.relativeName === this.props.splat}
             key={result.page.relativeName}
index 370c39b376fde6d62e9d753b8562c702dd95005d..6785fd66bf8eedf751812a4a2109802810098058 100644 (file)
@@ -36,7 +36,9 @@ const page = {
 describe('SearchResultEntry', () => {
   it('should render', () => {
     expect(
-      shallow(<SearchResultEntry active={true} result={{ page, highlights: {} }} />)
+      shallow(
+        <SearchResultEntry active={true} result={{ page, highlights: {}, longestTerm: '' }} />
+      )
     ).toMatchSnapshot();
   });
 });
@@ -44,24 +46,32 @@ describe('SearchResultEntry', () => {
 describe('SearchResultText', () => {
   it('should render with highlights', () => {
     expect(
-      shallow(<SearchResultText result={{ page, highlights: { text: [[12, 9]] } }} />)
+      shallow(
+        <SearchResultText result={{ page, highlights: { text: [[12, 9]] }, longestTerm: '' }} />
+      )
     ).toMatchSnapshot();
   });
 
   it('should render without highlights', () => {
-    expect(shallow(<SearchResultText result={{ page, highlights: {} }} />)).toMatchSnapshot();
+    expect(
+      shallow(<SearchResultText result={{ page, highlights: {}, longestTerm: '' }} />)
+    ).toMatchSnapshot();
   });
 });
 
 describe('SearchResultTitle', () => {
   it('should render with highlights', () => {
     expect(
-      shallow(<SearchResultTitle result={{ page, highlights: { title: [[0, 6]] } }} />)
+      shallow(
+        <SearchResultTitle result={{ page, highlights: { title: [[0, 6]] }, longestTerm: '' }} />
+      )
     ).toMatchSnapshot();
   });
 
   it('should render not without highlights', () => {
-    expect(shallow(<SearchResultTitle result={{ page, highlights: {} }} />)).toMatchSnapshot();
+    expect(
+      shallow(<SearchResultTitle result={{ page, highlights: {}, longestTerm: '' }} />)
+    ).toMatchSnapshot();
   });
 });
 
index 7a2b86c359066c0a89cd40e7ea5619e0432de29f..3be4c54be97a00907927027404efa385ff16b634 100644 (file)
@@ -11,6 +11,7 @@ exports[`SearchResultEntry should render 1`] = `
     result={
       Object {
         "highlights": Object {},
+        "longestTerm": "",
         "page": Object {
           "content": "",
           "order": -1,
@@ -25,6 +26,7 @@ exports[`SearchResultEntry should render 1`] = `
     result={
       Object {
         "highlights": Object {},
+        "longestTerm": "",
         "page": Object {
           "content": "",
           "order": -1,
index 69473caa2445f36b7ce3b246fb9ae8828a9a382d..cc6081342c887e380a3096d798055dcd5e3a9b62 100644 (file)
@@ -21,6 +21,7 @@ exports[`should search 1`] = `
             ],
           ],
         },
+        "longestTerm": "from",
         "page": Object {
           "content": "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words.",
           "order": -1,
@@ -44,6 +45,7 @@ exports[`should search 1`] = `
             ],
           ],
         },
+        "longestTerm": "from",
         "page": Object {
           "content": "Foobar is a universal variable understood to represent whatever is being discussed.",
           "order": -1,