refactor(docs-infra): prepare DocumentService to handle new disambiguated URLs (#42509)
A subsequent commit is going to change disambiguated URLs. This commit prepares the AIO application to attempt the new URLs if the old URLs fail. This will help to mitigate problems that may occur during the period between deployment of the new version and the service-worker not being updated. PR Close #42509
This commit is contained in:
parent
c44ab4f6da
commit
f788e6b8a6
|
@ -67,6 +67,30 @@ describe('DocumentService', () => {
|
||||||
expect(latestDocument).toEqual(doc1);
|
expect(latestDocument).toEqual(doc1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// HACK: PREPARE FOR CHANGING TO CASE-INSENSITIVE URLS
|
||||||
|
it('should attempt disambiguated document paths if the document is not found on the server', () => {
|
||||||
|
let currentDocument: DocumentContents|undefined;
|
||||||
|
const notFoundDoc = { id: FILE_NOT_FOUND_ID, contents: '<h1>Page Not Found</h1>' };
|
||||||
|
const { docService, logger } = getServices('missing/Doc-1');
|
||||||
|
docService.currentDocument.subscribe(doc => currentDocument = doc);
|
||||||
|
|
||||||
|
// Initial request return 404.
|
||||||
|
httpMock.expectOne({url: 'generated/docs/missing/Doc-1.json'}).flush(null, {status: 404, statusText: 'NOT FOUND'});
|
||||||
|
httpMock.expectOne({url: 'generated/docs/missing/d_oc-1.json'}).flush(null, {status: 404, statusText: 'NOT FOUND'});
|
||||||
|
httpMock.expectOne({url: 'generated/docs/missing/d_oc.json'}).flush(null, {status: 404, statusText: 'NOT FOUND'});
|
||||||
|
expect(logger.output.error).toEqual([
|
||||||
|
[jasmine.any(Error)]
|
||||||
|
]);
|
||||||
|
expect(logger.output.error[0][0].message).toEqual(`Document file not found at 'missing/Doc-1'`);
|
||||||
|
|
||||||
|
// Subsequent request for not-found document.
|
||||||
|
logger.output.error = [];
|
||||||
|
httpMock.expectOne(CONTENT_URL_PREFIX + 'file-not-found.json').flush(notFoundDoc);
|
||||||
|
expect(logger.output.error).toEqual([]); // does not report repeated errors
|
||||||
|
expect(currentDocument).toEqual(notFoundDoc);
|
||||||
|
});
|
||||||
|
// END HACK: PREPARE FOR CHANGING TO CASE-INSENSITIVE URLS
|
||||||
|
|
||||||
it('should emit the not-found document if the document is not found on the server', () => {
|
it('should emit the not-found document if the document is not found on the server', () => {
|
||||||
let currentDocument: DocumentContents|undefined;
|
let currentDocument: DocumentContents|undefined;
|
||||||
const notFoundDoc = { id: FILE_NOT_FOUND_ID, contents: '<h1>Page Not Found</h1>' };
|
const notFoundDoc = { id: FILE_NOT_FOUND_ID, contents: '<h1>Page Not Found</h1>' };
|
||||||
|
@ -83,7 +107,7 @@ describe('DocumentService', () => {
|
||||||
// Subsequent request for not-found document.
|
// Subsequent request for not-found document.
|
||||||
logger.output.error = [];
|
logger.output.error = [];
|
||||||
httpMock.expectOne(CONTENT_URL_PREFIX + 'file-not-found.json').flush(notFoundDoc);
|
httpMock.expectOne(CONTENT_URL_PREFIX + 'file-not-found.json').flush(notFoundDoc);
|
||||||
expect(logger.output.error).toEqual([]); // does not report repeate errors
|
expect(logger.output.error).toEqual([]); // does not report repeated errors
|
||||||
expect(currentDocument).toEqual(notFoundDoc);
|
expect(currentDocument).toEqual(notFoundDoc);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
|
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
|
||||||
|
|
||||||
import { AsyncSubject, Observable, of } from 'rxjs';
|
import { AsyncSubject, Observable, of, throwError } from 'rxjs';
|
||||||
import { catchError, switchMap, tap } from 'rxjs/operators';
|
import { catchError, switchMap, tap } from 'rxjs/operators';
|
||||||
|
|
||||||
import { DocumentContents } from './document-contents';
|
import { DocumentContents } from './document-contents';
|
||||||
|
@ -66,6 +66,20 @@ export class DocumentService {
|
||||||
throw Error('Invalid data');
|
throw Error('Invalid data');
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
// HACK: PREPARE FOR CHANGING TO CASE-INSENSITIVE URLS
|
||||||
|
catchError((error: HttpErrorResponse) => {
|
||||||
|
const encodedPath = encodeToLowercase(requestPath);
|
||||||
|
return error.status === 404 && encodedPath !== requestPath ?
|
||||||
|
this.http.get<DocumentContents>(encodedPath) :
|
||||||
|
throwError(error);
|
||||||
|
}),
|
||||||
|
catchError((error: HttpErrorResponse) => {
|
||||||
|
const disambiguatedPath = convertDisambiguatedPath(requestPath);
|
||||||
|
return error.status === 404 && disambiguatedPath !== requestPath ?
|
||||||
|
this.http.get<DocumentContents>(disambiguatedPath) :
|
||||||
|
throwError(error);
|
||||||
|
}),
|
||||||
|
// END HACK: PREPARE FOR CHANGING TO CASE-INSENSITIVE URLS
|
||||||
catchError((error: HttpErrorResponse) => {
|
catchError((error: HttpErrorResponse) => {
|
||||||
return error.status === 404 ? this.getFileNotFoundDoc(id) : this.getErrorDoc(id, error);
|
return error.status === 404 ? this.getFileNotFoundDoc(id) : this.getErrorDoc(id, error);
|
||||||
}),
|
}),
|
||||||
|
@ -97,3 +111,30 @@ export class DocumentService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode the path to the content in a deterministic, reversible, case-insensitive form.
|
||||||
|
*
|
||||||
|
* This avoids collisions on case-insensitive file-systems.
|
||||||
|
*
|
||||||
|
* - Escape underscores (_) to double underscores (__).
|
||||||
|
* - Convert all uppercase letters to lowercase followed by an underscore.
|
||||||
|
*/
|
||||||
|
function encodeToLowercase(str: string): string {
|
||||||
|
return str.replace(/[A-Z_]/g, char => char.toLowerCase() + '_');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A temporary function to deal with a future change to URL disambiguation.
|
||||||
|
*
|
||||||
|
* Currently there are disambiguated URLs such as `INJECTOR-0` and `Injector-1`, which
|
||||||
|
* will attempt to load their document contents from `injector-0.json` and `injector-1.json`
|
||||||
|
* respectively. In a future version of the AIO app, the disambiguation will be changed to
|
||||||
|
* escape the upper-case characters instead.
|
||||||
|
*
|
||||||
|
* This function will be called if the current AIO is trying to request documents from a
|
||||||
|
* server that has been updated to use the new disambiguated URLs.
|
||||||
|
*/
|
||||||
|
function convertDisambiguatedPath(str: string): string {
|
||||||
|
return encodeToLowercase(str.replace(/-\d+\.json$/, '.json'));
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"master": {
|
"master": {
|
||||||
"uncompressed": {
|
"uncompressed": {
|
||||||
"runtime-es2015": 4619,
|
"runtime-es2015": 4619,
|
||||||
"main-es2015": 453172,
|
"main-es2015": 453855,
|
||||||
"polyfills-es2015": 55210
|
"polyfills-es2015": 55210
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
"master": {
|
"master": {
|
||||||
"uncompressed": {
|
"uncompressed": {
|
||||||
"runtime-es2015": 4619,
|
"runtime-es2015": 4619,
|
||||||
"main-es2015": 453394,
|
"main-es2015": 453981,
|
||||||
"polyfills-es2015": 55291
|
"polyfills-es2015": 55291
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue