From 06c46fa4ff7f99671c56426a512661e9df9f53ac Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sun, 15 Apr 2018 21:04:17 +0200 Subject: [PATCH] Pipes / Performance. --- .../assets/pages/assets-page.component.html | 4 +- .../assets/pages/assets-page.component.ts | 6 +- .../shared/contents-selector.component.html | 2 +- .../shared/contents-selector.component.ts | 6 +- .../app/features/rules/declarations.ts | 1 + src/Squidex/app/features/rules/module.ts | 2 + .../app/features/rules/pages/events/pipes.ts | 26 ++++ .../events/rule-events-page.component.html | 8 +- .../events/rule-events-page.component.ts | 12 -- .../app/features/settings/declarations.ts | 7 ++ src/Squidex/app/features/settings/module.ts | 4 + .../pages/backups/backups-page.component.html | 112 +++++++++--------- .../pages/backups/backups-page.component.ts | 40 +------ .../features/settings/pages/backups/pipes.ts | 41 +++++++ .../angular/pipes/date-time.pipes.spec.ts | 12 ++ .../angular/pipes/date-time.pipes.ts | 10 ++ src/Squidex/app/framework/module.ts | 3 + src/Squidex/app/shared/internal.ts | 1 + src/Squidex/app/shared/module.ts | 2 + .../app/shared/state/backups.state.spec.ts | 75 ++++++++++++ src/Squidex/app/shared/state/backups.state.ts | 69 +++++++++++ 21 files changed, 330 insertions(+), 113 deletions(-) create mode 100644 src/Squidex/app/features/rules/pages/events/pipes.ts create mode 100644 src/Squidex/app/features/settings/pages/backups/pipes.ts create mode 100644 src/Squidex/app/shared/state/backups.state.spec.ts create mode 100644 src/Squidex/app/shared/state/backups.state.ts diff --git a/src/Squidex/app/features/assets/pages/assets-page.component.html b/src/Squidex/app/features/assets/pages/assets-page.component.html index bae9dfcae..cdde0ccfa 100644 --- a/src/Squidex/app/features/assets/pages/assets-page.component.html +++ b/src/Squidex/app/features/assets/pages/assets-page.component.html @@ -6,11 +6,11 @@ - - +
diff --git a/src/Squidex/app/features/assets/pages/assets-page.component.ts b/src/Squidex/app/features/assets/pages/assets-page.component.ts index 4bba8d4b0..ad35b6020 100644 --- a/src/Squidex/app/features/assets/pages/assets-page.component.ts +++ b/src/Squidex/app/features/assets/pages/assets-page.component.ts @@ -27,11 +27,11 @@ export class AssetsPageComponent implements OnInit { } public ngOnInit() { - this.load(); + this.assetsState.load().onErrorResumeNext().subscribe(); } - public load(notify = false) { - this.assetsState.load(notify).onErrorResumeNext().subscribe(); + public reload() { + this.assetsState.load(true).onErrorResumeNext().subscribe(); } public search() { diff --git a/src/Squidex/app/features/content/shared/contents-selector.component.html b/src/Squidex/app/features/content/shared/contents-selector.component.html index f904d8fb8..9c2b1282b 100644 --- a/src/Squidex/app/features/content/shared/contents-selector.component.html +++ b/src/Squidex/app/features/content/shared/contents-selector.component.html @@ -5,7 +5,7 @@
- diff --git a/src/Squidex/app/features/content/shared/contents-selector.component.ts b/src/Squidex/app/features/content/shared/contents-selector.component.ts index fd2e272d1..455e706e0 100644 --- a/src/Squidex/app/features/content/shared/contents-selector.component.ts +++ b/src/Squidex/app/features/content/shared/contents-selector.component.ts @@ -56,7 +56,11 @@ export class ContentsSelectorComponent implements OnInit { this.load(); } - public load(showInfo = false) { + public reload() { + this.load(true); + } + + private load(showInfo = false) { this.contentsService.getContents(this.appsState.appName, this.schema.name, this.contentsPager.pageSize, this.contentsPager.skip, this.contentsQuery, undefined, false) .finally(() => { this.selectedItems = {}; diff --git a/src/Squidex/app/features/rules/declarations.ts b/src/Squidex/app/features/rules/declarations.ts index 7d00f3b61..8c9a3da63 100644 --- a/src/Squidex/app/features/rules/declarations.ts +++ b/src/Squidex/app/features/rules/declarations.ts @@ -16,4 +16,5 @@ export * from './pages/rules/triggers/content-changed-trigger.component'; export * from './pages/rules/rule-wizard.component'; export * from './pages/rules/rules-page.component'; +export * from './pages/events/pipes'; export * from './pages/events/rule-events-page.component'; \ No newline at end of file diff --git a/src/Squidex/app/features/rules/module.ts b/src/Squidex/app/features/rules/module.ts index 25b4973bb..43d47233d 100644 --- a/src/Squidex/app/features/rules/module.ts +++ b/src/Squidex/app/features/rules/module.ts @@ -21,6 +21,7 @@ import { ContentChangedTriggerComponent, ElasticSearchActionComponent, FastlyActionComponent, + RuleEventBadgeClassPipe, RuleEventsPageComponent, RulesPageComponent, RuleWizardComponent, @@ -61,6 +62,7 @@ const routes: Routes = [ ContentChangedTriggerComponent, ElasticSearchActionComponent, FastlyActionComponent, + RuleEventBadgeClassPipe, RuleEventsPageComponent, RulesPageComponent, RuleWizardComponent, diff --git a/src/Squidex/app/features/rules/pages/events/pipes.ts b/src/Squidex/app/features/rules/pages/events/pipes.ts new file mode 100644 index 000000000..e15e28e90 --- /dev/null +++ b/src/Squidex/app/features/rules/pages/events/pipes.ts @@ -0,0 +1,26 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'sqxRuleEventBadgeClass', + pure: true +}) +export class RuleEventBadgeClassPipe implements PipeTransform { + public transform(status: string) { + if (status === 'Retry') { + return 'warning'; + } else if (status === 'Failed') { + return 'danger'; + } else if (status === 'Pending') { + return 'secondary'; + } else { + return status.toLowerCase(); + } + } +} \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/events/rule-events-page.component.html b/src/Squidex/app/features/rules/pages/events/rule-events-page.component.html index 21dcbd253..9bef7f4e3 100644 --- a/src/Squidex/app/features/rules/pages/events/rule-events-page.component.html +++ b/src/Squidex/app/features/rules/pages/events/rule-events-page.component.html @@ -29,9 +29,7 @@ Created - - - + @@ -39,7 +37,7 @@ - {{event.jobResult}} + {{event.jobResult}} {{event.eventName}} @@ -64,7 +62,7 @@
- {{event.result}} + {{event.result}}
Attempts: {{event.numCalls}} diff --git a/src/Squidex/app/features/rules/pages/events/rule-events-page.component.ts b/src/Squidex/app/features/rules/pages/events/rule-events-page.component.ts index 46cac2531..7ccdd2f20 100644 --- a/src/Squidex/app/features/rules/pages/events/rule-events-page.component.ts +++ b/src/Squidex/app/features/rules/pages/events/rule-events-page.component.ts @@ -76,17 +76,5 @@ export class RuleEventsPageComponent implements OnInit { this.load(); } - - public getBadgeClass(status: string) { - if (status === 'Retry') { - return 'warning'; - } else if (status === 'Failed') { - return 'danger'; - } else if (status === 'Pending') { - return 'secondary'; - } else { - return status.toLowerCase(); - } - } } diff --git a/src/Squidex/app/features/settings/declarations.ts b/src/Squidex/app/features/settings/declarations.ts index 66917745d..464cce4ab 100644 --- a/src/Squidex/app/features/settings/declarations.ts +++ b/src/Squidex/app/features/settings/declarations.ts @@ -6,14 +6,21 @@ */ export * from './pages/backups/backups-page.component'; +export * from './pages/backups/pipes'; + export * from './pages/clients/client.component'; export * from './pages/clients/clients-page.component'; + export * from './pages/contributors/contributors-page.component'; + export * from './pages/languages/language.component'; export * from './pages/languages/languages-page.component'; + export * from './pages/more/more-page.component'; + export * from './pages/patterns/pattern.component'; export * from './pages/patterns/patterns-page.component'; + export * from './pages/plans/plans-page.component'; export * from './settings-area.component'; \ No newline at end of file diff --git a/src/Squidex/app/features/settings/module.ts b/src/Squidex/app/features/settings/module.ts index d9a30da06..5abe1d47b 100644 --- a/src/Squidex/app/features/settings/module.ts +++ b/src/Squidex/app/features/settings/module.ts @@ -15,6 +15,8 @@ import { } from '@app/shared'; import { + BackupDownloadUrlPipe, + BackupDurationPipe, BackupsPageComponent, ClientComponent, ClientsPageComponent, @@ -76,6 +78,8 @@ const routes: Routes = [ RouterModule.forChild(routes) ], declarations: [ + BackupDownloadUrlPipe, + BackupDurationPipe, BackupsPageComponent, ClientComponent, ClientsPageComponent, diff --git a/src/Squidex/app/features/settings/pages/backups/backups-page.component.html b/src/Squidex/app/features/settings/pages/backups/backups-page.component.html index 2c7f275a0..3443c5e85 100644 --- a/src/Squidex/app/features/settings/pages/backups/backups-page.component.html +++ b/src/Squidex/app/features/settings/pages/backups/backups-page.component.html @@ -6,75 +6,77 @@ - -
+
Your have reached the maximum number of backups: 10.
-
- No backups created yet. -
- -
-
-
-
- -
-
- -
-
- -
-
-
-
- Started: -
-
- Duration: -
-
-
-
- {{backup.started.toISOString()}} + +
+ No backups created yet. +
+ +
+
+
+
+ +
+
+ +
+
+ +
-
- {{getDuration(backup) | sqxDuration}} +
+
+ Started: +
+
+ Duration: +
-
-
-
- - Events: {{backup.handledEvents | sqxKNumber}} - , - - Assets: {{backup.handledAssets | sqxKNumber}} - +
+
+ {{backup.started | sqxISODate}} +
+
+ {{backup | sqxBackupDuration}} +
-
- Download: +
+
+ + Events: {{backup.handledEvents | sqxKNumber}} + , + + Assets: {{backup.handledAssets | sqxKNumber}} + +
+
+ Download: - - Ready - + + Ready + +
+
+
+
-
-
-
-
+ \ No newline at end of file diff --git a/src/Squidex/app/features/settings/pages/backups/backups-page.component.ts b/src/Squidex/app/features/settings/pages/backups/backups-page.component.ts index 75585c0d5..c9b960da0 100644 --- a/src/Squidex/app/features/settings/pages/backups/backups-page.component.ts +++ b/src/Squidex/app/features/settings/pages/backups/backups-page.component.ts @@ -9,13 +9,9 @@ import { Component, OnInit, OnDestroy } from '@angular/core'; import { Observable, Subscription } from 'rxjs'; import { - ApiUrlConfig, AppsState, BackupDto, - BackupsService, - Duration, - DialogService, - ImmutableArray + BackupsState } from '@app/shared'; @Component({ @@ -26,13 +22,9 @@ import { export class BackupsPageComponent implements OnInit, OnDestroy { private loadSubscription: Subscription; - public backups = ImmutableArray.empty(); - constructor( public readonly appsState: AppsState, - private readonly apiUrl: ApiUrlConfig, - private readonly backupsService: BackupsService, - private readonly dialogs: DialogService + public readonly backupsState: BackupsState ) { } @@ -43,36 +35,16 @@ export class BackupsPageComponent implements OnInit, OnDestroy { public ngOnInit() { this.loadSubscription = Observable.timer(0, 2000) - .switchMap(t => this.backupsService.getBackups(this.appsState.appName)) - .subscribe(dtos => { - this.backups = ImmutableArray.of(dtos); - }); + .switchMap(t => this.backupsState.load().onErrorResumeNext()) + .subscribe(); } public startBackup() { - this.backupsService.postBackup(this.appsState.appName) - .subscribe(() => { - this.dialogs.notifyInfo('Backup started, it can take several minutes to complete.'); - }, error => { - this.dialogs.notifyError(error); - }); + this.backupsState.start().onErrorResumeNext().subscribe(); } public deleteBackup(backup: BackupDto) { - this.backupsService.deleteBackup(this.appsState.appName, backup.id) - .subscribe(() => { - this.dialogs.notifyInfo('Backup is about to be deleted.'); - }, error => { - this.dialogs.notifyError(error); - }); - } - - public getDownloadUrl(backup: BackupDto) { - return this.apiUrl.buildUrl(`api/apps/${this.appsState.appName}/backups/${backup.id}`); - } - - public getDuration(backup: BackupDto) { - return Duration.create(backup.started, backup.stopped!); + this.backupsState.delete(backup).onErrorResumeNext().subscribe(); } public trackByBackup(index: number, item: BackupDto) { diff --git a/src/Squidex/app/features/settings/pages/backups/pipes.ts b/src/Squidex/app/features/settings/pages/backups/pipes.ts new file mode 100644 index 000000000..7351ce9a6 --- /dev/null +++ b/src/Squidex/app/features/settings/pages/backups/pipes.ts @@ -0,0 +1,41 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { Pipe, PipeTransform } from '@angular/core'; + +import { + ApiUrlConfig, + AppsState, + BackupDto, + Duration +} from '@app/shared'; + +@Pipe({ + name: 'sqxBackupDuration', + pure: true +}) +export class BackupDurationPipe implements PipeTransform { + public transform(backup: BackupDto) { + return Duration.create(backup.started, backup.stopped!).toString(); + } +} + +@Pipe({ + name: 'sqxBackupDownloadUrl', + pure: true +}) +export class BackupDownloadUrlPipe implements PipeTransform { + constructor( + private readonly apiUrl: ApiUrlConfig, + private readonly appsState: AppsState + ) { + } + + public transform(backup: BackupDto) { + return this.apiUrl.buildUrl(`api/apps/${this.appsState.appName}/backups/${backup.id}`); + } +} \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/pipes/date-time.pipes.spec.ts b/src/Squidex/app/framework/angular/pipes/date-time.pipes.spec.ts index 341e88f6e..00da0249c 100644 --- a/src/Squidex/app/framework/angular/pipes/date-time.pipes.spec.ts +++ b/src/Squidex/app/framework/angular/pipes/date-time.pipes.spec.ts @@ -14,6 +14,7 @@ import { DurationPipe, FromNowPipe, FullDateTimePipe, + ISODatePipe, MonthPipe, ShortDatePipe, ShortTimePipe @@ -121,3 +122,14 @@ describe('ShortTimePipe', () => { expect(actual).toBe(expected); }); }); + +describe('ISODatePipe', () => { + it('should format to short time string', () => { + const pipe = new ISODatePipe(); + + const actual = pipe.transform(dateTime); + const expected = dateTime.toISOString(); + + expect(actual).toBe(expected); + }); +}); \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/pipes/date-time.pipes.ts b/src/Squidex/app/framework/angular/pipes/date-time.pipes.ts index 61234e139..145014c81 100644 --- a/src/Squidex/app/framework/angular/pipes/date-time.pipes.ts +++ b/src/Squidex/app/framework/angular/pipes/date-time.pipes.ts @@ -20,6 +20,16 @@ export class ShortDatePipe implements PipeTransform { } } +@Pipe({ + name: 'sqxISODate', + pure: true +}) +export class ISODatePipe implements PipeTransform { + public transform(value: DateTime): any { + return value.toISOString(); + } +} + @Pipe({ name: 'sqxDate', pure: true diff --git a/src/Squidex/app/framework/module.ts b/src/Squidex/app/framework/module.ts index 70d1772e3..8c1c138b8 100644 --- a/src/Squidex/app/framework/module.ts +++ b/src/Squidex/app/framework/module.ts @@ -35,6 +35,7 @@ import { IgnoreScrollbarDirective, ImageSourceDirective, IndeterminateValueDirective, + ISODatePipe, JscriptEditorComponent, JsonEditorComponent, KeysPipe, @@ -103,6 +104,7 @@ import { IgnoreScrollbarDirective, ImageSourceDirective, IndeterminateValueDirective, + ISODatePipe, JscriptEditorComponent, JsonEditorComponent, KeysPipe, @@ -160,6 +162,7 @@ import { IgnoreScrollbarDirective, ImageSourceDirective, IndeterminateValueDirective, + ISODatePipe, JscriptEditorComponent, JsonEditorComponent, KeysPipe, diff --git a/src/Squidex/app/shared/internal.ts b/src/Squidex/app/shared/internal.ts index 65a93073d..651f9157c 100644 --- a/src/Squidex/app/shared/internal.ts +++ b/src/Squidex/app/shared/internal.ts @@ -40,6 +40,7 @@ export * from './services/users.service'; export * from './state/apps.state'; export * from './state/assets.state'; +export * from './state/backups.state'; export * from './state/clients.state'; export * from './state/contributors.state'; export * from './state/patterns.state'; diff --git a/src/Squidex/app/shared/module.ts b/src/Squidex/app/shared/module.ts index f5fb7ea19..369f26277 100644 --- a/src/Squidex/app/shared/module.ts +++ b/src/Squidex/app/shared/module.ts @@ -32,6 +32,7 @@ import { AuthInterceptor, AuthService, BackupsService, + BackupsState, ClientsState, ContentsService, ContributorsState, @@ -140,6 +141,7 @@ export class SqxSharedModule { AssetsService, AuthService, BackupsService, + BackupsState, ClientsState, ContentsService, ContributorsState, diff --git a/src/Squidex/app/shared/state/backups.state.spec.ts b/src/Squidex/app/shared/state/backups.state.spec.ts new file mode 100644 index 000000000..2badfc29f --- /dev/null +++ b/src/Squidex/app/shared/state/backups.state.spec.ts @@ -0,0 +1,75 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { Observable } from 'rxjs'; +import { IMock, It, Mock, Times } from 'typemoq'; + +import { + AppsState, + BackupDto, + BackupsState, + BackupsService, + DateTime, + DialogService + } from '@app/shared'; + +describe('BackupsState', () => { + const app = 'my-app'; + + const oldBackups = [ + new BackupDto('id1', DateTime.now(), null, 1, 1, false), + new BackupDto('id2', DateTime.now(), null, 2, 2, false) + ]; + + let dialogs: IMock; + let appsState: IMock; + let backupsService: IMock; + let backupsState: BackupsState; + + beforeEach(() => { + dialogs = Mock.ofType(); + + appsState = Mock.ofType(); + + appsState.setup(x => x.appName) + .returns(() => app); + + backupsService = Mock.ofType(); + + backupsService.setup(x => x.getBackups(app)) + .returns(() => Observable.of(oldBackups)); + + backupsState = new BackupsState(appsState.object, backupsService.object, dialogs.object); + backupsState.load().subscribe(); + }); + + it('should load clients', () => { + expect(backupsState.snapshot.backups.values).toEqual(oldBackups); + }); + + it('should not add backup to snapshot', () => { + backupsService.setup(x => x.postBackup(app)) + .returns(() => Observable.of({})); + + backupsState.start().subscribe(); + + expect(backupsState.snapshot.backups.length).toBe(2); + + dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.once()); + }); + + it('should not remove backup from snapshot', () => { + backupsService.setup(x => x.deleteBackup(app, oldBackups[0].id)) + .returns(() => Observable.of({})); + + backupsState.delete(oldBackups[0]).subscribe(); + + expect(backupsState.snapshot.backups.length).toBe(2); + + dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.once()); + }); +}); \ No newline at end of file diff --git a/src/Squidex/app/shared/state/backups.state.ts b/src/Squidex/app/shared/state/backups.state.ts new file mode 100644 index 000000000..a08564b1b --- /dev/null +++ b/src/Squidex/app/shared/state/backups.state.ts @@ -0,0 +1,69 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; + +import '@app/framework/utils/rxjs-extensions'; + +import { + DialogService, + ImmutableArray, + State +} from '@app/framework'; + +import { AppsState } from './apps.state'; + +import { BackupDto, BackupsService } from './../services/backups.service'; + +interface Snapshot { + backups: ImmutableArray; +} + +@Injectable() +export class BackupsState extends State { + public backups = + this.changes.map(x => x.backups); + + public maxBackupsReached = + this.changes.map(x => x.backups.length >= 10); + + constructor( + private readonly appsState: AppsState, + private readonly backupsService: BackupsService, + private readonly dialogs: DialogService + ) { + super({ backups: ImmutableArray.empty() }); + } + + public load(): Observable { + return this.backupsService.getBackups(this.appName) + .do(dtos => { + this.next({ backups: ImmutableArray.of(dtos) }); + }); + } + + public start(): Observable { + return this.backupsService.postBackup(this.appsState.appName) + .do(() => { + this.dialogs.notifyInfo('Backup started, it can take several minutes to complete.'); + }) + .notify(this.dialogs); + } + + public delete(backup: BackupDto): Observable { + return this.backupsService.deleteBackup(this.appsState.appName, backup.id) + .do(() => { + this.dialogs.notifyInfo('Backup is about to be deleted.'); + }) + .notify(this.dialogs); + } + + private get appName() { + return this.appsState.appName; + } +} \ No newline at end of file