From ea062ecbb6e36d559c5ad6311dbf877ae6158bac Mon Sep 17 00:00:00 2001 From: Lukas Spirig Date: Sun, 19 Jan 2020 00:59:14 +0100 Subject: [PATCH] refactor: improve targets handling in frontend --- builders/t9n/server/constants.ts | 1 + builders/t9n/server/responses/index.ts | 1 + .../t9n/server/responses/root-response.ts | 9 +--- .../t9n/server/responses/targets-response.ts | 21 +++++++++ .../t9n/server/translation-server.spec.ts | 9 +++- builders/t9n/server/translation-server.ts | 7 ++- src/app/core/translation.service.ts | 47 ++++++++++--------- src/models/root-response.ts | 1 - src/models/targets-response.ts | 5 ++ 9 files changed, 68 insertions(+), 33 deletions(-) create mode 100644 builders/t9n/server/responses/targets-response.ts create mode 100644 src/models/targets-response.ts diff --git a/builders/t9n/server/constants.ts b/builders/t9n/server/constants.ts index d3dcd46..8f7e501 100644 --- a/builders/t9n/server/constants.ts +++ b/builders/t9n/server/constants.ts @@ -1,6 +1,7 @@ export const ROOT_ROUTE = 'root'; export const SOURCE_UNITS_ROUTE = 'sourceUnits'; export const SOURCE_UNIT_ROUTE = 'sourceUnit'; +export const TARGETS_ROUTE = 'targets'; export const TARGET_ROUTE = 'target'; export const TARGET_UNITS_ROUTE = 'targetUnits'; export const TARGET_UNIT_ROUTE = 'targetUnit'; diff --git a/builders/t9n/server/responses/index.ts b/builders/t9n/server/responses/index.ts index 43989ca..c1ad3b5 100644 --- a/builders/t9n/server/responses/index.ts +++ b/builders/t9n/server/responses/index.ts @@ -5,4 +5,5 @@ export * from './root-response'; export * from './source-unit-response'; export * from './source-unit-with-translations-response'; export * from './target-response'; +export * from './targets-response'; export * from './target-unit-response'; diff --git a/builders/t9n/server/responses/root-response.ts b/builders/t9n/server/responses/root-response.ts index 78bb007..5952560 100644 --- a/builders/t9n/server/responses/root-response.ts +++ b/builders/t9n/server/responses/root-response.ts @@ -1,5 +1,5 @@ import { TranslationContext } from '../../translation'; -import { ROOT_ROUTE, ROUTE_TEMPLATE, SOURCE_UNITS_ROUTE, TARGET_ROUTE } from '../constants'; +import { ROOT_ROUTE, SOURCE_UNITS_ROUTE, TARGETS_ROUTE } from '../constants'; import { Hal, HalLink, Links } from '../hal'; import { UrlFactory } from '../url-factory'; @@ -7,7 +7,6 @@ export class RootResponse implements Hal { project: string; sourceFile: string; sourceLanguage: string; - languages: string[]; unitCount: number; _links?: { [key: string]: HalLink }; _embedded?: { [key: string]: unknown }; @@ -16,15 +15,11 @@ export class RootResponse implements Hal { this.project = context.project; this.sourceFile = context.source.file; this.sourceLanguage = context.source.language; - this.languages = context.languages; this.unitCount = context.source.units.length; this._links = new Links() .self(urlFactory(ROOT_ROUTE)) + .href('targets', urlFactory(TARGETS_ROUTE)) .href('sourceUnits', urlFactory(SOURCE_UNITS_ROUTE)) - .templatedHref( - 'targets', - urlFactory(TARGET_ROUTE, { language: ROUTE_TEMPLATE }).replace(ROUTE_TEMPLATE, '{language}') - ) .build(); } } diff --git a/builders/t9n/server/responses/targets-response.ts b/builders/t9n/server/responses/targets-response.ts new file mode 100644 index 0000000..2130ebc --- /dev/null +++ b/builders/t9n/server/responses/targets-response.ts @@ -0,0 +1,21 @@ +import { TranslationContext } from '../../translation'; +import { ROUTE_TEMPLATE, TARGET_ROUTE, TARGETS_ROUTE } from '../constants'; +import { Hal, HalLink, Links } from '../hal'; +import { UrlFactory } from '../url-factory'; + +export class TargetsResponse implements Hal { + languages: string[]; + _links?: { [key: string]: HalLink }; + _embedded?: { [key: string]: unknown }; + + constructor(context: TranslationContext, urlFactory: UrlFactory) { + this.languages = context.languages; + this._links = new Links() + .self(urlFactory(TARGETS_ROUTE)) + .templatedHref( + 'target', + urlFactory(TARGET_ROUTE, { language: ROUTE_TEMPLATE }).replace(ROUTE_TEMPLATE, '{language}') + ) + .build(); + } +} diff --git a/builders/t9n/server/translation-server.spec.ts b/builders/t9n/server/translation-server.spec.ts index a76c6af..c66dd15 100644 --- a/builders/t9n/server/translation-server.spec.ts +++ b/builders/t9n/server/translation-server.spec.ts @@ -13,6 +13,7 @@ import { RootResponse, SourceUnitResponse, TargetResponse, + TargetsResponse, TargetUnitResponse } from './responses'; import { TranslationServer } from './translation-server'; @@ -64,7 +65,6 @@ describe('TranslationServer', () => { const response = await request(server.callback()).get('/api'); expect(response.status).toEqual(200); const root = response.body as RootResponse; - expect(root.languages).toEqual(context.languages); expect(root.project).toEqual(project); expect(root.sourceFile).toEqual(sourceFile); expect(root.sourceLanguage).toEqual(context.source.language); @@ -94,6 +94,13 @@ describe('TranslationServer', () => { expect(unitResponse.source).toEqual(unit.source); }); + it('GET /api/targets', async () => { + const response = await request(server.callback()).get('/api/targets'); + expect(response.status).toEqual(200); + const root = response.body as TargetsResponse; + expect(root.languages).toEqual(context.languages); + }); + it('GET /api/targets/:non-existant-target => 404', async () => { const response = await request(server.callback()).get('/api/targets/non-existant-target'); expect(response.status).toEqual(404); diff --git a/builders/t9n/server/translation-server.ts b/builders/t9n/server/translation-server.ts index 3b30a59..61804c4 100644 --- a/builders/t9n/server/translation-server.ts +++ b/builders/t9n/server/translation-server.ts @@ -18,7 +18,8 @@ import { SOURCE_UNITS_ROUTE, TARGET_ROUTE, TARGET_UNIT_ROUTE, - TARGET_UNITS_ROUTE + TARGET_UNITS_ROUTE, + TARGETS_ROUTE } from './constants'; import { OrphanResponse, @@ -26,6 +27,7 @@ import { RootResponse, SourceUnitWithTranslationsResponse, TargetResponse, + TargetsResponse, TargetUnitResponse } from './responses'; @@ -78,6 +80,9 @@ export class TranslationServer extends Koa { + ctx.body = new TargetsResponse(this._context, toUrlFactory(ctx)); + }) .get(TARGET_ROUTE, '/targets/:language', ctx => { const { language } = ctx.params; const target = this._context.target(language); diff --git a/src/app/core/translation.service.ts b/src/app/core/translation.service.ts index f2d69dc..a35b4f8 100644 --- a/src/app/core/translation.service.ts +++ b/src/app/core/translation.service.ts @@ -5,15 +5,16 @@ import { catchError, distinctUntilChanged, filter, - first, map, switchMap, + take, tap, timeout } from 'rxjs/operators'; import { environment } from '../../environments/environment'; import { RootResponse, TargetResponse } from '../../models'; +import { TargetsResponse } from '../../models/targets-response'; @Injectable({ providedIn: 'root' @@ -27,21 +28,20 @@ export class TranslationService { serviceDown: Observable; private _rootSubject = new BehaviorSubject(undefined); - private _targetsSubject = new BehaviorSubject(new Map()); + private _targetsSubject = new BehaviorSubject(undefined); + private _targetsMap = new BehaviorSubject(new Map()); private _root = this._rootSubject.pipe( filter((r): r is RootResponse => !!r), - distinctUntilChanged((x, y) => JSON.stringify(x) === JSON.stringify(y)) + distinctUntilChanged((x, y) => x.sourceFile === y.sourceFile) ); + private _targets = this._targetsSubject.pipe(filter((r): r is TargetsResponse => !!r)); constructor(private _http: HttpClient) { this.project = this._root.pipe(map(r => r.project)); this.sourceFile = this._root.pipe(map(r => r.sourceFile)); this.sourceLanguage = this._root.pipe(map(r => r.sourceLanguage)); this.unitCount = this._root.pipe(map(r => r.unitCount)); - this.targets = this._targetsSubject.pipe( - filter(t => t.size > 0), - map(t => Array.from(t.values()).sort((a, b) => a.language.localeCompare(b.language))) - ); + this.targets = this._targetsMap.pipe(map(t => Array.from(t.values()))); this.serviceDown = timer(0, 1000).pipe( switchMap(() => this._http.get(`${environment.translationServer}/api`).pipe( @@ -50,23 +50,24 @@ export class TranslationService { catchError(() => of(undefined)) ) ), - map(r => !r) + map(r => !r), + distinctUntilChanged() ); this._root - .pipe(switchMap(r => this._loadTargets(r))) + .pipe(switchMap(r => this._http.get(r._links!.targets.href))) .subscribe(targets => this._targetsSubject.next(targets)); + this._targets + .pipe(switchMap(t => this._loadTargets(t))) + .subscribe(m => this._targetsMap.next(m)); } target(language: string): Observable { - return this._targetsSubject.pipe( - filter(t => t.size > 0), - map(t => t.get(language)) - ); + return this._targetsMap.pipe(map(t => t.get(language))); } createTarget(language: string) { - return this._root.pipe( - first(), + return this._targets.pipe( + take(1), map(r => this._targetHref(r, language)), switchMap(href => this._http.post(href, {})), tap(t => this._updateTarget(t)) @@ -74,17 +75,17 @@ export class TranslationService { } updateTarget(language: string) { - return this._root.pipe( + return this._targets.pipe( map(r => this._targetHref(r, language)), switchMap(href => this._http.get(href)), tap(t => this._updateTarget(t)) ); } - private _loadTargets(rootResponse: RootResponse) { + private _loadTargets(targetsResponse: TargetsResponse) { return combineLatest( - rootResponse.languages - .map(l => this._targetHref(rootResponse, l)) + targetsResponse.languages + .map(l => this._targetHref(targetsResponse, l)) .map(href => this._http.get(href)) ).pipe( map(targets => @@ -96,15 +97,15 @@ export class TranslationService { ); } - private _targetHref(rootResponse: RootResponse, language: string) { - return rootResponse._links!.targets.href.replace('{language}', language); + private _targetHref(targets: TargetsResponse, language: string) { + return targets._links!.target.href.replace('{language}', language); } private _updateTarget(target: TargetResponse) { - const targets = new Map(this._targetsSubject.value).set( + const targets = new Map(this._targetsMap.value).set( target.language, target ); - this._targetsSubject.next(targets); + this._targetsMap.next(targets); } } diff --git a/src/models/root-response.ts b/src/models/root-response.ts index c474548..b3e3560 100644 --- a/src/models/root-response.ts +++ b/src/models/root-response.ts @@ -4,6 +4,5 @@ export interface RootResponse extends Hal { project: string; sourceFile: string; sourceLanguage: string; - languages: string[]; unitCount: number; } diff --git a/src/models/targets-response.ts b/src/models/targets-response.ts new file mode 100644 index 0000000..a5afb92 --- /dev/null +++ b/src/models/targets-response.ts @@ -0,0 +1,5 @@ +import { Hal } from './hal'; + +export interface TargetsResponse extends Hal { + languages: string[]; +}