Browse Source

Caching improved.

pull/95/head
Sebastian Stehle 8 years ago
parent
commit
4d9a75ff36
  1. 4
      src/Squidex/app/features/content/pages/contents/contents-page.component.html
  2. 6
      src/Squidex/app/features/content/pages/contents/contents-page.component.ts
  3. 27
      src/Squidex/app/features/schemas/pages/schemas/schema-form.component.ts
  4. 2
      src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html
  5. 9
      src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts
  6. 1
      src/Squidex/app/framework/declarations.ts
  7. 2
      src/Squidex/app/framework/module.ts
  8. 24
      src/Squidex/app/framework/services/routing-cache.service.ts
  9. 33
      src/Squidex/app/shared/guards/resolve-content.guard.spec.ts
  10. 11
      src/Squidex/app/shared/guards/resolve-content.guard.ts
  11. 38
      src/Squidex/app/shared/guards/resolve-published-schema.guard.spec.ts
  12. 15
      src/Squidex/app/shared/guards/resolve-published-schema.guard.ts
  13. 34
      src/Squidex/app/shared/guards/resolve-schema.guard.spec.ts
  14. 11
      src/Squidex/app/shared/guards/resolve-schema.guard.ts

4
src/Squidex/app/features/content/pages/contents/contents-page.component.html

@ -67,8 +67,8 @@
</thead> </thead>
<tbody *ngIf="!isReadOnly"> <tbody *ngIf="!isReadOnly">
<ng-template ngFor let-content [ngForOf]="contentItems"> <ng-template ngFor let-content [ngForOf]="contentItems")>
<tr [sqxContent]="content" [routerLink]="[content.id]" routerLinkActive="active" <tr [sqxContent]="content" (click)="cacheContent(content)" [routerLink]="[content.id]" routerLinkActive="active"
[languageCode]="languageSelected.iso2Code" [languageCode]="languageSelected.iso2Code"
[schemaFields]="contentFields" [schemaFields]="contentFields"
[schema]="schema" [schema]="schema"

6
src/Squidex/app/features/content/pages/contents/contents-page.component.ts

