build(docs-infra): expose deprecated status on items more clearly (#25750)

PR Close #25750
This commit is contained in:
Pete Bacon Darwin 2018-09-17 17:37:18 +01:00 committed by Kara Erickson
parent cea2e0477c
commit 026b60cd70
22 changed files with 321 additions and 93 deletions

View File

@ -775,7 +775,7 @@ describe('AppComponent', () => {
const searchService: MockSearchService = TestBed.get(SearchService); const searchService: MockSearchService = TestBed.get(SearchService);
const results = [ const results = [
{ path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '' } { path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '', deprecated: false }
]; ];
searchService.searchResults.next({ query: 'something', results: results }); searchService.searchResults.next({ query: 'something', results: results });

View File

@ -22,11 +22,14 @@
<article class="api-list-container l-content-small docs-content"> <article class="api-list-container l-content-small docs-content">
<div *ngFor="let section of filteredSections | async" > <div *ngFor="let section of filteredSections | async" >
<h2 *ngIf="section.items"><a [href]="section.path">{{section.title}}</a></h2> <h2 *ngIf="section.items"><a [href]="section.path" [class.deprecated-api-item]="section.deprecated">{{section.title}}</a></h2>
<ul class="api-list" *ngIf="section.items?.length"> <ul class="api-list" *ngIf="section.items?.length">
<ng-container *ngFor="let item of section.items"> <ng-container *ngFor="let item of section.items">
<li class="api-item"> <li class="api-item">
<a [href]="item.path"><span class="symbol {{item.docType}}"></span> {{item.title}}</a> <a [href]="item.path" [class.deprecated-api-item]="item.stability === 'deprecated'">
<span class="symbol {{item.docType}}"></span>
{{item.title}}
</a>
</li> </li>
</ng-container> </ng-container>
</ul> </ul>

View File

@ -226,6 +226,7 @@ const apiSections: ApiSection[] = [
"name": "common", "name": "common",
"title": "common", "title": "common",
"path": "api/common", "path": "api/common",
"deprecated": false,
"items": [ "items": [
{ {
"name": "class_1", "name": "class_1",
@ -233,7 +234,7 @@ const apiSections: ApiSection[] = [
"path": "api/common/class_1", "path": "api/common/class_1",
"docType": "class", "docType": "class",
"stability": "experimental", "stability": "experimental",
"securityRisk": false "securityRisk": false,
}, },
{ {
"name": "class_2", "name": "class_2",
@ -241,7 +242,7 @@ const apiSections: ApiSection[] = [
"path": "api/common/class_2", "path": "api/common/class_2",
"docType": "class", "docType": "class",
"stability": "stable", "stability": "stable",
"securityRisk": false "securityRisk": false,
}, },
{ {
"name": "directive_1", "name": "directive_1",
@ -249,7 +250,7 @@ const apiSections: ApiSection[] = [
"path": "api/common/directive_1", "path": "api/common/directive_1",
"docType": "directive", "docType": "directive",
"stability": "stable", "stability": "stable",
"securityRisk": true "securityRisk": true,
}, },
{ {
"name": "pipe_1", "name": "pipe_1",
@ -257,7 +258,7 @@ const apiSections: ApiSection[] = [
"path": "api/common/pipe_1", "path": "api/common/pipe_1",
"docType": "pipe", "docType": "pipe",
"stability": "stable", "stability": "stable",
"securityRisk": true "securityRisk": true,
}, },
] ]
}, },
@ -265,6 +266,7 @@ const apiSections: ApiSection[] = [
"name": "core", "name": "core",
"title": "core", "title": "core",
"path": "api/core", "path": "api/core",
"deprecated": false,
"items": [ "items": [
{ {
"name": "class_3", "name": "class_3",
@ -272,7 +274,7 @@ const apiSections: ApiSection[] = [
"path": "api/core/class_3", "path": "api/core/class_3",
"docType": "class", "docType": "class",
"stability": "experimental", "stability": "experimental",
"securityRisk": false "securityRisk": false,
}, },
{ {
"name": "function_1", "name": "function_1",
@ -280,7 +282,7 @@ const apiSections: ApiSection[] = [
"path": "api/core/function 1", "path": "api/core/function 1",
"docType": "function", "docType": "function",
"stability": "deprecated", "stability": "deprecated",
"securityRisk": true "securityRisk": true,
}, },
{ {
"name": "const_1", "name": "const_1",
@ -288,7 +290,7 @@ const apiSections: ApiSection[] = [
"path": "api/core/const_1", "path": "api/core/const_1",
"docType": "const", "docType": "const",
"stability": "stable", "stability": "stable",
"securityRisk": false "securityRisk": false,
} }
] ]
} }

View File

@ -50,7 +50,10 @@ describe('ApiService', () => {
describe('#sections', () => { describe('#sections', () => {
it('first subscriber should fetch sections', done => { it('first subscriber should fetch sections', done => {
const data = [{name: 'a', title: 'A', path: '', items: []}, {name: 'b', title: 'B', path: '', items: []}]; const data = [
{name: 'a', title: 'A', path: '', items: [], deprecated: false},
{name: 'b', title: 'B', path: '', items: [], deprecated: false},
];
service.sections.subscribe(sections => { service.sections.subscribe(sections => {
expect(sections).toEqual(data); expect(sections).toEqual(data);
@ -61,7 +64,10 @@ describe('ApiService', () => {
}); });
it('second subscriber should get previous sections and NOT trigger refetch', done => { it('second subscriber should get previous sections and NOT trigger refetch', done => {
const data = [{name: 'a', title: 'A', path: '', items: []}, {name: 'b', title: 'B', path: '', items: []}]; const data = [
{name: 'a', title: 'A', path: '', items: [], deprecated: false},
{name: 'b', title: 'B', path: '', items: [], deprecated: false},
];
let subscriptions = 0; let subscriptions = 0;
service.sections.subscribe(sections => { service.sections.subscribe(sections => {
@ -91,7 +97,10 @@ describe('ApiService', () => {
let call = 0; let call = 0;
let data = [{name: 'a', title: 'A', path: '', items: []}, {name: 'b', title: 'B', path: '', items: []}]; let data = [
{name: 'a', title: 'A', path: '', items: [], deprecated: false},
{name: 'b', title: 'B', path: '', items: [], deprecated: false},
];
service.sections.subscribe(sections => { service.sections.subscribe(sections => {
// called twice during this test // called twice during this test
@ -103,7 +112,7 @@ describe('ApiService', () => {
httpMock.expectOne({}).flush(data); httpMock.expectOne({}).flush(data);
// refresh/refetch // refresh/refetch
data = [{name: 'c', title: 'C', path: '', items: []}]; data = [{name: 'c', title: 'C', path: '', items: [], deprecated: false}];
service.fetchSections(); service.fetchSections();
httpMock.expectOne({}).flush(data); httpMock.expectOne({}).flush(data);

View File

@ -20,6 +20,7 @@ export interface ApiSection {
path: string; path: string;
name: string; name: string;
title: string; title: string;
deprecated: boolean;
items: ApiItem[]|null; items: ApiItem[]|null;
} }
@ -47,7 +48,12 @@ export class ApiService implements OnDestroy {
this._sections.subscribe(sections => this.logger.log('ApiService got API sections') ); this._sections.subscribe(sections => this.logger.log('ApiService got API sections') );
} }
return this._sections; return this._sections.pipe(tap(sections => {
sections.forEach(section => {
section.deprecated = !!section.items &&
section.items.every(item => item.stability === 'deprecated');
});
}));
}; };
constructor(private http: HttpClient, private logger: Logger) { } constructor(private http: HttpClient, private logger: Logger) { }

View File

@ -9,6 +9,7 @@ export interface SearchResult {
type: string; type: string;
titleWords: string; titleWords: string;
keywords: string; keywords: string;
deprecated: boolean;
} }
export interface SearchArea { export interface SearchArea {

View File

@ -9,14 +9,16 @@
<ul class="priority-pages" > <ul class="priority-pages" >
<li class="search-page" *ngFor="let page of area.priorityPages"> <li class="search-page" *ngFor="let page of area.priorityPages">
<a class="search-result-item" href="{{ page.path }}" (click)="onResultSelected(page, $event)"> <a class="search-result-item" href="{{ page.path }}" (click)="onResultSelected(page, $event)">
<span class="symbol {{page.type}}" *ngIf="area.name === 'api'"></span>{{ page.title }} <span class="symbol {{page.type}}" *ngIf="area.name === 'api'"></span>
<span [class.deprecated-api-item]="page.deprecated">{{ page.title }}</span>
</a> </a>
</li> </li>
</ul> </ul>
<ul> <ul>
<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, $event)"> <a class="search-result-item" href="{{ page.path }}" (click)="onResultSelected(page, $event)">
<span class="symbol {{page.type}}" *ngIf="area.name === 'api'"></span>{{ page.title }} <span class="symbol {{page.type}}" *ngIf="area.name === 'api'"></span>
<span [class.deprecated-api-item]="page.deprecated">{{ page.title }}</span>
</a> </a>
</li> </li>
</ul> </ul>

View File

@ -5,41 +5,59 @@ import { SearchResult } from 'app/search/interfaces';
import { SearchResultsComponent } from './search-results.component'; import { SearchResultsComponent } from './search-results.component';
describe('SearchResultsComponent', () => { describe('SearchResultsComponent', () => {
let component: SearchResultsComponent; let component: SearchResultsComponent;
let fixture: ComponentFixture<SearchResultsComponent>; let fixture: ComponentFixture<SearchResultsComponent>;
let guideA: SearchResult;
let apiD: SearchResult;
let guideB: SearchResult;
let guideAC: SearchResult;
let apiC: SearchResult;
let guideN: SearchResult;
let guideM: SearchResult;
let guideL: SearchResult;
let guideK: SearchResult;
let guideJ: SearchResult;
let guideI: SearchResult;
let guideH: SearchResult;
let guideG: SearchResult;
let guideF: SearchResult;
let guideE: SearchResult;
let standardResults: SearchResult[];
/** Get all text from component element */ /** Get all text from component element. */
function getText() { return fixture.debugElement.nativeElement.textContent; } function getText() { return fixture.debugElement.nativeElement.textContent; }
/** Get a full set of test results. "Take" what you need */ /** Pass the given search results to the component and trigger change detection. */
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 => ({...{ keywords: '', titleWords: '', type: '' }, ...r }));
return take === undefined ? results : results.slice(0, take);
}
function compareTitle(l: SearchResult, r: SearchResult) {
return l.title!.toUpperCase() > r.title!.toUpperCase() ? 1 : -1;
}
function setSearchResults(query: string, results: SearchResult[]) { function setSearchResults(query: string, results: SearchResult[]) {
component.searchResults = {query, results}; component.searchResults = {query, results};
component.ngOnChanges({}); component.ngOnChanges({});
fixture.detectChanges(); fixture.detectChanges();
} }
/** Get a full set of test results. "Take" what you need */
beforeEach(() => {
apiD = { path: 'api/d', title: 'API D', deprecated: false, keywords: '', titleWords: '', type: '' };
apiC = { path: 'api/c', title: 'API C', deprecated: false, keywords: '', titleWords: '', type: '' };
guideA = { path: 'guide/a', title: 'Guide A', deprecated: false, keywords: '', titleWords: '', type: '' };
guideB = { path: 'guide/b', title: 'Guide B', deprecated: false, keywords: '', titleWords: '', type: '' };
guideAC = { path: 'guide/a/c', title: 'Guide A - C', deprecated: false, keywords: '', titleWords: '', type: '' };
guideE = { path: 'guide/e', title: 'Guide e', deprecated: false, keywords: '', titleWords: '', type: '' };
guideF = { path: 'guide/f', title: 'Guide f', deprecated: false, keywords: '', titleWords: '', type: '' };
guideG = { path: 'guide/g', title: 'Guide g', deprecated: false, keywords: '', titleWords: '', type: '' };
guideH = { path: 'guide/h', title: 'Guide h', deprecated: false, keywords: '', titleWords: '', type: '' };
guideI = { path: 'guide/i', title: 'Guide i', deprecated: false, keywords: '', titleWords: '', type: '' };
guideJ = { path: 'guide/j', title: 'Guide j', deprecated: false, keywords: '', titleWords: '', type: '' };
guideK = { path: 'guide/k', title: 'Guide k', deprecated: false, keywords: '', titleWords: '', type: '' };
guideL = { path: 'guide/l', title: 'Guide l', deprecated: false, keywords: '', titleWords: '', type: '' };
guideM = { path: 'guide/m', title: 'Guide m', deprecated: false, keywords: '', titleWords: '', type: '' };
guideN = { path: 'guide/n', title: 'Guide n', deprecated: false, keywords: '', titleWords: '', type: '' };
standardResults = [
guideA, apiD, guideB, guideAC, apiC, guideN, guideM, guideL, guideK, guideJ, guideI, guideH, guideG, guideF, guideE,
];
});
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ SearchResultsComponent ] declarations: [ SearchResultsComponent ]
@ -53,48 +71,42 @@ 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', () => {
setSearchResults('', getTestResults(3)); setSearchResults('', [guideA, apiD, guideB]);
expect(component.searchAreas).toEqual([ expect(component.searchAreas).toEqual([
{ name: 'api', priorityPages: [ { name: 'api', priorityPages: [apiD], pages: [] },
{ path: 'api/d', title: 'API D', type: '', keywords: '', titleWords: '' } { name: 'guide', priorityPages: [guideA, guideB], pages: [] }
], pages: [] },
{ name: 'guide', priorityPages: [
{ path: 'guide/a', title: 'Guide A', type: '', keywords: '', titleWords: '' },
{ path: 'guide/b', title: 'Guide B', type: '', keywords: '', titleWords: '' },
], pages: [] }
]); ]);
}); });
it('should special case results that are top level folders', () => { it('should special case results that are top level folders', () => {
setSearchResults('', [ setSearchResults('', [
{ path: 'tutorial', title: 'Tutorial index', type: '', keywords: '', titleWords: '' }, { path: 'tutorial', title: 'Tutorial index', type: '', keywords: '', titleWords: '', deprecated: false },
{ path: 'tutorial/toh-pt1', title: 'Tutorial - part 1', type: '', keywords: '', titleWords: '' }, { path: 'tutorial/toh-pt1', title: 'Tutorial - part 1', type: '', keywords: '', titleWords: '', deprecated: false },
]); ]);
expect(component.searchAreas).toEqual([ expect(component.searchAreas).toEqual([
{ name: 'tutorial', priorityPages: [ { name: 'tutorial', priorityPages: [
{ path: 'tutorial', title: 'Tutorial index', type: '', keywords: '', titleWords: '' }, { path: 'tutorial', title: 'Tutorial index', type: '', keywords: '', titleWords: '', deprecated: false },
{ path: 'tutorial/toh-pt1', title: 'Tutorial - part 1', type: '', keywords: '', titleWords: '' }, { path: 'tutorial/toh-pt1', title: 'Tutorial - part 1', type: '', keywords: '', titleWords: '', deprecated: false },
], pages: [] } ], pages: [] }
]); ]);
}); });
it('should put first 5 results for each area into priorityPages', () => { it('should put, at most, the first 5 results for each area into priorityPages, not sorted', () => {
const results = getTestResults(); setSearchResults('', standardResults);
setSearchResults('', results); expect(component.searchAreas[0].priorityPages).toEqual([apiD, apiC]);
expect(component.searchAreas[0].priorityPages).toEqual(results.filter(p => p.path.startsWith('api')).slice(0, 5)); expect(component.searchAreas[1].priorityPages).toEqual([guideA, guideB, guideAC, guideN, guideM]);
expect(component.searchAreas[1].priorityPages).toEqual(results.filter(p => p.path.startsWith('guide')).slice(0, 5));
}); });
it('should put the nonPriorityPages into the pages array, sorted by title', () => { it('should put the nonPriorityPages into the pages array, sorted by title', () => {
const results = getTestResults(); setSearchResults('', standardResults);
setSearchResults('', results);
expect(component.searchAreas[0].pages).toEqual([]); expect(component.searchAreas[0].pages).toEqual([]);
expect(component.searchAreas[1].pages).toEqual(results.filter(p => p.path.startsWith('guide')).slice(5).sort(compareTitle)); expect(component.searchAreas[1].pages).toEqual([
guideE, guideF, guideG, guideH, guideI, guideJ, guideK, guideL
]);
}); });
it('should put a total count in the header of each area of search results', () => { it('should put a total count in the header of each area of search results', () => {
const results = getTestResults(); setSearchResults('', standardResults);
setSearchResults('', results);
fixture.detectChanges(); fixture.detectChanges();
const headers = fixture.debugElement.queryAll(By.css('h3')); const headers = fixture.debugElement.queryAll(By.css('h3'));
expect(headers.length).toEqual(2); expect(headers.length).toEqual(2);
@ -104,26 +116,55 @@ describe('SearchResultsComponent', () => {
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: '', deprecated: false }
]; ];
setSearchResults('', results); setSearchResults('', results);
expect(component.searchAreas).toEqual([ expect(component.searchAreas).toEqual([
{ name: 'other', priorityPages: [ { name: 'other', priorityPages: [
{ path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '' } { path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '', deprecated: false }
], pages: [] } ], pages: [] }
]); ]);
}); });
it('should omit search results with no title', () => { it('should omit search results with no title', () => {
const results = [ const results = [
{ path: 'news', title: '', type: 'marketing', keywords: '', titleWords: '' } { path: 'news', title: '', type: 'marketing', keywords: '', titleWords: '', deprecated: false }
]; ];
setSearchResults('something', results); setSearchResults('something', results);
expect(component.searchAreas).toEqual([]); expect(component.searchAreas).toEqual([]);
}); });
describe('when there are deprecated items', () => {
beforeEach(() => {
apiD.deprecated = true;
guideAC.deprecated = true;
guideJ.deprecated = true;
guideE.deprecated = true;
setSearchResults('something', standardResults);
});
it('should include deprecated items in priority pages unless there are fewer than 5 non-deprecated priority pages', () => {
// Priority pages do not include deprecated items:
expect(component.searchAreas[1].priorityPages).not.toContain(guideAC);
expect(component.searchAreas[1].priorityPages).not.toContain(guideJ);
// Except where there are too few priority pages:
expect(component.searchAreas[0].priorityPages).toContain(apiD);
});
it('should move the non-priority deprecated pages to the bottom of the pages list, unsorted', () => {
// Bottom pages are the deprecated ones (in original order)
expect(component.searchAreas[1].pages.slice(-3)).toEqual([guideAC, guideJ, guideE]);
});
it('should sort the non-deprecated, non-priority pages by title', () => {
// The rest of the pages are non-deprecated, sorted by title
expect(component.searchAreas[1].pages.slice(0, -3)).toEqual([
guideF, guideG, guideH, guideI, guideK,
]);
});
});
it('should display "Searching ..." while waiting for search results', () => { it('should display "Searching ..." while waiting for search results', () => {
fixture.detectChanges(); fixture.detectChanges();
expect(getText()).toContain('Searching ...'); expect(getText()).toContain('Searching ...');
@ -138,7 +179,7 @@ describe('SearchResultsComponent', () => {
component.resultSelected.subscribe((result: SearchResult) => selected = result); component.resultSelected.subscribe((result: SearchResult) => selected = result);
selected = null; selected = null;
searchResult = { path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '' }; searchResult = { path: 'news', title: 'News', type: 'marketing', keywords: '', titleWords: '', deprecated: false };
setSearchResults('something', [searchResult]); setSearchResults('something', [searchResult]);
fixture.detectChanges(); fixture.detectChanges();

View File

@ -53,12 +53,12 @@ export class SearchResultsComponent implements OnChanges {
}); });
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 => {
let pages: SearchResult[] = searchAreaMap[name]; const {priorityPages, pages, deprecated} = splitPages(searchAreaMap[name]);
return {
// Extract the top 5 most relevant results as priorityPages name,
const priorityPages = pages.splice(0, 5); priorityPages,
pages = pages.sort(compareResults); pages: pages.concat(deprecated),
return { name, pages, priorityPages }; };
}); });
} }
@ -72,6 +72,30 @@ export class SearchResultsComponent implements OnChanges {
} }
} }
function splitPages(allPages: SearchResult[]) {
const priorityPages: SearchResult[] = [];
const pages: SearchResult[] = [];
const deprecated: SearchResult[] = [];
allPages.forEach(page => {
if (page.deprecated) {
deprecated.push(page);
} else if (priorityPages.length < 5) {
priorityPages.push(page);
} else {
pages.push(page);
}
});
while (priorityPages.length < 5 && pages.length) {
priorityPages.push(pages.shift()!);
}
while (priorityPages.length < 5 && deprecated.length) {
priorityPages.push(deprecated.shift()!);
}
pages.sort(compareResults);
return { priorityPages, pages, deprecated };
}
function compareResults(l: SearchResult, r: SearchResult) { function compareResults(l: SearchResult, r: SearchResult) {
return l.title.toUpperCase() > r.title.toUpperCase() ? 1 : -1; return l.title.toUpperCase() > r.title.toUpperCase() ? 1 : -1;
} }

View File

@ -198,6 +198,10 @@ aio-api-list {
background: $blue-grey-50; background: $blue-grey-50;
color: $blue-500; color: $blue-500;
} }
&.deprecated-api-item {
text-decoration: line-through;
}
} }
} }
} }

View File

@ -114,3 +114,7 @@
padding: 0; padding: 0;
} }
} }
.deprecated-api-item {
text-decoration: line-through;
}

View File

@ -49,11 +49,7 @@ label.raised, .api-header label {
&.api-status-label { &.api-status-label {
background-color: $mediumgray; background-color: $mediumgray;
&.security { &.deprecated, &.security, &.impure-pipe {
background-color: $brightred;
}
&.impure-pipe {
background-color: $brightred; background-color: $brightred;
} }
} }

View File

@ -23,6 +23,7 @@ $mediumgray: #6e6e6e;
$darkgray: #333; $darkgray: #333;
$black: #0A1014; $black: #0A1014;
$orange: #FF9800; $orange: #FF9800;
$darkorange: #940;
$anti-pattern: $brightred; $anti-pattern: $brightred;
// API & CODE COLORS // API & CODE COLORS
@ -95,7 +96,7 @@ $api-symbols: (
), ),
ngmodule: ( ngmodule: (
content: 'M', content: 'M',
background: $darkred background: $darkorange
), ),
package: ( package: (
content: 'Pk', content: 'Pk',

View File

@ -175,7 +175,7 @@ describe('site App', function() {
page.navigateTo('http/router'); page.navigateTo('http/router');
const results = page.getSearchResults(); const results = page.getSearchResults();
expect(results).toContain('Http'); expect(results).toContain('HttpRequest');
expect(results).toContain('Router'); expect(results).toContain('Router');
}); });
}); });

View File

@ -2,8 +2,8 @@ const { dirname } = require('canonical-path');
module.exports = function processPackages() { module.exports = function processPackages() {
return { return {
$runAfter: ['extractDecoratedClassesProcessor'], $runAfter: ['extractDecoratedClassesProcessor', 'computeStability'],
$runBefore: ['computing-ids'], $runBefore: ['computing-ids', 'generateKeywordsProcessor'],
$process(docs) { $process(docs) {
const packageContentFiles = {}; const packageContentFiles = {};
const packageMap = {}; const packageMap = {};
@ -34,6 +34,9 @@ module.exports = function processPackages() {
doc.directives = doc.exports.filter(doc => doc.docType === 'directive'); doc.directives = doc.exports.filter(doc => doc.docType === 'directive');
doc.pipes = doc.exports.filter(doc => doc.docType === 'pipe'); doc.pipes = doc.exports.filter(doc => doc.docType === 'pipe');
doc.types = doc.exports.filter(doc => doc.docType === 'type-alias' || doc.docType === 'const'); doc.types = doc.exports.filter(doc => doc.docType === 'type-alias' || doc.docType === 'const');
if (doc.exports.every(doc => !!doc.deprecated)) {
doc.deprecated = 'all exports of this entry point are deprecated.';
}
} }
// Copy over docs from the PACKAGE.md file that is used to document packages // Copy over docs from the PACKAGE.md file that is used to document packages
@ -58,6 +61,12 @@ module.exports = function processPackages() {
} }
}); });
// Update package deprecation status (compared to entry point status)
Object.keys(packageMap).forEach(key => {
const pkg = packageMap[key];
pkg.primary.packageDeprecated = pkg.primary.deprecated !== undefined && pkg.secondary.every(entryPoint => entryPoint.deprecated !== undefined);
});
return docs; return docs;
} }
}; };

View File

@ -9,8 +9,8 @@ describe('processPackages processor', () => {
const injector = dgeni.configureInjector(); const injector = dgeni.configureInjector();
const processor = injector.get('processPackages'); const processor = injector.get('processPackages');
expect(processor.$process).toBeDefined(); expect(processor.$process).toBeDefined();
expect(processor.$runAfter).toEqual(['extractDecoratedClassesProcessor']); expect(processor.$runAfter).toEqual(['extractDecoratedClassesProcessor', 'computeStability']);
expect(processor.$runBefore).toEqual(['computing-ids']); expect(processor.$runBefore).toEqual(['computing-ids', 'generateKeywordsProcessor']);
}); });
it('should filter out any `package-content` docs from the collection', () => { it('should filter out any `package-content` docs from the collection', () => {
@ -177,4 +177,110 @@ describe('processPackages processor', () => {
{ docType: 'const', id: 'const-2' }, { docType: 'const', id: 'const-2' },
]); ]);
}); });
it('should compute the deprecated status of each entry point', () => {
const docs = [
{
fileInfo: { filePath: 'some/package-1/index' },
docType: 'module',
id: 'package-1',
exports: [
{ docType: 'class', id: 'class-1', deprecated: true },
]
},
{
fileInfo: { filePath: 'some/package-1/sub-1index' },
docType: 'module',
id: 'package-1/sub-1',
exports: [
{ docType: 'class', id: 'class-2', deprecated: true },
]
},
{
fileInfo: { filePath: 'some/package-2/index' },
docType: 'module',
id: 'package-2',
exports: [
{ docType: 'class', id: 'class-3' },
{ docType: 'class', id: 'class-4', deprecated: true },
]
},
{
fileInfo: { filePath: 'some/package-3/index' },
docType: 'module',
id: 'package-3',
exports: [
{ docType: 'class', id: 'class-5' },
{ docType: 'class', id: 'class-6' },
]
},
];
const processor = processorFactory();
const newDocs = processor.$process(docs);
expect(newDocs[0].deprecated).toBeTruthy();
expect(newDocs[1].deprecated).toBeTruthy();
expect(newDocs[2].deprecated).toBeUndefined();
expect(newDocs[3].deprecated).toBeUndefined();
});
it('should compute the deprecated status of packages', () => {
const docs = [
{
fileInfo: { filePath: 'some/package-1/index' },
docType: 'module',
id: 'package-1',
exports: [
{ docType: 'class', id: 'class-1', deprecated: true },
]
},
{
fileInfo: { filePath: 'some/package-1/sub-1index' },
docType: 'module',
id: 'package-1/sub-1',
exports: [
{ docType: 'class', id: 'class-2', deprecated: true },
]
},
{
fileInfo: { filePath: 'some/package-2/index' },
docType: 'module',
id: 'package-2',
exports: [
{ docType: 'class', id: 'class-3', deprecated: true },
]
},
{
fileInfo: { filePath: 'some/package-2/sub-1index' },
docType: 'module',
id: 'package-2/sub-1',
exports: [
{ docType: 'class', id: 'class-4', deprecated: false },
]
},
{
fileInfo: { filePath: 'some/package-3/index' },
docType: 'module',
id: 'package-3',
exports: [
{ docType: 'class', id: 'class-5', deprecated: false },
]
},
{
fileInfo: { filePath: 'some/package-3/sub-1index' },
docType: 'module',
id: 'package-3/sub-1',
exports: [
{ docType: 'class', id: 'class-6', deprecated: true },
]
},
];
const processor = processorFactory();
const newDocs = processor.$process(docs);
expect(newDocs[0].packageDeprecated).toBe(true);
expect(newDocs[1].packageDeprecated).toBeUndefined();
expect(newDocs[2].packageDeprecated).toBe(false);
expect(newDocs[3].packageDeprecated).toBeUndefined();
expect(newDocs[4].packageDeprecated).toBe(false);
});
}); });

