Browse Source

SONAR-11472 Add support highlighting exact matches in results

tags/7.6
Wouter Admiraal 5 years ago
parent
commit
77d4844e58

+ 26
- 3
server/sonar-docs/src/layouts/components/SearchEntryResult.js View File



export function SearchResultText({ result }) { export function SearchResultText({ result }) {
const textHighlights = result.highlights.text; 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 ( return (
<div className="note"> <div className="note">
<SearchResultTokens tokens={cutWords(tokens)} /> <SearchResultTokens tokens={cutWords(tokens)} />

+ 31
- 3
server/sonar-web/src/main/js/apps/documentation/components/SearchResultEntry.tsx View File

highlights: { [field: string]: [number, number][] }; highlights: { [field: string]: [number, number][] };
longestTerm: string; longestTerm: string;
page: DocumentationEntry; page: DocumentationEntry;
query: string;
} }


interface Props { interface Props {


export function SearchResultText({ result }: { result: SearchResult }) { export function SearchResultText({ result }: { result: SearchResult }) {
const textHighlights = result.highlights.text; 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 ( return (
<div className="note"> <div className="note">
<SearchResultTokens tokens={cutWords(tokens)} /> <SearchResultTokens tokens={cutWords(tokens)} />

+ 1
- 1
server/sonar-web/src/main/js/apps/documentation/components/SearchResults.tsx View File

}); });
}); });


return { page, highlights, longestTerm, exactMatch };
return { exactMatch, highlights, longestTerm, page, query };
}) })
.filter(result => result.page) as SearchResult[]; .filter(result => result.page) as SearchResult[];



+ 30
- 22
server/sonar-web/src/main/js/apps/documentation/components/__tests__/SearchResultEntry-test.tsx View File

SearchResultTokens SearchResultTokens
} from '../SearchResultEntry'; } 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', () => { describe('SearchResultEntry', () => {
it('should render', () => { it('should render', () => {
expect( expect(
shallow(
<SearchResultEntry active={true} result={{ page, highlights: {}, longestTerm: '' }} />
)
shallow(<SearchResultEntry active={true} result={mockSearchResult()} />)
).toMatchSnapshot(); ).toMatchSnapshot();
}); });
}); });


describe('SearchResultText', () => { describe('SearchResultText', () => {
it('should render with highlights', () => { it('should render with highlights', () => {
expect(
shallow(<SearchResultText result={mockSearchResult({ highlights: { text: [[12, 9]] } })} />)
).toMatchSnapshot();
});

it('should correctly extract exact matches', () => {
expect( expect(
shallow( shallow(
<SearchResultText result={{ page, highlights: { text: [[12, 9]] }, longestTerm: '' }} />
<SearchResultText
result={mockSearchResult({ exactMatch: true, query: 'variable understood' })}
/>
) )
).toMatchSnapshot(); ).toMatchSnapshot();
}); });


it('should render without highlights', () => { it('should render without highlights', () => {
expect(
shallow(<SearchResultText result={{ page, highlights: {}, longestTerm: '' }} />)
).toMatchSnapshot();
expect(shallow(<SearchResultText result={mockSearchResult()} />)).toMatchSnapshot();
}); });
}); });


describe('SearchResultTitle', () => { describe('SearchResultTitle', () => {
it('should render with highlights', () => { it('should render with highlights', () => {
expect( expect(
shallow(
<SearchResultTitle result={{ page, highlights: { title: [[0, 6]] }, longestTerm: '' }} />
)
shallow(<SearchResultTitle result={mockSearchResult({ highlights: { title: [[0, 6]] } })} />)
).toMatchSnapshot(); ).toMatchSnapshot();
}); });


it('should render not without highlights', () => { it('should render not without highlights', () => {
expect(
shallow(<SearchResultTitle result={{ page, highlights: {}, longestTerm: '' }} />)
).toMatchSnapshot();
expect(shallow(<SearchResultTitle result={mockSearchResult()} />)).toMatchSnapshot();
}); });
}); });


).toMatchSnapshot(); ).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
};
}

+ 27
- 0
server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/SearchResultEntry-test.tsx.snap View File

"title": "Foobar", "title": "Foobar",
"url": "/foo/bar", "url": "/foo/bar",
}, },
"query": "",
} }
} }
/> />
"title": "Foobar", "title": "Foobar",
"url": "/foo/bar", "url": "/foo/bar",
}, },
"query": "",
} }
} }
/> />
</Link> </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`] = ` exports[`SearchResultText should render with highlights 1`] = `
<div <div
className="note" className="note"

+ 2
- 0
server/sonar-web/src/main/js/apps/documentation/components/__tests__/__snapshots__/SearchResults-test.tsx.snap View File

"title": "Where does Foobar come from?", "title": "Where does Foobar come from?",
"url": "/foobar", "url": "/foobar",
}, },
"query": "simply text",
} }
} }
/> />
"title": "Where does it come from?", "title": "Where does it come from?",
"url": "/lorem/origin", "url": "/lorem/origin",
}, },
"query": "simply text",
} }
} }
/> />

Loading…
Cancel
Save