@ -29,6 +29,7 @@ import {
MessageBus, MessageBus,
NotificationService, NotificationService,
Pager, Pager,
RoutingCache,
SchemaDetailsDto SchemaDetailsDto
} from 'shared'; } from 'shared';
@ -61,6 +62,7 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
private readonly authService: AuthService, private readonly authService: AuthService,
private readonly contentsService: ContentsService, private readonly contentsService: ContentsService,
private readonly route: ActivatedRoute, private readonly route: ActivatedRoute,
private readonly routingCache: RoutingCache,
private readonly messageBus: MessageBus private readonly messageBus: MessageBus
) { ) {
super(notifications, apps); super(notifications, apps);
@ -168,6 +170,10 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
this.languageSelected = language; this.languageSelected = language;
} }
public cacheContent(content: ContentDto) {
this.routingCache.set(`content.${content.id}`, content);
}
public goNext() { public goNext() {
this.contentsPager = this.contentsPager.goNext(); this.contentsPager = this.contentsPager.goNext();

27
src/Squidex/app/features/schemas/pages/schemas/schema-form.component.ts

@ -7,16 +7,12 @@
import { Component, EventEmitter, Input, Output } from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms'; import { FormBuilder, Validators } from '@angular/forms';
import { Observable } from 'rxjs';
import { import {
ApiUrlConfig, ApiUrlConfig,
AuthService, AuthService,
fadeAnimation, fadeAnimation,
Notification,
NotificationService,
SchemaDetailsDto, SchemaDetailsDto,
SchemaDto,
SchemasService, SchemasService,
ValidatorsEx, ValidatorsEx,
Version Version
@ -34,7 +30,7 @@ const FALLBACK_NAME = 'my-schema';
}) })
export class SchemaFormComponent { export class SchemaFormComponent {
@Output() @Output()
public created = new EventEmitter<{ schema: SchemaDto, isAvailable: boolean }>(); public created = new EventEmitter<SchemaDetailsDto>();
@Output() @Output()
public cancelled = new EventEmitter(); public cancelled = new EventEmitter();
@ -63,7 +59,6 @@ export class SchemaFormComponent {
constructor( constructor(
public readonly apiUrl: ApiUrlConfig, public readonly apiUrl: ApiUrlConfig,
private readonly notifications: NotificationService,
private readonly schemas: SchemasService, private readonly schemas: SchemasService,
private readonly formBuilder: FormBuilder, private readonly formBuilder: FormBuilder,
private readonly authService: AuthService private readonly authService: AuthService
@ -95,25 +90,11 @@ export class SchemaFormComponent {
const me = this.authService.user!.token; const me = this.authService.user!.token;
this.schemas.postSchema(this.appName, requestDto, me, undefined, schemaVersion) this.schemas.postSchema(this.appName, requestDto, me, undefined, schemaVersion)
.switchMap(dto => {
return this.schemas.getSchema(this.appName, dto.id)
.retryWhen(errors => errors
.delay(500)
.take(10)
.concat(Observable.throw(dto)));
})
.subscribe(dto => { .subscribe(dto => {
this.emitCreated(dto, true); this.emitCreated(dto);
this.resetCreateForm(); this.resetCreateForm();
}, error => { }, error => {
if (error instanceof SchemaDetailsDto) {
this.notifications.notify(Notification.error('Schema has been created but is awaiting to be processed. Reload in a few seconds.'));
this.emitCreated(error, false);
this.resetCreateForm();
} else {
this.enableCreateForm(error.displayMessage); this.enableCreateForm(error.displayMessage);
}
}); });
} }
} }
@ -122,8 +103,8 @@ export class SchemaFormComponent {
this.cancelled.emit(); this.cancelled.emit();
} }
private emitCreated(schema: SchemaDto, isAvailable: boolean) { private emitCreated(schema: SchemaDetailsDto) {
this.created.emit({ schema, isAvailable }); this.created.emit(schema);
} }
private enableCreateForm(message: string) { private enableCreateForm(message: string) {

2
src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html

@ -66,7 +66,7 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<sqx-schema-form [appName]="appName() | async" (created)="onSchemaCreated($event.schema, $event.isAvailable)" (cancelled)="addSchemaDialog.hide()"></sqx-schema-form> <sqx-schema-form [appName]="appName() | async" (created)="onSchemaCreated($event)" (cancelled)="addSchemaDialog.hide()"></sqx-schema-form>
</div> </div>
</div> </div>
</div> </div>

9
src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts

@ -18,6 +18,7 @@ import {
MessageBus, MessageBus,
ModalView, ModalView,
NotificationService, NotificationService,
RoutingCache,
SchemaDto, SchemaDto,
SchemasService SchemasService
} from 'shared'; } from 'shared';
@ -47,7 +48,8 @@ export class SchemasPageComponent extends AppComponentBase implements OnDestroy,
private readonly schemasService: SchemasService, private readonly schemasService: SchemasService,
private readonly messageBus: MessageBus, private readonly messageBus: MessageBus,
private readonly router: Router, private readonly router: Router,
private readonly route: ActivatedRoute private readonly route: ActivatedRoute,
private readonly routingCache: RoutingCache
) { ) {
super(notifications, apps); super(notifications, apps);
} }
@ -97,15 +99,14 @@ export class SchemasPageComponent extends AppComponentBase implements OnDestroy,
}); });
} }
public onSchemaCreated(dto: SchemaDto, isAvailable: boolean) { public onSchemaCreated(dto: SchemaDto) {
this.updateSchemas(this.schemas.push(dto), this.schemaQuery); this.updateSchemas(this.schemas.push(dto), this.schemaQuery);
this.addSchemaDialog.hide(); this.addSchemaDialog.hide();
if (isAvailable) { this.routingCache.set(`schema.${dto.name}`, dto);
this.router.navigate([ dto.name ], { relativeTo: this.route }); this.router.navigate([ dto.name ], { relativeTo: this.route });
} }
}
private updateSchemas(schemas: ImmutableArray<SchemaDto>, query?: string) { private updateSchemas(schemas: ImmutableArray<SchemaDto>, query?: string) {
this.schemas = schemas; this.schemas = schemas;

1
src/Squidex/app/framework/declarations.ts

@ -54,6 +54,7 @@ export * from './services/message-bus';
export * from './services/notification.service'; export * from './services/notification.service';
export * from './services/resource-loader.service'; export * from './services/resource-loader.service';
export * from './services/root-view.service'; export * from './services/root-view.service';
export * from './services/routing-cache.service';
export * from './services/shortcut.service'; export * from './services/shortcut.service';
export * from './services/title.service'; export * from './services/title.service';

2
src/Squidex/app/framework/module.ts

@ -50,6 +50,7 @@ import {
RichEditorComponent, RichEditorComponent,
RootViewDirective, RootViewDirective,
RootViewService, RootViewService,
RoutingCache,
ScrollActiveDirective, ScrollActiveDirective,
ShortcutComponent, ShortcutComponent,
ShortcutService, ShortcutService,
@ -182,6 +183,7 @@ export class SqxFrameworkModule {
NotificationService, NotificationService,
ResourceLoaderService, ResourceLoaderService,
RootViewService, RootViewService,
RoutingCache,
ShortcutService, ShortcutService,
TitleService TitleService
] ]

24
src/Squidex/app/framework/services/routing-cache.service.ts

@ -0,0 +1,24 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
export class RoutingCache {
private readonly entries: { [key: string]: { value: any, inserted: number } } = {};
public getValue<T>(key: string) {
let entry = this.entries[key];
if (entry && (new Date().getTime() - entry.inserted) < 100) {
return <T> entry.value;
} else {
return undefined;
}
}
public set<T>(key: string, value: T) {
this.entries[key] = { value, inserted: new Date().getTime() };
}
}

33
src/Squidex/app/shared/guards/resolve-content.guard.spec.ts

@ -8,7 +8,7 @@
import { IMock, Mock } from 'typemoq'; import { IMock, Mock } from 'typemoq';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { ContentsService } from 'shared'; import { ContentsService, RoutingCache } from 'shared';
import { ResolveContentGuard } from './resolve-content.guard'; import { ResolveContentGuard } from './resolve-content.guard';
import { RouterMockup } from './router-mockup'; import { RouterMockup } from './router-mockup';
@ -31,35 +31,54 @@ describe('ResolveContentGuard', () => {
}; };
let appsStore: IMock<ContentsService>; let appsStore: IMock<ContentsService>;
let routingCache: IMock<RoutingCache>;
beforeEach(() => { beforeEach(() => {
appsStore = Mock.ofType(ContentsService); appsStore = Mock.ofType(ContentsService);
routingCache = Mock.ofType(RoutingCache);
}); });
it('should throw if route does not contain app name', () => { it('should throw if route does not contain app name', () => {
const guard = new ResolveContentGuard(appsStore.object, <any>new RouterMockup()); const guard = new ResolveContentGuard(appsStore.object, <any>new RouterMockup(), routingCache.object);
expect(() => guard.resolve(<any>{ params: {} }, <any>{})).toThrow('Route must contain app name.'); expect(() => guard.resolve(<any>{ params: {} }, <any>{})).toThrow('Route must contain app name.');
}); });
it('should throw if route does not contain schema name', () => { it('should throw if route does not contain schema name', () => {
const guard = new ResolveContentGuard(appsStore.object, <any>new RouterMockup()); const guard = new ResolveContentGuard(appsStore.object, <any>new RouterMockup(), routingCache.object);
expect(() => guard.resolve(<any>{ params: { appName: 'my-app' } }, <any>{})).toThrow('Route must contain schema name.'); expect(() => guard.resolve(<any>{ params: { appName: 'my-app' } }, <any>{})).toThrow('Route must contain schema name.');
}); });
it('should throw if route does not contain content id', () => { it('should throw if route does not contain content id', () => {
const guard = new ResolveContentGuard(appsStore.object, <any>new RouterMockup()); const guard = new ResolveContentGuard(appsStore.object, <any>new RouterMockup(), routingCache.object);
expect(() => guard.resolve(<any>{ params: { appName: 'my-app', schemaName: 'my-schema' } }, <any>{})).toThrow('Route must contain content id.'); expect(() => guard.resolve(<any>{ params: { appName: 'my-app', schemaName: 'my-schema' } }, <any>{})).toThrow('Route must contain content id.');
}); });
it('should provide content from cache if found', (done) => {
const content = { };
routingCache.setup(x => x.getValue('content.123'))
.returns(() => content);
const guard = new ResolveContentGuard(appsStore.object, <any>new RouterMockup(), routingCache.object);
guard.resolve(<any>route, <any>{})
.subscribe(result => {
expect(result).toBe(content);
done();
});
});
it('should navigate to 404 page if schema is not found', (done) => { it('should navigate to 404 page if schema is not found', (done) => {
appsStore.setup(x => x.getContent('my-app', 'my-schema', '123')) appsStore.setup(x => x.getContent('my-app', 'my-schema', '123'))
.returns(() => Observable.of(null!)); .returns(() => Observable.of(null!));
const router = new RouterMockup(); const router = new RouterMockup();
const guard = new ResolveContentGuard(appsStore.object, <any>router); const guard = new ResolveContentGuard(appsStore.object, <any>router, routingCache.object);
guard.resolve(<any>route, <any>{}) guard.resolve(<any>route, <any>{})
.subscribe(result => { .subscribe(result => {
@ -75,7 +94,7 @@ describe('ResolveContentGuard', () => {
.returns(() => Observable.throw(null!)); .returns(() => Observable.throw(null!));
const router = new RouterMockup(); const router = new RouterMockup();
const guard = new ResolveContentGuard(appsStore.object, <any>router); const guard = new ResolveContentGuard(appsStore.object, <any>router, routingCache.object);
guard.resolve(<any>route, <any>{}) guard.resolve(<any>route, <any>{})
.subscribe(result => { .subscribe(result => {
@ -93,7 +112,7 @@ describe('ResolveContentGuard', () => {
.returns(() => Observable.of(content)); .returns(() => Observable.of(content));
const router = new RouterMockup(); const router = new RouterMockup();
const guard = new ResolveContentGuard(appsStore.object, <any>router); const guard = new ResolveContentGuard(appsStore.object, <any>router, routingCache.object);
guard.resolve(<any>route, <any>{}) guard.resolve(<any>route, <any>{})
.subscribe(result => { .subscribe(result => {

11
src/Squidex/app/shared/guards/resolve-content.guard.ts

@ -9,7 +9,7 @@ import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router'; import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { allParams } from 'framework'; import { allParams, RoutingCache } from 'framework';
import { ContentDto, ContentsService } from './../services/contents.service'; import { ContentDto, ContentsService } from './../services/contents.service';
@ -17,7 +17,8 @@ import { ContentDto, ContentsService } from './../services/contents.service';
export class ResolveContentGuard implements Resolve<ContentDto> { export class ResolveContentGuard implements Resolve<ContentDto> {
constructor( constructor(
private readonly contentsService: ContentsService, private readonly contentsService: ContentsService,
private readonly router: Router private readonly router: Router,
private readonly routingCache: RoutingCache
) { ) {
} }
@ -42,6 +43,12 @@ export class ResolveContentGuard implements Resolve<ContentDto> {
throw 'Route must contain content id.'; throw 'Route must contain content id.';
} }
const content = this.routingCache.getValue<ContentDto>(`content.${contentId}`);
if (content) {
return Observable.of(content);
}
const result = const result =
this.contentsService.getContent(appName, schemaName, contentId) this.contentsService.getContent(appName, schemaName, contentId)
.do(dto => { .do(dto => {

38
src/Squidex/app/shared/guards/resolve-published-schema.guard.spec.ts

@ -8,10 +8,9 @@
import { IMock, Mock } from 'typemoq'; import { IMock, Mock } from 'typemoq';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { SchemasService } from 'shared'; import { RoutingCache, SchemasService } from 'shared';
import { ResolvePublishedSchemaGuard } from './resolve-published-schema.guard';
import { RouterMockup } from './router-mockup'; import { RouterMockup } from './router-mockup';
import { ResolvePublishedSchemaGuard } from './resolve-published-schema.guard';
describe('ResolvePublishedSchemaGuard', () => { describe('ResolvePublishedSchemaGuard', () => {
const route = { const route = {
@ -26,29 +25,48 @@ describe('ResolvePublishedSchemaGuard', () => {
}; };
let schemasService: IMock<SchemasService>; let schemasService: IMock<SchemasService>;
let routingCache: IMock<RoutingCache>;
beforeEach(() => { beforeEach(() => {
schemasService = Mock.ofType(SchemasService); schemasService = Mock.ofType(SchemasService);
routingCache = Mock.ofType(RoutingCache);
}); });
it('should throw if route does not contain app name', () => { it('should throw if route does not contain app name', () => {
const guard = new ResolvePublishedSchemaGuard(schemasService.object, <any>new RouterMockup()); const guard = new ResolvePublishedSchemaGuard(schemasService.object, <any>new RouterMockup(), routingCache.object);
expect(() => guard.resolve(<any>{ params: {} }, <any>{})).toThrow('Route must contain app name.'); expect(() => guard.resolve(<any>{ params: {} }, <any>{})).toThrow('Route must contain app name.');
}); });
it('should throw if route does not contain schema name', () => { it('should throw if route does not contain schema name', () => {
const guard = new ResolvePublishedSchemaGuard(schemasService.object, <any>new RouterMockup()); const guard = new ResolvePublishedSchemaGuard(schemasService.object, <any>new RouterMockup(), routingCache.object);
expect(() => guard.resolve(<any>{ params: { appName: 'my-app' } }, <any>{})).toThrow('Route must contain schema name.'); expect(() => guard.resolve(<any>{ params: { appName: 'my-app' } }, <any>{})).toThrow('Route must contain schema name.');
}); });
it('should provide schema from cache if found', (done) => {
const schema = { isPublished: true };
routingCache.setup(x => x.getValue('schema.my-schema'))
.returns(() => schema);
const guard = new ResolvePublishedSchemaGuard(schemasService.object, <any>new RouterMockup(), routingCache.object);
guard.resolve(<any>route, <any>{})
.subscribe(result => {
expect(result).toBe(schema);
done();
});
});
it('should navigate to 404 page if schema is not found', (done) => { it('should navigate to 404 page if schema is not found', (done) => {
schemasService.setup(x => x.getSchema('my-app', 'my-schema')) schemasService.setup(x => x.getSchema('my-app', 'my-schema'))
.returns(() => Observable.of(null!)); .returns(() => Observable.of(null!));
const router = new RouterMockup(); const router = new RouterMockup();
const guard = new ResolvePublishedSchemaGuard(schemasService.object, <any>router); const guard = new ResolvePublishedSchemaGuard(schemasService.object, <any>router, routingCache.object);
guard.resolve(<any>route, <any>{}) guard.resolve(<any>route, <any>{})
.subscribe(result => { .subscribe(result => {
@ -64,7 +82,7 @@ describe('ResolvePublishedSchemaGuard', () => {
.returns(() => Observable.throw(null)); .returns(() => Observable.throw(null));
const router = new RouterMockup(); const router = new RouterMockup();
const guard = new ResolvePublishedSchemaGuard(schemasService.object, <any>router); const guard = new ResolvePublishedSchemaGuard(schemasService.object, <any>router, routingCache.object);
guard.resolve(<any>route, <any>{}) guard.resolve(<any>route, <any>{})
.subscribe(result => { .subscribe(result => {
@ -82,11 +100,11 @@ describe('ResolvePublishedSchemaGuard', () => {
.returns(() => Observable.of(schema)); .returns(() => Observable.of(schema));
const router = new RouterMockup(); const router = new RouterMockup();
const guard = new ResolvePublishedSchemaGuard(schemasService.object, <any>router); const guard = new ResolvePublishedSchemaGuard(schemasService.object, <any>router, routingCache.object);
guard.resolve(<any>route, <any>{}) guard.resolve(<any>route, <any>{})
.subscribe(result => { .subscribe(result => {
expect(result).toBeFalsy(); expect(result).toBe(schema);
expect(router.lastNavigation).toEqual(['/404']); expect(router.lastNavigation).toEqual(['/404']);
done(); done();
@ -100,7 +118,7 @@ describe('ResolvePublishedSchemaGuard', () => {
.returns(() => Observable.of(schema)); .returns(() => Observable.of(schema));
const router = new RouterMockup(); const router = new RouterMockup();
const guard = new ResolvePublishedSchemaGuard(schemasService.object, <any>router); const guard = new ResolvePublishedSchemaGuard(schemasService.object, <any>router, routingCache.object);
guard.resolve(<any>route, <any>{}) guard.resolve(<any>route, <any>{})
.subscribe(result => { .subscribe(result => {

15
src/Squidex/app/shared/guards/resolve-published-schema.guard.ts

@ -9,7 +9,7 @@ import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router'; import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { allParams } from 'framework'; import { allParams, RoutingCache } from 'framework';
import { SchemaDetailsDto, SchemasService } from './../services/schemas.service'; import { SchemaDetailsDto, SchemasService } from './../services/schemas.service';
@ -17,7 +17,8 @@ import { SchemaDetailsDto, SchemasService } from './../services/schemas.service'
export class ResolvePublishedSchemaGuard implements Resolve<SchemaDetailsDto> { export class ResolvePublishedSchemaGuard implements Resolve<SchemaDetailsDto> {
constructor( constructor(
private readonly schemasService: SchemasService, private readonly schemasService: SchemasService,
private readonly router: Router private readonly router: Router,
private readonly routingCache: RoutingCache
) { ) {
} }
@ -36,10 +37,16 @@ export class ResolvePublishedSchemaGuard implements Resolve<SchemaDetailsDto> {
throw 'Route must contain schema name.'; throw 'Route must contain schema name.';
} }
const schema = this.routingCache.getValue<SchemaDetailsDto>(`schema.${schemaName}`);
if (schema && schema.isPublished) {
return Observable.of(schema);
}
const result = const result =
this.schemasService.getSchema(appName, schemaName).map(dto => dto && dto.isPublished ? dto : null) this.schemasService.getSchema(appName, schemaName)
.do(dto => { .do(dto => {
if (!dto) { if (!dto || !dto.isPublished) {
this.router.navigate(['/404']); this.router.navigate(['/404']);
} }
}) })

34
src/Squidex/app/shared/guards/resolve-schema.guard.spec.ts

@ -8,10 +8,9 @@
import { IMock, Mock } from 'typemoq'; import { IMock, Mock } from 'typemoq';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { SchemasService } from 'shared'; import { RoutingCache, SchemasService } from 'shared';
import { ResolveSchemaGuard } from './resolve-schema.guard';
import { RouterMockup } from './router-mockup'; import { RouterMockup } from './router-mockup';
import { ResolveSchemaGuard } from './resolve-schema.guard';
describe('ResolveSchemaGuard', () => { describe('ResolveSchemaGuard', () => {
const route = { const route = {
@ -26,29 +25,48 @@ describe('ResolveSchemaGuard', () => {
}; };
let schemasService: IMock<SchemasService>; let schemasService: IMock<SchemasService>;
let routingCache: IMock<RoutingCache>;
beforeEach(() => { beforeEach(() => {
schemasService = Mock.ofType(SchemasService); schemasService = Mock.ofType(SchemasService);
routingCache = Mock.ofType(RoutingCache);
}); });
it('should throw if route does not contain app name', () => { it('should throw if route does not contain app name', () => {
const guard = new ResolveSchemaGuard(schemasService.object, <any>new RouterMockup()); const guard = new ResolveSchemaGuard(schemasService.object, <any>new RouterMockup(), routingCache.object);
expect(() => guard.resolve(<any>{ params: {} }, <any>{})).toThrow('Route must contain app name.'); expect(() => guard.resolve(<any>{ params: {} }, <any>{})).toThrow('Route must contain app name.');
}); });
it('should throw if route does not contain schema name', () => { it('should throw if route does not contain schema name', () => {
const guard = new ResolveSchemaGuard(schemasService.object, <any>new RouterMockup()); const guard = new ResolveSchemaGuard(schemasService.object, <any>new RouterMockup(), routingCache.object);
expect(() => guard.resolve(<any>{ params: { appName: 'my-app' } }, <any>{})).toThrow('Route must contain schema name.'); expect(() => guard.resolve(<any>{ params: { appName: 'my-app' } }, <any>{})).toThrow('Route must contain schema name.');
}); });
it('should provide schema from cache if found', (done) => {
const schema = { isPublished: true };
routingCache.setup(x => x.getValue('schema.my-schema'))
.returns(() => schema);
const guard = new ResolveSchemaGuard(schemasService.object, <any>new RouterMockup(), routingCache.object);
guard.resolve(<any>route, <any>{})
.subscribe(result => {
expect(result).toBe(schema);
done();
});
});
it('should navigate to 404 page if schema is not found', (done) => { it('should navigate to 404 page if schema is not found', (done) => {
schemasService.setup(x => x.getSchema('my-app', 'my-schema')) schemasService.setup(x => x.getSchema('my-app', 'my-schema'))
.returns(() => Observable.of(null!)); .returns(() => Observable.of(null!));
const router = new RouterMockup(); const router = new RouterMockup();
const guard = new ResolveSchemaGuard(schemasService.object, <any>router); const guard = new ResolveSchemaGuard(schemasService.object, <any>router, routingCache.object);
guard.resolve(<any>route, <any>{}) guard.resolve(<any>route, <any>{})
.subscribe(result => { .subscribe(result => {
@ -64,7 +82,7 @@ describe('ResolveSchemaGuard', () => {
.returns(() => Observable.throw(null!)); .returns(() => Observable.throw(null!));
const router = new RouterMockup(); const router = new RouterMockup();
const guard = new ResolveSchemaGuard(schemasService.object, <any>router); const guard = new ResolveSchemaGuard(schemasService.object, <any>router, routingCache.object);
guard.resolve(<any>route, <any>{}) guard.resolve(<any>route, <any>{})
.subscribe(result => { .subscribe(result => {
@ -82,7 +100,7 @@ describe('ResolveSchemaGuard', () => {
.returns(() => Observable.of(schema)); .returns(() => Observable.of(schema));
const router = new RouterMockup(); const router = new RouterMockup();
const guard = new ResolveSchemaGuard(schemasService.object, <any>router); const guard = new ResolveSchemaGuard(schemasService.object, <any>router, routingCache.object);
guard.resolve(<any>route, <any>{}) guard.resolve(<any>route, <any>{})
.subscribe(result => { .subscribe(result => {

11
src/Squidex/app/shared/guards/resolve-schema.guard.ts

@ -9,7 +9,7 @@ import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router'; import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { allParams } from 'framework'; import { allParams, RoutingCache } from 'framework';
import { SchemaDetailsDto, SchemasService } from './../services/schemas.service'; import { SchemaDetailsDto, SchemasService } from './../services/schemas.service';
@ -17,7 +17,8 @@ import { SchemaDetailsDto, SchemasService } from './../services/schemas.service'
export class ResolveSchemaGuard implements Resolve<SchemaDetailsDto> { export class ResolveSchemaGuard implements Resolve<SchemaDetailsDto> {
constructor( constructor(
private readonly schemasService: SchemasService, private readonly schemasService: SchemasService,
private readonly router: Router private readonly router: Router,
private readonly routingCache: RoutingCache
) { ) {
} }
@ -36,6 +37,12 @@ export class ResolveSchemaGuard implements Resolve<SchemaDetailsDto> {
throw 'Route must contain schema name.'; throw 'Route must contain schema name.';
} }
const schema = this.routingCache.getValue<SchemaDetailsDto>(`schema.${schemaName}`);
if (schema) {
return Observable.of(schema);
}
const result = const result =
this.schemasService.getSchema(appName, schemaName) this.schemasService.getSchema(appName, schemaName)
.do(dto => { .do(dto => {

Loading…
Cancel
Save