View File

@ -140,7 +140,8 @@ module.exports = function generateKeywordsProcessor(log, readFilesProcessor) {
return Object.assign({ return Object.assign({
path: page.path, path: page.path,
title: page.searchTitle, title: page.searchTitle,
type: page.docType type: page.docType,
deprecated: !!page.deprecated,
}, page.searchTerms); }, page.searchTerms);
}); });

View File

@ -166,7 +166,8 @@ describe('generateKeywords processor', () => {
'titleWords':'someclass', 'titleWords':'someclass',
'headingWords':'heading some someclass', 'headingWords':'heading some someclass',
'keywords':'api class documentation for is someclass the', 'keywords':'api class documentation for is someclass the',
'members':'' 'members':'',
'deprecated': false,
}] }]
); );
}); });

View File

@ -16,12 +16,15 @@
</script> </script>
{% for crumb in doc.breadCrumbs %}{% if not loop.last %} {$ breadcrumbDelimiter() $} {% if crumb.path %}<a href="{$ crumb.path $}">{$ crumb.text $}</a>{% else %}{$ crumb.text $}{% endif %}{% endif %}{% endfor %} {% for crumb in doc.breadCrumbs %}{% if not loop.last %} {$ breadcrumbDelimiter() $} {% if crumb.path %}<a href="{$ crumb.path $}">{$ crumb.text $}</a>{% else %}{$ crumb.text $}{% endif %}{% endif %}{% endfor %}
</div> </div>
{% block header %}
<header class="api-header"> <header class="api-header">
<h1>{$ doc.name $}</h1> <h1>{$ doc.name $}</h1>
<label class="api-type-label {$ doc.docType $}">{$ doc.docType $}</label> <label class="api-type-label {$ doc.docType $}">{$ doc.docType $}</label>
{% if doc.deprecated !== undefined %}<label class="api-status-label deprecated">deprecated</label>{% endif %}
{% if doc.security !== undefined %}<label class="api-status-label security">security</label>{% endif %} {% if doc.security !== undefined %}<label class="api-status-label security">security</label>{% endif %}
{% if doc.pipeOptions.pure === 'false' %}<label class="api-status-label impure-pipe">impure</label>{% endif %} {% if doc.pipeOptions.pure === 'false' %}<label class="api-status-label impure-pipe">impure</label>{% endif %}
</header> </header>
{% endblock %}
<aio-toc class="embedded"></aio-toc> <aio-toc class="embedded"></aio-toc>
<div class="api-body"> <div class="api-body">

View File

@ -1,6 +1,5 @@
{% if doc.deprecated %} {% if doc.deprecated !== undefined %}
<section class="deprecated"> <section class="deprecated">
<h2>Deprecation notes</h2> {$ ('**Deprecated:** ' + doc.deprecated) | marked $}
{$ doc.deprecated | marked $}
</section> </section>
{% endif %} {% endif %}

View File

@ -39,10 +39,11 @@
<tr> <tr>
<td> <td>
<a href="{$ item.path $}"> <a href="{$ item.path $}">
<code-example language="ts" hideCopy="true" linenums="false" class="no-box">{$ item.name | escape $}</code-example> <code-example language="ts" hideCopy="true" linenums="false" class="no-box{% if item.deprecated !== undefined %} deprecated-api-item{% endif %}">{$ item.name | escape $}</code-example>
</a> </a>
</td> </td>
<td> <td>
{% if item.deprecated !== undefined %}{$ ('**Deprecated:** ' + item.deprecated) | marked $}{% endif %}
{$ item.shortDescription | marked $} {$ item.shortDescription | marked $}
</td> </td>
</tr> </tr>

View File

@ -9,8 +9,13 @@
{% for item in filteredItems %} {% for item in filteredItems %}
<tr> <tr>
<td><code class="code-anchor"> <td><code class="code-anchor">
<a href="{$ overridePath or item.path $}">{$ item.name $}</a></code></td> <a href="{$ overridePath or item.path $}"
<td>{% if item.shortDescription %}{$ item.shortDescription | marked $}{% endif %}</td> {%- if item.deprecated != undefined %} class="deprecated-api-item"{% endif %}>{$ item.name $}</a></code>
</td>
<td>
{% if item.deprecated !== undefined %}{$ ('**Deprecated:** ' + item.deprecated) | marked $}{% endif %}
{% if item.shortDescription %}{$ item.shortDescription | marked $}{% endif %}
</td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
@ -18,8 +23,17 @@
{% endif %} {% endif %}
{% endmacro %} {% endmacro %}
{% block header %}
<header class="api-header">
<h1>{$ doc.name $}</h1>
<label class="api-type-label {$ doc.docType $}">{$ doc.docType $}</label>
{% if doc.packageDeprecated or (not doc.isPrimaryPackage and doc.deprecated !== undefined) %}<label class="api-status-label deprecated">deprecated</label>{% endif %}
{% if doc.security !== undefined %}<label class="api-status-label security">security</label>{% endif %}
{% if doc.pipeOptions.pure === 'false' %}<label class="api-status-label impure-pipe">impure</label>{% endif %}
</header>
{% endblock %}
{% block body -%} {% block body -%}
{% include "includes/deprecation.html" %}
{$ doc.shortDescription | marked $} {$ doc.shortDescription | marked $}
{% if doc.description %}{$ doc.description | marked $}{% endif %} {% if doc.description %}{$ doc.description | marked $}{% endif %}
@ -32,6 +46,7 @@
{% endif %} {% endif %}
<h2>{% if doc.isPrimaryPackage %}Primary entry{% else %}Entry{% endif %} point exports</h2> <h2>{% if doc.isPrimaryPackage %}Primary entry{% else %}Entry{% endif %} point exports</h2>
{% include "includes/deprecation.html" %}
{$ listItems(doc.ngmodules, 'NgModules') $} {$ listItems(doc.ngmodules, 'NgModules') $}
{$ listItems(doc.classes, 'Classes') $} {$ listItems(doc.classes, 'Classes') $}
{$ listItems(doc.decorators, 'Decorators') $} {$ listItems(doc.decorators, 'Decorators') $}