feat(aio): top 5 weighted search results shown when many area results
This commit is contained in:
parent
04dc24820d
commit
0c69903123
@ -1,8 +1,12 @@
|
|||||||
<div class="search-results" *ngIf="hasAreas | async" >
|
<div class="search-results" *ngIf="hasAreas | async" >
|
||||||
<h2 class="visually-hidden">Search Results</h2>
|
<h2 class="visually-hidden">Search Results</h2>
|
||||||
<div class="search-area" *ngFor="let area of searchAreas | async">
|
<div class="search-area" *ngFor="let area of searchAreas | async">
|
||||||
<h3>{{area.name}}</h3>
|
<h3>{{area.name}} ({{area.pages.length}})</h3>
|
||||||
<ul>
|
<ul>
|
||||||
|
<li class="search-page" *ngFor="let page of area.priorityPages">
|
||||||
|
<a class="search-result-item" href="{{ page.path }}" (click)="onResultSelected(page)">{{ page.title }}</a>
|
||||||
|
</li>
|
||||||
|
<li *ngIf="area.priorityPages.length"><hr></li>
|
||||||
<li class="search-page" *ngFor="let page of area.pages">
|
<li class="search-page" *ngFor="let page of area.pages">
|
||||||
<a class="search-result-item" href="{{ page.path }}" (click)="onResultSelected(page)">{{ page.title }}</a>
|
<a class="search-result-item" href="{{ page.path }}" (click)="onResultSelected(page)">{{ page.title }}</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -16,6 +16,25 @@ describe('SearchResultsComponent', () => {
|
|||||||
/** Get all text from component element */
|
/** Get all text from component element */
|
||||||
function getText() { return fixture.debugElement.nativeElement.innerText; }
|
function getText() { return fixture.debugElement.nativeElement.innerText; }
|
||||||
|
|
||||||
|
/** Get a full set of test results. "Take" what you need */
|
||||||
|
function getTestResults(take?: number) {
|
||||||
|
const results: SearchResult[] = [
|
||||||
|
{ path: 'guide/a', title: 'Guide A'},
|
||||||
|
{ path: 'api/d', title: 'API D'},
|
||||||
|
{ path: 'guide/b', title: 'Guide B' },
|
||||||
|
{ path: 'guide/a/c', title: 'Guide A - C'},
|
||||||
|
{ path: 'api/c', title: 'API C' }
|
||||||
|
]
|
||||||
|
// fill it out to exceed 10 guide pages
|
||||||
|
.concat('nmlkjihgfe'.split('').map(l => {
|
||||||
|
return { path: 'guide/' + l, title: 'Guide ' + l};
|
||||||
|
}))
|
||||||
|
// add these empty fields to satisfy interface
|
||||||
|
.map(r => ({...r, ...{ keywords: '', titleWords: '', type: '' }}));
|
||||||
|
|
||||||
|
return take === undefined ? results : results.slice(0, take);
|
||||||
|
}
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [ SearchResultsComponent ],
|
declarations: [ SearchResultsComponent ],
|
||||||
@ -36,50 +55,57 @@ describe('SearchResultsComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should map the search results into groups based on their containing folder', () => {
|
it('should map the search results into groups based on their containing folder', () => {
|
||||||
const results = [
|
const results = getTestResults(3);
|
||||||
{path: 'guide/a', title: 'Guide A', type: 'content', keywords: '', titleWords: '' },
|
|
||||||
{path: 'guide/b', title: 'Guide B', type: 'content', keywords: '', titleWords: '' },
|
|
||||||
{path: 'api/c', title: 'API C', type: 'class', keywords: '', titleWords: '' },
|
|
||||||
{path: 'guide/b/c', title: 'Guide B - C', type: 'content', keywords: '', titleWords: '' },
|
|
||||||
];
|
|
||||||
|
|
||||||
searchResults.next({ query: '', results: results});
|
searchResults.next({ query: '', results: results});
|
||||||
expect(currentAreas).toEqual([
|
expect(currentAreas).toEqual([
|
||||||
{ name: 'api', pages: [
|
{ name: 'api', pages: [
|
||||||
{ path: 'api/c', title: 'API C', type: 'class', keywords: '', titleWords: '' }
|
{ path: 'api/d', title: 'API D', type: '', keywords: '', titleWords: '' }
|
||||||
] },
|
], priorityPages: [] },
|
||||||
{ name: 'guide', pages: [
|
{ name: 'guide', pages: [
|
||||||
{ path: 'guide/a', title: 'Guide A', type: 'content', keywords: '', titleWords: '' },
|
{ path: 'guide/a', title: 'Guide A', type: '', keywords: '', titleWords: '' },
|
||||||
{ path: 'guide/b', title: 'Guide B', type: 'content', keywords: '', titleWords: '' },
|
{ path: 'guide/b', title: 'Guide B', type: '', keywords: '', titleWords: '' },
|
||||||
{ path: 'guide/b/c', title: 'Guide B - C', type: 'content', keywords: '', titleWords: '' }
|
], priorityPages: [] }
|
||||||
] }
|
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should sort by title within sorted area', () => {
|
it('should sort by title within sorted area', () => {
|
||||||
const results = [
|
const results = getTestResults(5);
|
||||||
{path: 'guide/b', title: 'Guide B', type: 'content', keywords: '', titleWords: '' },
|
|
||||||
{path: 'guide/a', title: 'Guide A', type: 'content', keywords: '', titleWords: '' },
|
|
||||||
{path: 'api/d', title: 'API D', type: 'class', keywords: '', titleWords: '' },
|
|
||||||
{path: 'guide/a/c', title: 'Guide A - C', type: 'content', keywords: '', titleWords: '' },
|
|
||||||
{path: 'api/c', title: 'API C', type: 'class', keywords: '', titleWords: '' },
|
|
||||||
];
|
|
||||||
|
|
||||||
searchResults.next({ query: '', results: results });
|
searchResults.next({ query: '', results: results });
|
||||||
|
|
||||||
expect(currentAreas).toEqual([
|
expect(currentAreas).toEqual([
|
||||||
{ name: 'api', pages: [
|
{ name: 'api', pages: [
|
||||||
{path: 'api/c', title: 'API C', type: 'class', keywords: '', titleWords: '' },
|
{path: 'api/c', title: 'API C', type: '', keywords: '', titleWords: '' },
|
||||||
{path: 'api/d', title: 'API D', type: 'class', keywords: '', titleWords: '' },
|
{path: 'api/d', title: 'API D', type: '', keywords: '', titleWords: '' },
|
||||||
] },
|
], priorityPages: [] },
|
||||||
{ name: 'guide', pages: [
|
{ name: 'guide', pages: [
|
||||||
{path: 'guide/a', title: 'Guide A', type: 'content', keywords: '', titleWords: '' },
|
{path: 'guide/a', title: 'Guide A', type: '', keywords: '', titleWords: '' },
|
||||||
{path: 'guide/a/c', title: 'Guide A - C', type: 'content', keywords: '', titleWords: '' },
|
{path: 'guide/a/c', title: 'Guide A - C', type: '', keywords: '', titleWords: '' },
|
||||||
{path: 'guide/b', title: 'Guide B', type: 'content', keywords: '', titleWords: '' },
|
{path: 'guide/b', title: 'Guide B', type: '', keywords: '', titleWords: '' },
|
||||||
] }
|
], priorityPages: [] }
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should put first 5 area results into priorityPages when more than 10 pages', () => {
|
||||||
|
const results = getTestResults();
|
||||||
|
const sorted = results.slice().sort((l, r) => l.title > r.title ? 1 : -1);
|
||||||
|
const expected = [
|
||||||
|
{
|
||||||
|
name: 'api',
|
||||||
|
pages: sorted.filter(p => p.path.startsWith('api')),
|
||||||
|
priorityPages: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'guide',
|
||||||
|
pages: sorted.filter(p => p.path.startsWith('guide')),
|
||||||
|
priorityPages: results.filter(p => p.path.startsWith('guide')).slice(0, 5)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
searchResults.next({ query: '', results: results });
|
||||||
|
expect(currentAreas).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
it('should put search results with no containing folder into the default area (other)', () => {
|
it('should put search results with no containing folder into the default area (other)', () => {
|
||||||
const results = [
|
const results = [
|
||||||
{path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '' }
|
{path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '' }
|
||||||
@ -89,7 +115,7 @@ describe('SearchResultsComponent', () => {
|
|||||||
expect(currentAreas).toEqual([
|
expect(currentAreas).toEqual([
|
||||||
{ name: 'other', pages: [
|
{ name: 'other', pages: [
|
||||||
{ path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '' }
|
{ path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '' }
|
||||||
] }
|
], priorityPages: [] }
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import { SearchResult, SearchResults, SearchService } from '../search.service';
|
|||||||
export interface SearchArea {
|
export interface SearchArea {
|
||||||
name: string;
|
name: string;
|
||||||
pages: SearchResult[];
|
pages: SearchResult[];
|
||||||
|
priorityPages: SearchResult[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -65,10 +66,12 @@ export class SearchResultsComponent implements OnInit {
|
|||||||
area.push(result);
|
area.push(result);
|
||||||
});
|
});
|
||||||
const keys = Object.keys(searchAreaMap).sort((l, r) => l > r ? 1 : -1);
|
const keys = Object.keys(searchAreaMap).sort((l, r) => l > r ? 1 : -1);
|
||||||
return keys.map(name => ({
|
return keys.map(name => {
|
||||||
name,
|
let pages = searchAreaMap[name];
|
||||||
pages: searchAreaMap[name].sort(compareResults)
|
const priorityPages = pages.length > 10 ? searchAreaMap[name].slice(0, 5) : [];
|
||||||
}));
|
pages = pages.sort(compareResults);
|
||||||
|
return { name, pages, priorityPages };
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split the search result path and use the top level folder, if there is one, as the area name.
|
// Split the search result path and use the top level folder, if there is one, as the area name.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user