Skip to content

Commit

Permalink
refactor: improve targets handling in frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
kyubisation committed Jan 18, 2020
1 parent c0fcf0a commit ea062ec
Show file tree
Hide file tree
Showing 9 changed files with 68 additions and 33 deletions.
1 change: 1 addition & 0 deletions builders/t9n/server/constants.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
1 change: 1 addition & 0 deletions builders/t9n/server/responses/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
9 changes: 2 additions & 7 deletions builders/t9n/server/responses/root-response.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
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';

export class RootResponse implements Hal {
project: string;
sourceFile: string;
sourceLanguage: string;
languages: string[];
unitCount: number;
_links?: { [key: string]: HalLink };
_embedded?: { [key: string]: unknown };
Expand All @@ -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();
}
}
21 changes: 21 additions & 0 deletions builders/t9n/server/responses/targets-response.ts
Original file line number Diff line number Diff line change
@@ -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();
}
}
9 changes: 8 additions & 1 deletion builders/t9n/server/translation-server.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
RootResponse,
SourceUnitResponse,
TargetResponse,
TargetsResponse,
TargetUnitResponse
} from './responses';
import { TranslationServer } from './translation-server';
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
7 changes: 6 additions & 1 deletion builders/t9n/server/translation-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,16 @@ import {
SOURCE_UNITS_ROUTE,
TARGET_ROUTE,
TARGET_UNIT_ROUTE,
TARGET_UNITS_ROUTE
TARGET_UNITS_ROUTE,
TARGETS_ROUTE
} from './constants';
import {
OrphanResponse,
PaginationResponse,
RootResponse,
SourceUnitWithTranslationsResponse,
TargetResponse,
TargetsResponse,
TargetUnitResponse
} from './responses';

Expand Down Expand Up @@ -78,6 +80,9 @@ export class TranslationServer extends Koa<any, Koa.DefaultContext & Router.Rout
ctx.body = new SourceUnitWithTranslationsResponse(unit, this._context, toUrlFactory(ctx));
}
})
.get(TARGETS_ROUTE, '/targets', ctx => {
ctx.body = new TargetsResponse(this._context, toUrlFactory(ctx));
})
.get(TARGET_ROUTE, '/targets/:language', ctx => {
const { language } = ctx.params;
const target = this._context.target(language);
Expand Down
47 changes: 24 additions & 23 deletions src/app/core/translation.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -27,21 +28,20 @@ export class TranslationService {
serviceDown: Observable<boolean>;

private _rootSubject = new BehaviorSubject<RootResponse | undefined>(undefined);
private _targetsSubject = new BehaviorSubject(new Map<string, TargetResponse>());
private _targetsSubject = new BehaviorSubject<TargetsResponse | undefined>(undefined);
private _targetsMap = new BehaviorSubject(new Map<string, TargetResponse>());
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<RootResponse>(`${environment.translationServer}/api`).pipe(
Expand All @@ -50,41 +50,42 @@ 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<TargetsResponse>(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<TargetResponse | undefined> {
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<TargetResponse>(href, {})),
tap(t => this._updateTarget(t))
);
}

updateTarget(language: string) {
return this._root.pipe(
return this._targets.pipe(
map(r => this._targetHref(r, language)),
switchMap(href => this._http.get<TargetResponse>(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<TargetResponse>(href))
).pipe(
map(targets =>
Expand All @@ -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<string, TargetResponse>(this._targetsSubject.value).set(
const targets = new Map<string, TargetResponse>(this._targetsMap.value).set(
target.language,
target
);
this._targetsSubject.next(targets);
this._targetsMap.next(targets);
}
}
1 change: 0 additions & 1 deletion src/models/root-response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,5 @@ export interface RootResponse extends Hal {
project: string;
sourceFile: string;
sourceLanguage: string;
languages: string[];
unitCount: number;
}
5 changes: 5 additions & 0 deletions src/models/targets-response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Hal } from './hal';

export interface TargetsResponse extends Hal {
languages: string[];
}

0 comments on commit ea062ec

Please sign in to comment.