]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-11472 Add support highlighting exact matches in results
authorWouter Admiraal <wouter.admiraal@sonarsource.com>
Wed, 26 Dec 2018 13:54:08 +0000 (14:54 +0100)
committerSonarTech <sonartech@sonarsource.com>
Thu, 10 Jan 2019 19:21:02 +0000 (20:21 +0100)
server/sonar-docs/src/layouts/components/SearchEntryResult.js
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 c145669167451063699944b34d9cf4c1c12cfcc1..20d3a27e661eb3f7be9767739365a616cf5d8f6c 100644 (file)
@@ -50,9 +50,32 @@ export function SearchResultTitle({ result }) {
 
 export function SearchResultText({ result }) {
   const textHighlights = result.highlights.text;
-  if (textHighlights && textHighlights.length > 0) {
-    const { text } = result.page;
-    const tokens = highlightMarks(text, textHighlights.map(h => ({ from: h[0], to: h[0] + h[1] })));
+  const { text } = result.page;
+  let tokens = [];
+
+  if (result.exactMatch) {
+    const pageText = result.page.text.toLowerCase();
+    const highlights = [];
+    let start = 0;
+    let index;
+    let loopCount = 0;
+
+    while ((index = pageText.indexOf(result.query, start)) > -1 && loopCount < 10) {
+      loopCount++;
+      highlights.push({ from: index, to: index + result.query.length });
+      start = index + 1;
+    }
+
+    if (highlights.length) {
+      tokens = highlightMarks(text, highlights);
+    }
+  }
+
+  if (tokens.length === 0 && textHighlights && textHighlights.length > 0) {
+    tokens = highlightMarks(text, textHighlights.map(h => ({ from: h[0], to: h[0] + h[1] })));
+  }
+
+  if (tokens.length) {
     return (
       <div className="note">
         <SearchResultTokens tokens={cutWords(tokens)} />
index a47e71e0041309f72e58f6ea89809e6a3bd279c8..57083fa5021b54c68ea51d03ee3dae0048ca5b71 100644 (file)
@@ -27,6 +27,7 @@ export interface SearchResult {
   highlights: { [field: string]: [number, number][] };
   longestTerm: string;
   page: DocumentationEntry;
+  query: string;
 }
 
 interface Props {
@@ -69,9 +70,36 @@ export function SearchResultTitle({ result }: { result: SearchResult }) {
 
 export function SearchResultText({ result }: { result: SearchResult }) {
   const textHighlights = result.highlights.text;
-  if (textHighlights && textHighlights.length > 0) {
-    const { text } = result.page;
-    const tokens = highlightMarks(text, textHighlights.map(h => ({ from: h[0], to: h[0] + h[1] })));
+  const { text } = result.page;
+  let tokens: {
+    text: string;
+    marked: boolean;
+  }[] = [];
+
+  if (result.exactMatch) {
+    const pageText = result.page.text.toLowerCase();
+    const highlights: { from: number; to: number }[] = [];
+    let start = 0;
+    let index = pageText.indexOf(result.query, start);
+    let loopCount = 0;
+
+    while (index > -1 && loopCount < 10) {
+      loopCount++;
+      highlights.push({ from: index, to: index + result.query.length });
+      start = index + 1;
+      index = pageText.indexOf(result.query, start);
+    }
+
+    if (highlights.length) {
+      tokens = highlightMarks(text, highlights);
+    }
+  }
+
+  if (tokens.length === 0 && textHighlights && textHighlights.length > 0) {
+    tokens = highlightMarks(text, textHighlights.map(h => ({ from: h[0], to: h[0] + h[1] })));
+  }
+
+  if (tokens.length) {
     return (
       <div className="note">
         <SearchResultTokens tokens={cutWords(tokens)} />
index 7a0be949f76c1fd949e68a696127c1863d3724e0..fc70715270b87775f95e62a9080243d14a8788d7 100644 (file)
@@ -89,7 +89,7 @@ export default class SearchResults extends React.PureComponent<Props> {
           });
         });
 
-        return { page, highlights, longestTerm, exactMatch };
+        return { exactMatch, highlights, longestTerm, page, query };
       })
       .filter(result => result.page) as SearchResult[];
 
index 0f28fc04b89f446544ae1447614dcc1620922c1a..49ada2d46a1e042125e44867ee0a6e27580105c8 100644 (file)
@@ -25,54 +25,45 @@ import SearchResultEntry, {
   SearchResultTokens
 } from '../SearchResultEntry';
 
-const page = {
-  content: '',
-  relativeName: 'foo/bar',
-  url: '/foo/bar',
-  text: 'Foobar is a universal variable understood to represent whatever is being discussed.',
-  title: 'Foobar',
-  navTitle: undefined
-};
-
 describe('SearchResultEntry', () => {
   it('should render', () => {
     expect(
-      shallow(
-        <SearchResultEntry active={true} result={{ page, highlights: {}, longestTerm: '' }} />
-      )
+      shallow(<SearchResultEntry active={true} result={mockSearchResult()} />)
     ).toMatchSnapshot();
   });
 });
 
 describe('SearchResultText', () => {
   it('should render with highlights', () => {
+    expect(
+      shallow(<SearchResultText result={mockSearchResult({ highlights: { text: [[12, 9]] } })} />)
+    ).toMatchSnapshot();
+  });
+
+  it('should correctly extract exact matches', () => {
     expect(
       shallow(
-        <SearchResultText result={{ page, highlights: { text: [[12, 9]] }, longestTerm: '' }} />
+        <SearchResultText
+          result={mockSearchResult({ exactMatch: true, query: 'variable understood' })}
+        />
       )
     ).toMatchSnapshot();
   });
 
   it('should render without highlights', () => {
-    expect(
-      shallow(<SearchResultText result={{ page, highlights: {}, longestTerm: '' }} />)
-    ).toMatchSnapshot();
+    expect(shallow(<SearchResultText result={mockSearchResult()} />)).toMatchSnapshot();
   });
 });
 
 describe('SearchResultTitle', () => {
   it('should render with highlights', () => {
     expect(
-      shallow(
-        <SearchResultTitle result={{ page, highlights: { title: [[0, 6]] }, longestTerm: '' }} />
-      )
+      shallow(<SearchResultTitle result={mockSearchResult({ highlights: { title: [[0, 6]] } })} />)
     ).toMatchSnapshot();
   });
 
   it('should render not without highlights', () => {
-    expect(
-      shallow(<SearchResultTitle result={{ page, highlights: {}, longestTerm: '' }} />)
-    ).toMatchSnapshot();
+    expect(shallow(<SearchResultTitle result={mockSearchResult()} />)).toMatchSnapshot();
   });
 });
 
@@ -94,3 +85,20 @@ describe('SearchResultTokens', () => {
     ).toMatchSnapshot();
   });
 });
+
+function mockSearchResult(overrides = {}) {
+  return {
+    page: {
+      content: '',
+      relativeName: 'foo/bar',
+      url: '/foo/bar',
+      text: 'Foobar is a universal variable understood to represent whatever is being discussed.',
+      title: 'Foobar',
+      navTitle: undefined
+    },
+    highlights: {},
+    longestTerm: '',
+    query: '',
+    ...overrides
+  };
+}
index 40818733fe7a41dab6549b484a0fb634cf69132a..c45f65fc1a58af7d11d53b3bc2d3585a2cee93aa 100644 (file)
@@ -20,6 +20,7 @@ exports[`SearchResultEntry should render 1`] = `
           "title": "Foobar",
           "url": "/foo/bar",
         },
+        "query": "",
       }
     }
   />
@@ -36,12 +37,38 @@ exports[`SearchResultEntry should render 1`] = `
           "title": "Foobar",
           "url": "/foo/bar",
         },
+        "query": "",
       }
     }
   />
 </Link>
 `;
 
+exports[`SearchResultText should correctly extract exact matches 1`] = `
+<div
+  className="note"
+>
+  <SearchResultTokens
+    tokens={
+      Array [
+        Object {
+          "marked": false,
+          "text": "Foobar is a universal ",
+        },
+        Object {
+          "marked": true,
+          "text": "variable understood",
+        },
+        Object {
+          "marked": false,
+          "text": " to represent whatever is being discussed.",
+        },
+      ]
+    }
+  />
+</div>
+`;
+
 exports[`SearchResultText should render with highlights 1`] = `
 <div
   className="note"
index f82b3fdda85deca509bf92c7c5e8e69dec6fa301..ae5779ca00ca2b612dec70f07be4726ed4242c82 100644 (file)
@@ -35,6 +35,7 @@ exports[`should search 1`] = `
           "title": "Where does Foobar come from?",
           "url": "/foobar",
         },
+        "query": "simply text",
       }
     }
   />
@@ -71,6 +72,7 @@ exports[`should search 1`] = `
           "title": "Where does it come from?",
           "url": "/lorem/origin",
         },
+        "query": "simply text",
       }
     }
   />