Browse Source

Continued

pull/356/head
Sebastian Stehle 7 years ago
parent
commit
d8628ccb5c
  1. 12
      src/Squidex/.vscode/settings.json
  2. 2
      src/Squidex/app-config/webpack.config.js
  3. 4
      src/Squidex/app-config/webpack.test.coverage.js
  4. 2
      src/Squidex/app/features/administration/pages/restore/restore-page.component.html
  5. 25
      src/Squidex/app/features/administration/pages/restore/restore-page.component.ts
  6. 11
      src/Squidex/app/features/administration/services/event-consumers.service.ts
  7. 35
      src/Squidex/app/features/administration/services/users.service.ts
  8. 83
      src/Squidex/app/features/administration/state/event-consumers.state.ts
  9. 139
      src/Squidex/app/features/administration/state/users.state.ts
  10. 9
      src/Squidex/app/features/assets/pages/assets-filters-page.component.ts
  11. 15
      src/Squidex/app/features/assets/pages/assets-page.component.ts
  12. 5
      src/Squidex/app/features/content/pages/content/content-history-page.component.ts
  13. 2
      src/Squidex/app/features/content/pages/content/content-page.component.ts
  14. 3
      src/Squidex/app/features/content/pages/contents/contents-filters-page.component.ts
  15. 18
      src/Squidex/app/features/content/pages/contents/contents-page.component.ts
  16. 3
      src/Squidex/app/features/content/pages/schemas/schemas-page.component.ts
  17. 11
      src/Squidex/app/features/content/shared/contents-selector.component.ts
  18. 13
      src/Squidex/app/features/rules/pages/events/rule-events-page.component.ts
  19. 13
      src/Squidex/app/features/schemas/pages/schema/field.component.ts
  20. 3
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts
  21. 4
      src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts
  22. 3
      src/Squidex/app/features/settings/pages/languages/language.component.ts
  23. 5
      src/Squidex/app/features/settings/pages/languages/languages-page.component.ts
  24. 3
      src/Squidex/app/features/settings/pages/patterns/pattern.component.ts
  25. 5
      src/Squidex/app/features/settings/pages/patterns/patterns-page.component.ts
  26. 7
      src/Squidex/app/features/settings/pages/plans/plans-page.component.ts
  27. 3
      src/Squidex/app/features/settings/pages/roles/role.component.ts
  28. 5
      src/Squidex/app/features/settings/pages/roles/roles-page.component.ts
  29. 2
      src/Squidex/app/framework/angular/http/http-extensions.ts
  30. 59
      src/Squidex/app/framework/utils/rxjs-extensions.ts
  31. 11
      src/Squidex/app/framework/utils/version.spec.ts
  32. 12
      src/Squidex/app/framework/utils/version.ts
  33. 17
      src/Squidex/app/shared/components/asset-uploader.component.ts
  34. 15
      src/Squidex/app/shared/components/asset.component.ts
  35. 25
      src/Squidex/app/shared/components/assets-list.component.ts
  36. 9
      src/Squidex/app/shared/components/assets-selector.component.ts
  37. 7
      src/Squidex/app/shared/components/history.component.ts
  38. 3
      src/Squidex/app/shared/components/schema-category.component.ts
  39. 8
      src/Squidex/app/shared/services/app-languages.service.spec.ts
  40. 31
      src/Squidex/app/shared/services/app-languages.service.ts
  41. 30
      src/Squidex/app/shared/services/apps.service.ts
  42. 22
      src/Squidex/app/shared/services/assets.service.ts
  43. 27
      src/Squidex/app/shared/services/backups.service.ts
  44. 8
      src/Squidex/app/shared/services/clients.service.spec.ts
  45. 31
      src/Squidex/app/shared/services/clients.service.ts
  46. 24
      src/Squidex/app/shared/services/comments.service.ts
  47. 50
      src/Squidex/app/shared/services/contents.service.ts
  48. 15
      src/Squidex/app/shared/services/contributors.service.spec.ts
  49. 38
      src/Squidex/app/shared/services/contributors.service.ts
  50. 11
      src/Squidex/app/shared/services/history.service.ts
  51. 21
      src/Squidex/app/shared/services/languages.service.ts
  52. 20
      src/Squidex/app/shared/services/news.service.ts
  53. 10
      src/Squidex/app/shared/services/patterns.service.spec.ts
  54. 34
      src/Squidex/app/shared/services/patterns.service.ts
  55. 21
      src/Squidex/app/shared/services/plans.service.spec.ts
  56. 51
      src/Squidex/app/shared/services/plans.service.ts
  57. 7
      src/Squidex/app/shared/services/roles.service.spec.ts
  58. 29
      src/Squidex/app/shared/services/roles.service.ts
  59. 42
      src/Squidex/app/shared/services/rules.service.ts
  60. 36
      src/Squidex/app/shared/services/schemas.service.ts
  61. 4
      src/Squidex/app/shared/services/translations.service.ts
  62. 39
      src/Squidex/app/shared/services/usages.service.ts
  63. 21
      src/Squidex/app/shared/services/users.service.ts
  64. 20
      src/Squidex/app/shared/state/apps.state.spec.ts
  65. 109
      src/Squidex/app/shared/state/apps.state.ts
  66. 4
      src/Squidex/app/shared/state/assets.state.spec.ts
  67. 6
      src/Squidex/app/shared/state/assets.state.ts
  68. 65
      src/Squidex/app/shared/state/backups.state.ts
  69. 15
      src/Squidex/app/shared/state/clients.state.spec.ts
  70. 97
      src/Squidex/app/shared/state/clients.state.ts
  71. 124
      src/Squidex/app/shared/state/comments.state.ts
  72. 19
      src/Squidex/app/shared/state/contents.state.ts
  73. 15
      src/Squidex/app/shared/state/contributors.state.spec.ts
  74. 79
      src/Squidex/app/shared/state/contributors.state.ts
  75. 11
      src/Squidex/app/shared/state/languages.state.spec.ts
  76. 63
      src/Squidex/app/shared/state/languages.state.ts
  77. 15
      src/Squidex/app/shared/state/patterns.state.spec.ts
  78. 105
      src/Squidex/app/shared/state/patterns.state.ts
  79. 147
      src/Squidex/app/shared/state/plans.state.spec.ts
  80. 18
      src/Squidex/app/shared/state/plans.state.ts
  81. 15
      src/Squidex/app/shared/state/roles.state.spec.ts
  82. 40
      src/Squidex/app/shared/state/roles.state.ts
  83. 8
      src/Squidex/app/shared/state/rule-events.state.ts
  84. 12
      src/Squidex/app/shared/state/rules.state.spec.ts
  85. 143
      src/Squidex/app/shared/state/rules.state.ts
  86. 630
      src/Squidex/app/shared/state/schemas.state.spec.ts
  87. 179
      src/Squidex/app/shared/state/schemas.state.ts
  88. 10700
      src/Squidex/package-lock.json
  89. 93
      src/Squidex/package.json
  90. 8
      src/Squidex/tsconfig.json

12
src/Squidex/.vscode/settings.json

@ -7,7 +7,6 @@
// Configure glob patterns for excluding files and folders.
"files.exclude": {
"_test-output": true,
"**/node_modules": true,
"**/Assets": true,
"**/artifacts": true,
@ -23,10 +22,15 @@
"**/*.user": true,
"**/*.xproj": true,
"**/*.gitattributes": true,
"appsetttings.Development.json",
"appsetttings.Production.json",
"appsetttings.Development.json": true,
"appsetttings.Production.json": true,
".awcache": true,
".vs:": true,
".vscode:": true
}
},
"coverage-gutters.coverageFileNames": [
"_test-output/coverage/lcov.info"
],
"coverage-gutters.showLineCoverage": true
}

2
src/Squidex/app-config/webpack.config.js

@ -57,7 +57,7 @@ module.exports = {
}, {
test: /\.ts$/,
use: [{
loader: 'awesome-typescript-loader', options: { useCache: true, useBabel: true }
loader: 'awesome-typescript-loader'
}, {
loader: 'angular-router-loader'
}, {

4
src/Squidex/app-config/webpack.test.coverage.js

@ -16,7 +16,7 @@ module.exports = webpackMerge(testConfig, {
rules: [{
test: /\.ts$/,
use: [{
loader: 'awesome-typescript-loader'
loader: 'ts-loader'
}],
include: [/\.(e2e|spec)\.ts$/],
@ -25,7 +25,7 @@ module.exports = webpackMerge(testConfig, {
use: [{
loader: 'istanbul-instrumenter-loader'
}, {
loader: 'awesome-typescript-loader'
loader: 'ts-loader'
}, {
loader: 'angular-router-loader'
}, {

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

@ -6,7 +6,7 @@
</ng-container>
<ng-container content>
<div class="card section" *ngIf="restoreJob; let job">
<div class="card section" *ngIf="restoreJob | async; let job">
<div class="card-header">
<div class="row no-gutters">
<div class="col-auto pr-2">

25
src/Squidex/app/features/administration/pages/restore/restore-page.component.ts

@ -5,18 +5,16 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Component, OnInit } from '@angular/core';
import { Component } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { timer } from 'rxjs';
import { onErrorResumeNext, switchMap } from 'rxjs/operators';
import {
AuthService,
BackupsService,
DialogService,
ResourceOwner,
RestoreDto,
RestoreForm
RestoreForm,
switchSafe
} from '@app/shared';
@Component({
@ -24,27 +22,18 @@ import {
styleUrls: ['./restore-page.component.scss'],
templateUrl: './restore-page.component.html'
})
export class RestorePageComponent extends ResourceOwner implements OnInit {
public restoreJob: RestoreDto | null;
export class RestorePageComponent {
public restoreForm = new RestoreForm(this.formBuilder);
public restoreJob =
timer(0, 2000).pipe(switchSafe(() => this.backupsService.getRestore()));
constructor(
public readonly authState: AuthService,
private readonly backupsService: BackupsService,
private readonly dialogs: DialogService,
private readonly formBuilder: FormBuilder
) {
super();
}
public ngOnInit() {
this.own(
timer(0, 2000).pipe(switchMap(() => this.backupsService.getRestore().pipe(onErrorResumeNext())))
.subscribe(job => {
if (job) {
this.restoreJob = job;
}
}));
}
public restore() {

11
src/Squidex/app/features/administration/services/event-consumers.service.ts

@ -40,15 +40,16 @@ export class EventConsumersService {
const url = this.apiUrl.buildUrl('/api/event-consumers');
return this.http.get<any[]>(url).pipe(
map(response => {
return response.map(item => {
return new EventConsumerDto(
map(body => {
const eventConsumers = body.map(item =>
new EventConsumerDto(
item.name,
item.isStopped,
item.isResetting,
item.error,
item.position);
});
item.position));
return eventConsumers;
}),
pretifyError('Failed to load event consumers. Please reload.'));
}

35
src/Squidex/app/features/administration/services/users.service.ts

@ -61,17 +61,16 @@ export class UsersService {
const url = this.apiUrl.buildUrl(`api/user-management?take=${take}&skip=${skip}&query=${query || ''}`);
return this.http.get<{ total: number, items: any[] }>(url).pipe(
map(response => {
const users = response.items.map(item => {
return new UserDto(
map(body => {
const users = body.items.map(item =>
new UserDto(
item.id,
item.email,
item.displayName,
item.permissions,
item.isLocked);
});
item.isLocked));
return new UsersDto(response.total, users);
return new UsersDto(body.total, users);
}),
pretifyError('Failed to load users. Please reload.'));
}
@ -80,13 +79,15 @@ export class UsersService {
const url = this.apiUrl.buildUrl(`api/user-management/${id}`);
return this.http.get<any>(url).pipe(
map(response => {
return new UserDto(
response.id,
response.email,
response.displayName,
response.permissions,
response.isLocked);
map(body => {
const user = new UserDto(
body.id,
body.email,
body.displayName,
body.permissions,
body.isLocked);
return user;
}),
pretifyError('Failed to load user. Please reload.'));
}
@ -95,13 +96,15 @@ export class UsersService {
const url = this.apiUrl.buildUrl('api/user-management');
return this.http.post<any>(url, dto).pipe(
map(response => {
return new UserDto(
response.id,
map(body => {
const user = new UserDto(
body.id,
dto.email,
dto.displayName,
dto.permissions,
false);
return user;
}),
pretifyError('Failed to create user. Please reload.'));
}

83
src/Squidex/app/features/administration/state/event-consumers.state.ts

@ -7,11 +7,12 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { distinctUntilChanged, map, share } from 'rxjs/operators';
import { distinctUntilChanged, map, tap } from 'rxjs/operators';
import {
DialogService,
ImmutableArray,
shareSubscribed,
State
} from '@app/shared';
@ -49,66 +50,46 @@ export class EventConsumersState extends State<Snapshot> {
this.resetState();
}
const http$ =
this.eventConsumersService.getEventConsumers().pipe(
share());
return this.eventConsumersService.getEventConsumers().pipe(
tap(payload => {
if (isReload && !silent) {
this.dialogs.notifyInfo('Event Consumers reloaded.');
}
http$.subscribe(response => {
if (isReload && !silent) {
this.dialogs.notifyInfo('Event Consumers reloaded.');
}
const eventConsumers = ImmutableArray.of(payload);
const eventConsumers = ImmutableArray.of(response);
this.next(s => {
return { ...s, eventConsumers, isLoaded: true };
});
}, error => {
if (!silent) {
this.dialogs.notifyError(error);
}
});
return http$;
this.next(s => {
return { ...s, eventConsumers, isLoaded: true };
});
}),
shareSubscribed(this.dialogs, { silent }));
}
public start(eventConsumer: EventConsumerDto): Observable<any> {
const http$ =
this.eventConsumersService.putStart(eventConsumer.name).pipe(
map(() => setStopped(eventConsumer, false), share()));
this.updateState(http$);
return http$;
return this.eventConsumersService.putStart(eventConsumer.name).pipe(
map(() => setStopped(eventConsumer, false)),
tap(updated => {
this.replaceEventConsumer(updated);
}),
shareSubscribed(this.dialogs));
}
public stop(eventConsumer: EventConsumerDto): Observable<EventConsumerDto> {
const http$ =
this.eventConsumersService.putStop(eventConsumer.name).pipe(
map(() => setStopped(eventConsumer, true), share()));
this.updateState(http$);
return http$;
}
public reset(eventConsumer: EventConsumerDto): Observable<any> {
const http$ =
this.eventConsumersService.putReset(eventConsumer.name).pipe(
map(() => reset(eventConsumer), share()));
this.updateState(http$);
return http$;
return this.eventConsumersService.putStop(eventConsumer.name).pipe(
map(() => setStopped(eventConsumer, true)),
tap(updated => {
this.replaceEventConsumer(updated);
}),
shareSubscribed(this.dialogs));
}
private updateState(http$: Observable<EventConsumerDto>) {
http$.subscribe(updated => {
this.replaceEventConsumer(updated);
}, error => {
this.dialogs.notifyError(error);
});
public reset(eventConsumer: EventConsumerDto): Observable<EventConsumerDto> {
return this.eventConsumersService.putReset(eventConsumer.name).pipe(
map(() => reset(eventConsumer)),
tap(updated => {
this.replaceEventConsumer(updated);
}),
shareSubscribed(this.dialogs));
}
private replaceEventConsumer(eventConsumer: EventConsumerDto) {

139
src/Squidex/app/features/administration/state/users.state.ts

@ -7,7 +7,7 @@
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { catchError, distinctUntilChanged, map, share } from 'rxjs/operators';
import { catchError, distinctUntilChanged, map, tap } from 'rxjs/operators';
import '@app/framework/utils/rxjs-extensions';
@ -16,6 +16,7 @@ import {
DialogService,
ImmutableArray,
Pager,
shareSubscribed,
State
} from '@app/shared';
@ -81,15 +82,11 @@ export class UsersState extends State<Snapshot> {
}
public select(id: string | null): Observable<SnapshotUser | null> {
const http$ =
this.loadUser(id).pipe(
share());
http$.subscribe(selectedUser => {
this.next(s => ({ ...s, selectedUser }));
});
return http$;
return this.loadUser(id).pipe(
tap(selectedUser => {
this.next(s => ({ ...s, selectedUser }));
}),
shareSubscribed(this.dialogs, { silent: true }));
}
private loadUser(id: string | null) {
@ -115,83 +112,69 @@ export class UsersState extends State<Snapshot> {
}
private loadInternal(isReload = false): Observable<any> {
const http$ =
this.usersService.getUsers(
return this.usersService.getUsers(
this.snapshot.usersPager.pageSize,
this.snapshot.usersPager.skip,
this.snapshot.usersQuery).pipe(
share());
http$.subscribe(response => {
if (isReload) {
this.dialogs.notifyInfo('Users reloaded.');
}
this.next(s => {
const usersPager = s.usersPager.setCount(response.total);
const users = ImmutableArray.of(response.items.map(x => this.createUser(x)));
let selectedUser = s.selectedUser;
if (selectedUser) {
selectedUser = users.find(x => x.user.id === selectedUser!.user.id) || selectedUser;
tap(({ total, items }) => {
if (isReload) {
this.dialogs.notifyInfo('Users reloaded.');
}
return { ...s, users, usersPager, selectedUser, isLoaded: true };
});
this.next(s => {
const usersPager = s.usersPager.setCount(total);
const users = ImmutableArray.of(items.map(x => this.createUser(x)));
}, error => {
this.dialogs.notifyError(error);
});
let selectedUser = s.selectedUser;
if (selectedUser) {
selectedUser = users.find(x => x.user.id === selectedUser!.user.id) || selectedUser;
}
return http$;
return { ...s, users, usersPager, selectedUser, isLoaded: true };
});
}),
shareSubscribed(this.dialogs));
}
public create(request: CreateUserDto): Observable<UserDto> {
const http$ =
this.usersService.postUser(request).pipe(
share());
http$.subscribe(dto => {
this.next(s => {
const users = s.users.pushFront(this.createUser(dto));
const usersPager = s.usersPager.incrementCount();
return { ...s, users, usersPager };
});
});
return this.usersService.postUser(request).pipe(
tap(payload => {
this.next(s => {
const users = s.users.pushFront(this.createUser(payload));
const usersPager = s.usersPager.incrementCount();
return http$;
return { ...s, users, usersPager };
});
}),
shareSubscribed(this.dialogs, { silent: true }));
}
public update(user: UserDto, request: UpdateUserDto): Observable<UserDto> {
const http$ =
this.usersService.putUser(user.id, request).pipe(
map(() => update(user, request)), share());
this.updateState(http$, false);
return http$;
return this.usersService.putUser(user.id, request).pipe(
map(() => update(user, request)),
tap(updated => {
this.replaceUser(updated);
}),
shareSubscribed(this.dialogs));
}
public lock(user: UserDto): Observable<UserDto> {
const http$ =
this.usersService.lockUser(user.id).pipe(
map(() => setLocked(user, true)), share());
this.updateState(http$, true);
return http$;
return this.usersService.lockUser(user.id).pipe(
map(() => setLocked(user, true)),
tap(updated => {
this.replaceUser(updated);
}),
shareSubscribed(this.dialogs));
}
public unlock(user: UserDto): Observable<UserDto> {
const http$ =
this.usersService.unlockUser(user.id).pipe(
map(() => setLocked(user, false)), share());
this.updateState(http$, true);
return http$;
return this.usersService.unlockUser(user.id).pipe(
map(() => setLocked(user, false)),
tap(updated => {
this.replaceUser(updated);
}),
shareSubscribed(this.dialogs));
}
public search(query: string): Observable<UsersResult> {
@ -214,7 +197,7 @@ export class UsersState extends State<Snapshot> {
private replaceUser(user: UserDto) {
return this.next(s => {
const users = s.users.map(u => u.user.id === user.id ? this.createUser(user, u) : u);
const users = s.users.map(u => u.user.id === user.id ? this.createUser(user) : u);
const selectedUser =
s.selectedUser &&
@ -230,24 +213,8 @@ export class UsersState extends State<Snapshot> {
return this.authState.user!.id;
}
private updateState(stream: Observable<UserDto>, notify: boolean) {
stream.subscribe(dto => {
this.replaceUser(dto);
}, error => {
if (notify) {
this.dialogs.notifyError(error);
}
});
}
private createUser(user: UserDto, current?: SnapshotUser): SnapshotUser {
if (!user) {
return null!;
} else if (current && current.user === user) {
return current;
} else {
return { user, isCurrentUser: user.id === this.userId };
}
private createUser(user: UserDto): SnapshotUser {
return { user, isCurrentUser: user.id === this.userId };
}
}

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

@ -6,7 +6,6 @@
*/
import { Component } from '@angular/core';
import { onErrorResumeNext } from 'rxjs/operators';
import {
AssetsState,
@ -29,19 +28,19 @@ export class AssetsFiltersPageComponent {
}
public search(query: string) {
this.assetsState.search(query).pipe(onErrorResumeNext()).subscribe();
this.assetsState.search(query);
}
public selectTags(tags: string[]) {
this.assetsState.selectTags(tags).pipe(onErrorResumeNext()).subscribe();
this.assetsState.selectTags(tags);
}
public toggleTag(tag: string) {
this.assetsState.toggleTag(tag).pipe(onErrorResumeNext()).subscribe();
this.assetsState.toggleTag(tag);
}
public resetTags() {
this.assetsState.resetTags().pipe(onErrorResumeNext()).subscribe();
this.assetsState.resetTags();
}
public isSelectedQuery(query: string) {

15
src/Squidex/app/features/assets/pages/assets-page.component.ts

@ -7,7 +7,6 @@
import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { onErrorResumeNext } from 'rxjs/operators';
import {
AppsState,
@ -45,7 +44,7 @@ export class AssetsPageComponent extends ResourceOwner implements OnInit {
}
public ngOnInit() {
this.assetsState.load().pipe(onErrorResumeNext()).subscribe();
this.assetsState.load();
this.own(
this.assetsState.assetsQuery
@ -55,27 +54,27 @@ export class AssetsPageComponent extends ResourceOwner implements OnInit {
}
public reload() {
this.assetsState.load(true).pipe(onErrorResumeNext()).subscribe();
this.assetsState.load(true);
}
public search() {
this.assetsState.search(this.filter.apiFilter).pipe(onErrorResumeNext()).subscribe();
this.assetsState.search(this.filter.apiFilter);
}
public selectTags(tags: string[]) {
this.assetsState.selectTags(tags).pipe(onErrorResumeNext()).subscribe();
this.assetsState.selectTags(tags);
}
public toggleTag(tag: string) {
this.assetsState.toggleTag(tag).pipe(onErrorResumeNext()).subscribe();
this.assetsState.toggleTag(tag);
}
public goNext() {
this.assetsState.goNext().pipe(onErrorResumeNext()).subscribe();
this.assetsState.goNext();
}
public goPrev() {
this.assetsState.goPrev().pipe(onErrorResumeNext()).subscribe();
this.assetsState.goPrev();
}
public changeView(isListView: boolean) {

5
src/Squidex/app/features/content/pages/content/content-history-page.component.ts

@ -8,7 +8,7 @@
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { merge, Observable, timer } from 'rxjs';
import { delay, onErrorResumeNext, switchMap } from 'rxjs/operators';
import { delay } from 'rxjs/operators';
import {
allParams,
@ -17,6 +17,7 @@ import {
HistoryEventDto,
HistoryService,
MessageBus,
switchSafe,
Version
} from '@app/shared';
@ -51,7 +52,7 @@ export class ContentHistoryPageComponent {
timer(0, 10000),
this.messageBus.of(HistoryChannelUpdated).pipe(delay(1000))
).pipe(
switchMap(() => this.historyService.getHistory(this.appsState.appName, this.channel).pipe(onErrorResumeNext())));
switchSafe(() => this.historyService.getHistory(this.appsState.appName, this.channel)));
constructor(
private readonly appsState: AppsState,

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

@ -171,7 +171,7 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD
}
public discardChanges() {
this.contentsState.discardChanges(this.content).pipe(onErrorResumeNext()).subscribe();
this.contentsState.discardChanges(this.content);
}
public publish() {

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

@ -6,7 +6,6 @@
*/
import { Component, OnInit } from '@angular/core';
import { onErrorResumeNext } from 'rxjs/operators';
import {
ContentsState,
@ -43,7 +42,7 @@ export class ContentsFiltersPageComponent extends ResourceOwner implements OnIni
}
public search(query: string) {
this.contentsState.search(query).pipe(onErrorResumeNext()).subscribe();
this.contentsState.search(query);
}
public isSelectedQuery(query: string) {

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

@ -77,7 +77,7 @@ export class ContentsPageComponent extends ResourceOwner implements OnInit {
this.schema = schema!;
this.schemaQueries = new Queries(this.uiState, `schemas.${this.schema.name}`);
this.contentsState.init().pipe(onErrorResumeNext()).subscribe();
this.contentsState.init();
}));
this.own(
@ -103,15 +103,15 @@ export class ContentsPageComponent extends ResourceOwner implements OnInit {
}
public reload() {
this.contentsState.load(true).pipe(onErrorResumeNext()).subscribe();
this.contentsState.load(true);
}
public deleteSelected() {
this.contentsState.deleteMany(this.selectItems()).pipe(onErrorResumeNext()).subscribe();
this.contentsState.deleteMany(this.selectItems());
}
public delete(content: ContentDto) {
this.contentsState.deleteMany([content]).pipe(onErrorResumeNext()).subscribe();
this.contentsState.deleteMany([content]);
}
public publish(content: ContentDto) {
@ -147,7 +147,7 @@ export class ContentsPageComponent extends ResourceOwner implements OnInit {
}
public clone(content: ContentDto) {
this.contentsState.create(content.dataDraft, false).pipe(onErrorResumeNext()).subscribe();
this.contentsState.create(content.dataDraft, false);
}
private changeContentItems(contents: ContentDto[], action: string) {
@ -165,19 +165,19 @@ export class ContentsPageComponent extends ResourceOwner implements OnInit {
}
public goArchive(isArchive: boolean) {
this.contentsState.goArchive(isArchive).pipe(onErrorResumeNext()).subscribe();
this.contentsState.goArchive(isArchive);
}
public goPrev() {
this.contentsState.goPrev().pipe(onErrorResumeNext()).subscribe();
this.contentsState.goPrev();
}
public goNext() {
this.contentsState.goNext().pipe(onErrorResumeNext()).subscribe();
this.contentsState.goNext();
}
public search() {
this.contentsState.search(this.filter.apiFilter).pipe(onErrorResumeNext()).subscribe();
this.contentsState.search(this.filter.apiFilter);
}
public selectLanguage(language: AppLanguageDto) {

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

@ -7,7 +7,6 @@
import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { onErrorResumeNext } from 'rxjs/operators';
import { AppsState, SchemasState } from '@app/shared';
@ -26,7 +25,7 @@ export class SchemasPageComponent implements OnInit {
}
public ngOnInit() {
this.schemasState.load().pipe(onErrorResumeNext()).subscribe();
this.schemasState.load();
}
public trackByCategory(index: number, category: string) {

11
src/Squidex/app/features/content/shared/contents-selector.component.ts

@ -6,7 +6,6 @@
*/
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { onErrorResumeNext } from 'rxjs/operators';
import {
ContentDto,
@ -54,23 +53,23 @@ export class ContentsSelectorComponent implements OnInit {
public ngOnInit() {
this.contentsState.schema = this.schema;
this.contentsState.load().pipe(onErrorResumeNext()).subscribe();
this.contentsState.load();
}
public reload() {
this.contentsState.load(true).pipe(onErrorResumeNext()).subscribe();
this.contentsState.load(true);
}
public search() {
this.contentsState.search(this.filter.apiFilter).pipe(onErrorResumeNext()).subscribe();
this.contentsState.search(this.filter.apiFilter);
}
public goNext() {
this.contentsState.goNext().pipe(onErrorResumeNext()).subscribe();
this.contentsState.goNext();
}
public goPrev() {
this.contentsState.goPrev().pipe(onErrorResumeNext()).subscribe();
this.contentsState.goPrev();
}
public isItemSelected(content: ContentDto) {

13
src/Squidex/app/features/rules/pages/events/rule-events-page.component.ts

@ -6,7 +6,6 @@
*/
import { Component, OnInit } from '@angular/core';
import { onErrorResumeNext } from 'rxjs/operators';
import {
AppsState,
@ -29,27 +28,27 @@ export class RuleEventsPageComponent implements OnInit {
}
public ngOnInit() {
this.ruleEventsState.load().pipe(onErrorResumeNext()).subscribe();
this.ruleEventsState.load();
}
public reload() {
this.ruleEventsState.load(true).pipe(onErrorResumeNext()).subscribe();
this.ruleEventsState.load(true);
}
public goNext() {
this.ruleEventsState.goNext().pipe(onErrorResumeNext()).subscribe();
this.ruleEventsState.goNext();
}
public goPrev() {
this.ruleEventsState.goPrev().pipe(onErrorResumeNext()).subscribe();
this.ruleEventsState.goPrev();
}
public enqueue(event: RuleEventDto) {
this.ruleEventsState.enqueue(event).pipe(onErrorResumeNext()).subscribe();
this.ruleEventsState.enqueue(event);
}
public cancel(event: RuleEventDto) {
this.ruleEventsState.cancel(event).pipe(onErrorResumeNext()).subscribe();
this.ruleEventsState.cancel(event);
}
public selectEvent(id: string) {

13
src/Squidex/app/features/schemas/pages/schema/field.component.ts

@ -7,7 +7,6 @@
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { onErrorResumeNext } from 'rxjs/operators';
import {
createProperties,
@ -82,23 +81,23 @@ export class FieldComponent implements OnChanges {
}
public deleteField() {
this.schemasState.deleteField(this.schema, this.field).pipe(onErrorResumeNext()).subscribe();
this.schemasState.deleteField(this.schema, this.field);
}
public enableField() {
this.schemasState.enableField(this.schema, this.field).pipe(onErrorResumeNext()).subscribe();
this.schemasState.enableField(this.schema, this.field);
}
public disableField() {
this.schemasState.disableField(this.schema, this.field).pipe(onErrorResumeNext()).subscribe();
this.schemasState.disableField(this.schema, this.field);
}
public showField() {
this.schemasState.showField(this.schema, this.field).pipe(onErrorResumeNext()).subscribe();
this.schemasState.showField(this.schema, this.field);
}
public hideField() {
this.schemasState.hideField(this.schema, this.field).pipe(onErrorResumeNext()).subscribe();
this.schemasState.hideField(this.schema, this.field);
}
public sortFields(fields: NestedFieldDto[]) {
@ -106,7 +105,7 @@ export class FieldComponent implements OnChanges {
}
public lockField() {
this.schemasState.lockField(this.schema, this.field).pipe(onErrorResumeNext()).subscribe();
this.schemasState.lockField(this.schema, this.field);
}
public trackByField(index: number, field: NestedFieldDto) {

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

@ -9,7 +9,6 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { onErrorResumeNext } from 'rxjs/operators';
import {
AppsState,
@ -66,7 +65,7 @@ export class SchemaPageComponent extends ResourceOwner implements OnInit {
}
public ngOnInit() {
this.patternsState.load().pipe(onErrorResumeNext()).subscribe();
this.patternsState.load();
this.own(
this.schemasState.selectedSchema

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

@ -8,7 +8,7 @@
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormControl } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { map, onErrorResumeNext } from 'rxjs/operators';
import { map } from 'rxjs/operators';
import {
AppsState,
@ -63,7 +63,7 @@ export class SchemasPageComponent extends ResourceOwner implements OnInit {
}
}));
this.schemasState.load().pipe(onErrorResumeNext()).subscribe();
this.schemasState.load();
}
public removeCategory(name: string) {

3
src/Squidex/app/features/settings/pages/languages/language.component.ts

@ -7,7 +7,6 @@
import { Component, Input, OnChanges } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { onErrorResumeNext } from 'rxjs/operators';
import {
AppLanguageDto,
@ -56,7 +55,7 @@ export class LanguageComponent implements OnChanges {
}
public remove() {
this.languagesState.remove(this.language).pipe(onErrorResumeNext()).subscribe();
this.languagesState.remove(this.language);
}
public save() {

5
src/Squidex/app/features/settings/pages/languages/languages-page.component.ts

@ -7,7 +7,6 @@
import { Component, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { onErrorResumeNext } from 'rxjs/operators';
import {
AddLanguageForm,
@ -42,11 +41,11 @@ export class LanguagesPageComponent extends ResourceOwner implements OnInit {
}
}));
this.languagesState.load().pipe(onErrorResumeNext()).subscribe();
this.languagesState.load();
}
public reload() {
this.languagesState.load(true).pipe(onErrorResumeNext()).subscribe();
this.languagesState.load(true);
}
public addLanguage() {

3
src/Squidex/app/features/settings/pages/patterns/pattern.component.ts

@ -7,7 +7,6 @@
import { Component, Input, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { onErrorResumeNext } from 'rxjs/operators';
import {
EditPatternForm,
@ -41,7 +40,7 @@ export class PatternComponent implements OnInit {
}
public delete() {
this.patternsState.delete(this.pattern).pipe(onErrorResumeNext()).subscribe();
this.patternsState.delete(this.pattern);
}
public save() {

5
src/Squidex/app/features/settings/pages/patterns/patterns-page.component.ts

@ -6,7 +6,6 @@
*/
import { Component, OnInit } from '@angular/core';
import { onErrorResumeNext } from 'rxjs/operators';
import {
AppsState,
@ -27,11 +26,11 @@ export class PatternsPageComponent implements OnInit {
}
public ngOnInit() {
this.patternsState.load().pipe(onErrorResumeNext()).subscribe();
this.patternsState.load();
}
public reload() {
this.patternsState.load(true).pipe(onErrorResumeNext()).subscribe();
this.patternsState.load(true);
}
public trackByPattern(index: number, pattern: PatternDto) {

7
src/Squidex/app/features/settings/pages/plans/plans-page.component.ts

@ -7,7 +7,6 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { onErrorResumeNext } from 'rxjs/operators';
import {
ApiUrlConfig,
@ -39,15 +38,15 @@ export class PlansPageComponent implements OnInit {
this.overridePlanId = params['planId'];
}).unsubscribe();
this.plansState.load(false, this.overridePlanId).pipe(onErrorResumeNext()).subscribe();
this.plansState.load(false, this.overridePlanId);
}
public reload() {
this.plansState.load(true, this.overridePlanId).pipe(onErrorResumeNext()).subscribe();
this.plansState.load(true, this.overridePlanId);
}
public change(planId: string) {
this.plansState.change(planId).pipe(onErrorResumeNext()).subscribe();
this.plansState.change(planId);
}
public trackByPlan(index: number, planInfo: { plan: PlanDto }) {

3
src/Squidex/app/features/settings/pages/roles/role.component.ts

@ -7,7 +7,6 @@
import { Component, Input, OnChanges, ViewChild } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { onErrorResumeNext } from 'rxjs/operators';
import {
AddPermissionForm,
@ -76,7 +75,7 @@ export class RoleComponent implements OnChanges {
}
public remove() {
this.rolesState.delete(this.role).pipe(onErrorResumeNext()).subscribe();
this.rolesState.delete(this.role);
}
public addPermission() {

5
src/Squidex/app/features/settings/pages/roles/roles-page.component.ts

@ -8,7 +8,6 @@
import { Component, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { Observable, of } from 'rxjs';
import { onErrorResumeNext } from 'rxjs/operators';
import {
AddRoleForm,
@ -50,11 +49,11 @@ export class RolesPageComponent implements OnInit {
}
public ngOnInit() {
this.rolesState.load().pipe(onErrorResumeNext()).subscribe();
this.rolesState.load();
}
public reload() {
this.rolesState.load(true).pipe(onErrorResumeNext()).subscribe();
this.rolesState.load(true);
}
public cancelAddRole() {

2
src/Squidex/app/framework/angular/http/http-extensions.ts

@ -56,7 +56,7 @@ export module HTTP {
return httpRequest.pipe(map((response: HttpResponse<T>) => {
const etag = response.headers.get('etag') || '';
return new Versioned(new Version(etag), response);
return { version: new Version(etag), payload: response };
}));
}
}

59
src/Squidex/app/framework/utils/rxjs-extensions.ts

@ -5,16 +5,61 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
// tslint:disable: only-arrow-functions
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { catchError, map, onErrorResumeNext, shareReplay, switchMap } from 'rxjs/operators';
import { DialogService } from './../services/dialog.service';
/* tslint:disable:no-shadowed-variable */
import {
Version,
versioned,
Versioned
} from './version';
export function mapVersioned<T = any, R = any>(project: (value: T, version: Version) => R) {
return function mapOperation(source: Observable<Versioned<T>>) {
return source.pipe(map<Versioned<T>, Versioned<R>>(({ version, payload }) => {
return versioned(version, project(payload, version));
}));
};
}
export function notify<T>(dialogs: DialogService) {
return function mapOperation(source: Observable<T>) {
return source.pipe(catchError(error => {
dialogs.notifyError(error);
return throwError(error);
}));
};
}
type Options<T, R = T> = { silent?: boolean, project?: ((value: T) => R) };
export function shareSubscribed<T, R = T>(dialogs: DialogService, options?: Options<T, R>) {
return function mapOperation(source: Observable<T>) {
const shared = source.pipe(shareReplay());
shared.subscribe(undefined, error => {
if (dialogs && (!options || !options.silent)) {
dialogs.notifyError(error);
}
});
if (options && !!options.project) {
const project = options.project;
export const notify = (dialogs: DialogService) => <T>(source: Observable<T>) =>
source.pipe(catchError(error => {
dialogs.notifyError(error);
return shared.pipe(map(x => project(x)));
} else {
return <any>shared;
}
};
}
return throwError(error);
}));
export function switchSafe<T, R>(project: (source: T) => Observable<R>) {
return function mapOperation(source: Observable<T>) {
return source.pipe(switchMap(project), onErrorResumeNext<R, R>());
};
}

11
src/Squidex/app/framework/utils/version.spec.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Version, Versioned } from './version';
import { Version } from './version';
describe('Version', () => {
it('should initialize with init value', () => {
@ -20,13 +20,4 @@ describe('Version', () => {
expect(new Version('W/2').eq(new Version('2'))).toBeTruthy();
expect(new Version('W/2').eq(new Version('W/2'))).toBeTruthy();
});
});
describe('Versioned', () => {
it('should initialize with version and payload', () => {
const versioned = new Versioned(new Version('1.0'), 123);
expect(versioned.version.value).toBe('1.0');
expect(versioned.payload).toBe(123);
});
});

12
src/Squidex/app/framework/utils/version.ts

@ -24,10 +24,8 @@ export class Version {
}
}
export class Versioned<T> {
constructor(
public readonly version: Version,
public readonly payload: T
) {
}
}
export function versioned<T = any>(version: Version, payload: T = undefined!): Versioned<T> {
return { version, payload };
}
export type Versioned<T> = { readonly version: Version, readonly payload: T };

17
src/Squidex/app/shared/components/asset-uploader.component.ts

@ -10,8 +10,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
import {
AssetsState,
DialogModel,
fadeAnimation,
UploadingAsset
fadeAnimation
} from '@app/shared/internal';
@Component({
@ -31,19 +30,7 @@ export class AssetUploaderComponent {
) {
}
public addFiles(files: File[]) {
for (let file of files) {
this.assets.upload(file).subscribe();
}
public addFiles() {
this.modalMenu.show();
}
public stopUpload(upload: UploadingAsset) {
this.assets.remove(upload);
}
public trackByUpload(index: number, upload: UploadingAsset) {
return upload.id;
}
}

15
src/Squidex/app/shared/components/asset.component.ts

@ -17,8 +17,7 @@ import {
DialogService,
fadeAnimation,
StatefulComponent,
Types,
Versioned
Types
} from '@app/shared/internal';
interface State {
@ -101,10 +100,10 @@ export class AssetComponent extends StatefulComponent<State> implements OnInit {
this.assetsService.uploadFile(this.appsState.appName, initFile, this.authState.user!.token, DateTime.now())
.subscribe(dto => {
if (Types.is(dto, AssetDto)) {
this.emitLoad(dto);
} else {
if (Types.isNumber(dto)) {
this.setProgress(dto);
} else {
this.emitLoad(dto);
}
}, error => {
this.dialogs.notifyError(error);
@ -120,10 +119,10 @@ export class AssetComponent extends StatefulComponent<State> implements OnInit {
this.assetsService.replaceFile(this.appsState.appName, this.asset.id, files[0], this.asset.version)
.subscribe(dto => {
if (Types.is(dto, Versioned)) {
this.updateAsset(this.asset.update(dto.payload, this.authState.user!.token, dto.version), true);
} else {
if (Types.isNumber(dto)) {
this.setProgress(dto);
} else {
this.updateAsset(this.asset.update(dto.payload, this.authState.user!.token, dto.version), true);
}
}, error => {
this.dialogs.notifyError(error);

25
src/Squidex/app/shared/components/assets-list.component.ts

@ -11,7 +11,7 @@ import { onErrorResumeNext } from 'rxjs/operators';
import {
AssetDto,
AssetsState,
AssetWithUpload
ImmutableArray
} from '@app/shared/internal';
@Component({
@ -21,6 +21,8 @@ import {
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AssetsListComponent {
public newFiles = ImmutableArray.empty<File>();
@Input()
public state: AssetsState;
@ -36,6 +38,12 @@ export class AssetsListComponent {
@Output()
public select = new EventEmitter<AssetDto>();
public add(file: File, asset: AssetDto) {
this.newFiles = this.newFiles.remove(file);
this.state.add(asset);
}
public search() {
this.state.load().pipe(onErrorResumeNext()).subscribe();
}
@ -56,10 +64,6 @@ export class AssetsListComponent {
this.state.update(asset);
}
public updateFile(asset: AssetDto, file: File) {
this.state.replaceFile(asset, file).pipe(onErrorResumeNext()).subscribe();
}
public emitSelect(asset: AssetDto) {
this.select.emit(asset);
}
@ -68,9 +72,13 @@ export class AssetsListComponent {
return this.selectedIds && this.selectedIds[asset.id];
}
public remove(file: File) {
this.newFiles = this.newFiles.remove(file);
}
public addFiles(files: File[]) {
for (let file of files) {
this.state.upload(file);
this.newFiles = this.newFiles.pushFront(file);
}
return true;
@ -79,9 +87,4 @@ export class AssetsListComponent {
public trackByAsset(index: number, asset: AssetDto) {
return asset.id;
}
public trackByUpload(index: number, upload: AssetWithUpload) {
return upload.asset.id;
}
}

9
src/Squidex/app/shared/components/assets-selector.component.ts

@ -6,7 +6,6 @@
*/
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, OnInit, Output } from '@angular/core';
import { onErrorResumeNext } from 'rxjs/operators';
import {
AssetDto,
@ -51,15 +50,15 @@ export class AssetsSelectorComponent extends StatefulComponent<State> implements
}
public ngOnInit() {
this.assetsState.load().pipe(onErrorResumeNext()).subscribe();
this.assetsState.load();
}
public reload() {
this.assetsState.load(true).pipe(onErrorResumeNext()).subscribe();
this.assetsState.load(true);
}
public search() {
this.assetsState.search(this.filter.apiFilter).pipe(onErrorResumeNext()).subscribe();
this.assetsState.search(this.filter.apiFilter);
}
public emitComplete() {
@ -71,7 +70,7 @@ export class AssetsSelectorComponent extends StatefulComponent<State> implements
}
public selectTags(tags: string[]) {
this.assetsState.selectTags(tags).pipe(onErrorResumeNext()).subscribe();
this.assetsState.selectTags(tags);
}
public selectAsset(asset: AssetDto) {

7
src/Squidex/app/shared/components/history.component.ts

@ -8,7 +8,7 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { merge, Observable, timer } from 'rxjs';
import { delay, onErrorResumeNext, switchMap } from 'rxjs/operators';
import { delay } from 'rxjs/operators';
import {
allParams,
@ -16,7 +16,8 @@ import {
HistoryChannelUpdated,
HistoryEventDto,
HistoryService,
MessageBus
MessageBus,
switchSafe
} from '@app/shared/internal';
@Component({
@ -33,7 +34,7 @@ export class HistoryComponent {
timer(0, 10000),
this.messageBus.of(HistoryChannelUpdated).pipe(delay(1000))
).pipe(
switchMap(() => this.historyService.getHistory(this.appsState.appName, this.channel).pipe(onErrorResumeNext())));
switchSafe(() => this.historyService.getHistory(this.appsState.appName, this.channel)));
constructor(
private readonly appsState: AppsState,

3
src/Squidex/app/shared/components/schema-category.component.ts

@ -6,7 +6,6 @@
*/
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core';
import { onErrorResumeNext } from 'rxjs/operators';
import {
fadeAnimation,
@ -127,7 +126,7 @@ export class SchemaCategoryComponent extends StatefulComponent<State> implements
}
public changeCategory(schema: SchemaDto) {
this.schemasState.changeCategory(schema, this.name).pipe(onErrorResumeNext()).subscribe();
this.schemasState.changeCategory(schema, this.name);
}
public emitRemove() {

8
src/Squidex/app/shared/services/app-languages.service.spec.ts

@ -71,11 +71,13 @@ describe('AppLanguagesService', () => {
}
});
expect(languages!).toEqual(
new AppLanguagesDto([
expect(languages!).toEqual({
payload: [
new AppLanguageDto('en', 'English', true, true, ['de', 'en']),
new AppLanguageDto('it', 'Italian', false, false, [])
], new Version('2')));
],
version: new Version('2')
});
}));
it('should make post request to add language',

31
src/Squidex/app/shared/services/app-languages.service.ts

@ -8,12 +8,13 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { tap } from 'rxjs/operators';
import {
AnalyticsService,
ApiUrlConfig,
HTTP,
mapVersioned,
Model,
pretifyError,
Version,
@ -22,14 +23,7 @@ import {
import { LanguageDto } from './languages.service';
export class AppLanguagesDto extends Model<AppLanguagesDto> {
constructor(
public readonly languages: AppLanguageDto[],
public readonly version: Version
) {
super();
}
}
export type AppLanguagesDto = Versioned<AppLanguageDto[]>;
export class AppLanguageDto extends Model<AppLanguageDto> {
constructor(
@ -75,21 +69,18 @@ export class AppLanguagesService {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages`);
return HTTP.getVersioned<any>(this.http, url).pipe(
map(response => {
const body = response.payload.body;
mapVersioned(({ body }) => {
const items: any[] = body;
const languages = items.map(item => {
return new AppLanguageDto(
const languages = items.map(item =>
new AppLanguageDto(
item.iso2Code,
item.englishName,
item.isMaster,
item.isOptional,
item.fallback || []);
});
item.fallback || []));
return new AppLanguagesDto(languages, response.version);
return languages;
}),
pretifyError('Failed to load languages. Please reload.'));
}
@ -98,9 +89,7 @@ export class AppLanguagesService {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/languages`);
return HTTP.postVersioned<any>(this.http, url, dto, version).pipe(
map(response => {
const body = response.payload.body;
mapVersioned(({ body }) => {
const language = new AppLanguageDto(
body.iso2Code,
body.englishName,
@ -108,7 +97,7 @@ export class AppLanguagesService {
body.isOptional,
body.fallback || []);
return new Versioned(response.version, language);
return language;
}),
tap(() => {
this.analytics.trackEvent('Language', 'Added', appName);

30
src/Squidex/app/shared/services/apps.service.ts

@ -14,7 +14,6 @@ import {
AnalyticsService,
ApiUrlConfig,
DateTime,
HTTP,
Model,
Permission,
pretifyError
@ -51,13 +50,9 @@ export class AppsService {
public getApps(): Observable<AppDto[]> {
const url = this.apiUrl.buildUrl('/api/apps');
return HTTP.getVersioned<any>(this.http, url).pipe(
map(response => {
const body = response.payload.body;
const items: any[] = body;
return items.map(item => {
return this.http.get<any[]>(url).pipe(
map(body => {
const apps = body.map(item => {
const permissions = (<string[]>item.permissions).map(x => new Permission(x));
return new AppDto(
@ -69,6 +64,8 @@ export class AppsService {
item.planName,
item.planUpgrade);
});
return apps;
}),
pretifyError('Failed to load apps. Please reload.'));
}
@ -76,15 +73,22 @@ export class AppsService {
public postApp(dto: CreateAppDto, now?: DateTime): Observable<AppDto> {
const url = this.apiUrl.buildUrl('api/apps');
return HTTP.postVersioned<any>(this.http, url, dto).pipe(
map(response => {
const body = response.payload.body;
return this.http.post<any>(url, dto).pipe(
map(body => {
now = now || DateTime.now();
const permissions = (<string[]>body.permissions).map(x => new Permission(x));
return new AppDto(body.id, dto.name, permissions, now, now, body.planName, body.planUpgrade);
const app = new AppDto(
body.id,
dto.name,
permissions,
now,
now,
body.planName,
body.planUpgrade);
return app;
}),
tap(() => {
this.analytics.trackEvent('App', 'Created', dto.name);

22
src/Squidex/app/shared/services/assets.service.ts

@ -21,7 +21,8 @@ import {
ResultSet,
Types,
Version,
Versioned
Versioned,
versioned
} from '@app/framework';
export class AssetsDto extends ResultSet<AssetDto> { }
@ -102,8 +103,7 @@ export class AssetsService {
public getTags(appName: string): Observable<{ [name: string]: number }> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/tags`);
return HTTP.getVersioned(this.http, url).pipe(
map(response => <any>response.payload.body));
return this.http.get<{ [name: string]: number }>(url);
}
public getAssets(appName: string, take: number, skip: number, query?: string, tags?: string[], ids?: string[]): Observable<AssetsDto> {
@ -141,12 +141,12 @@ export class AssetsService {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets?${fullQuery}`);
return HTTP.getVersioned<any>(this.http, url).pipe(
map(response => {
const body = response.payload.body;
map(({ payload }) => {
const body = payload.body;
const items: any[] = body.items;
return new AssetsDto(body.total, items.map(item => {
const assets = new AssetsDto(body.total, items.map(item => {
const assetUrl = this.apiUrl.buildUrl(`api/assets/${item.id}`);
return new AssetDto(
@ -170,6 +170,8 @@ export class AssetsService {
assetUrl,
new Version(item.version.toString()));
}));
return assets;
}),
pretifyError('Failed to load assets. Please reload.'));
}
@ -239,8 +241,8 @@ export class AssetsService {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/${id}`);
return HTTP.getVersioned<any>(this.http, url).pipe(
map(response => {
const body = response.payload.body;
map(({ version, payload }) => {
const body = payload.body;
const assetUrl = this.apiUrl.buildUrl(`api/assets/${body.id}`);
@ -263,7 +265,7 @@ export class AssetsService {
body.slug,
body.tags || [],
assetUrl,
response.version);
version);
}),
pretifyError('Failed to load assets. Please reload.'));
}
@ -285,7 +287,7 @@ export class AssetsService {
} else if (Types.is(event, HttpResponse)) {
const response: any = event.body;
return new Versioned(new Version(event.headers.get('etag')!), response);
return versioned(new Version(event.headers.get('etag')!), response);
} else {
throw 'Invalid';
}

27
src/Squidex/app/shared/services/backups.service.ts

@ -62,16 +62,17 @@ export class BackupsService {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/backups`);
return this.http.get<any[]>(url).pipe(
map(response => {
return response.map(item => {
return new BackupDto(
map(body => {
const backups = body.map(item =>
new BackupDto(
item.id,
DateTime.parseISO_UTC(item.started),
item.stopped ? DateTime.parseISO_UTC(item.stopped) : null,
item.handledEvents,
item.handledAssets,
item.status);
});
item.status));
return backups;
}),
pretifyError('Failed to load backups.'));
}
@ -80,13 +81,15 @@ export class BackupsService {
const url = this.apiUrl.buildUrl(`api/apps/restore`);
return this.http.get<any>(url).pipe(
map(response => {
return new RestoreDto(
response.url,
DateTime.parseISO_UTC(response.started),
response.stopped ? DateTime.parseISO_UTC(response.stopped) : null,
response.status,
response.log);
map(body => {
const restore = new RestoreDto(
body.url,
DateTime.parseISO_UTC(body.started),
body.stopped ? DateTime.parseISO_UTC(body.stopped) : null,
body.status,
body.log);
return restore;
}),
catchError(error => {
if (Types.is(error, HttpErrorResponse) && error.status === 404) {

8
src/Squidex/app/shared/services/clients.service.spec.ts

@ -71,11 +71,13 @@ describe('ClientsService', () => {
}
});
expect(clients!).toEqual(
new ClientsDto([
expect(clients!).toEqual({
payload: [
new ClientDto('client1', 'Client 1', 'secret1', 'Editor'),
new ClientDto('client2', 'Client 2', 'secret2', 'Developer')
], new Version('2')));
],
version: new Version('2')
});
}));
it('should make post request to create client',

31
src/Squidex/app/shared/services/clients.service.ts

@ -14,20 +14,14 @@ import {
AnalyticsService,
ApiUrlConfig,
HTTP,
mapVersioned,
Model,
pretifyError,
Version,
Versioned
} from '@app/framework';
export class ClientsDto extends Model<ClientsDto> {
constructor(
public readonly clients: ClientDto[],
public readonly version: Version
) {
super();
}
}
export type ClientsDto = Versioned<ClientDto[]>;
export class ClientDto extends Model<ClientDto> {
constructor(
@ -70,20 +64,17 @@ export class ClientsService {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients`);
return HTTP.getVersioned<any>(this.http, url).pipe(
map(response => {
const body = response.payload.body;
mapVersioned(({ body }) => {
const items: any[] = body;
const clients = items.map(item => {
return new ClientDto(
const clients = items.map(item =>
new ClientDto(
item.id,
item.name || body.id,
item.name || item.id,
item.secret,
item.role);
});
item.role));
return new ClientsDto(clients, response.version);
return clients;
}),
pretifyError('Failed to load clients. Please reload.'));
}
@ -92,16 +83,14 @@ export class ClientsService {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/clients`);
return HTTP.postVersioned<any>(this.http, url, dto, version).pipe(
map(response => {
const body = response.payload.body;
mapVersioned(({ body }) => {
const client = new ClientDto(
body.id,
body.name || body.id,
body.secret,
body.role);
return new Versioned(response.version, client);
return client;
}),
tap(() => {
this.analytics.trackEvent('Client', 'Created', appName);

24
src/Squidex/app/shared/services/comments.service.ts

@ -56,25 +56,27 @@ export class CommentsService {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/comments/${commentsId}?version=${version.value}`);
return this.http.get<any>(url).pipe(
map(response => {
return new CommentsDto(
response.createdComments.map((item: any) => {
map(body => {
const comments = new CommentsDto(
body.createdComments.map((item: any) => {
return new CommentDto(
item.id,
DateTime.parseISO_UTC(item.time),
item.text,
item.user);
}),
response.updatedComments.map((item: any) => {
body.updatedComments.map((item: any) => {
return new CommentDto(
item.id,
DateTime.parseISO_UTC(item.time),
item.text,
item.user);
}),
response.deletedComments,
new Version(response.version)
body.deletedComments,
new Version(body.version)
);
return comments;
}),
pretifyError('Failed to load comments.'));
}
@ -82,15 +84,15 @@ export class CommentsService {
public postComment(appName: string, commentsId: string, dto: UpsertCommentDto): Observable<CommentDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/comments/${commentsId}`);
return this.http.post(url, dto).pipe(
map(response => {
const body: any = response;
return new CommentDto(
return this.http.post<any>(url, dto).pipe(
map(body => {
const comment = new CommentDto(
body.id,
DateTime.parseISO_UTC(body.time),
body.text,
body.user);
return comment;
}),
pretifyError('Failed to create comment.'));
}

50
src/Squidex/app/shared/services/contents.service.ts

@ -15,6 +15,7 @@ import {
ApiUrlConfig,
DateTime,
HTTP,
mapVersioned,
Model,
pretifyError,
ResultSet,
@ -99,13 +100,13 @@ export class ContentsService {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}?${fullQuery}`);
return HTTP.getVersioned<any>(this.http, url).pipe(
map(response => {
const body = response.payload.body;
map(({ payload }) => {
const body = payload.body;
const items: any[] = body.items;
return new ContentsDto(body.total, items.map(item => {
return new ContentDto(
const contents = new ContentsDto(body.total, items.map(item =>
new ContentDto(
item.id,
item.status,
DateTime.parseISO_UTC(item.created), item.createdBy,
@ -119,8 +120,9 @@ export class ContentsService {
item.isPending === true,
item.data,
item.dataDraft,
new Version(item.version.toString()));
}));
new Version(item.version.toString()))));
return contents;
}),
pretifyError('Failed to load contents. Please reload.'));
}
@ -129,10 +131,10 @@ export class ContentsService {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}`);
return HTTP.getVersioned<any>(this.http, url).pipe(
map(response => {
const body = response.payload.body;
map(({ version, payload }) => {
const body = payload.body;
return new ContentDto(
const content = new ContentDto(
body.id,
body.status,
DateTime.parseISO_UTC(body.created), body.createdBy,
@ -146,7 +148,9 @@ export class ContentsService {
body.isPending === true,
body.data,
body.dataDraft,
response.version);
version);
return content;
}),
pretifyError('Failed to load content. Please reload.'));
}
@ -155,8 +159,8 @@ export class ContentsService {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}/${version.value}`);
return HTTP.getVersioned<any>(this.http, url).pipe(
map(response => {
return new Versioned(response.version, response.payload.body);
mapVersioned(({ body }) => {
return body;
}),
pretifyError('Failed to load data. Please reload.'));
}
@ -165,10 +169,10 @@ export class ContentsService {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}?publish=${publish}`);
return HTTP.postVersioned<any>(this.http, url, dto).pipe(
map(response => {
const body = response.payload.body;
map(({ version, payload }) => {
const body = payload.body;
return new ContentDto(
const content = new ContentDto(
body.id,
body.status,
DateTime.parseISO_UTC(body.created), body.createdBy,
@ -177,7 +181,9 @@ export class ContentsService {
body.isPending,
null,
body.data,
response.version);
version);
return content;
}),
tap(() => {
this.analytics.trackEvent('Content', 'Created', appName);
@ -189,10 +195,8 @@ export class ContentsService {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}?asDraft=${asDraft}`);
return HTTP.putVersioned(this.http, url, dto, version).pipe(
map(response => {
const body = response.payload.body;
return new Versioned(response.version, body);
mapVersioned(payload => {
return payload.body;
}),
tap(() => {
this.analytics.trackEvent('Content', 'Updated', appName);
@ -204,10 +208,8 @@ export class ContentsService {
const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}/${id}`);
return HTTP.patchVersioned(this.http, url, dto, version).pipe(
map(response => {
const body = response.payload.body;
return new Versioned(response.version, body);
mapVersioned(payload => {
return payload.body;
}),
tap(() => {
this.analytics.trackEvent('Content', 'Updated', appName);

15
src/Squidex/app/shared/services/contributors.service.spec.ts

@ -70,11 +70,16 @@ describe('ContributorsService', () => {
}
});
expect(contributors!).toEqual(
new ContributorsDto([
new ContributorDto('123', 'Owner'),
new ContributorDto('456', 'Owner')
], 100, new Version('2')));
expect(contributors!).toEqual({
payload: {
contributors: [
new ContributorDto('123', 'Owner'),
new ContributorDto('456', 'Owner')
],
maxContributors: 100
},
version: new Version('2')
});
}));
it('should make post request to assign contributor',

38
src/Squidex/app/shared/services/contributors.service.ts

@ -8,27 +8,23 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { tap } from 'rxjs/operators';
import {
AnalyticsService,
ApiUrlConfig,
HTTP,
mapVersioned,
Model,
pretifyError,
Version,
Versioned
} from '@app/framework';
export class ContributorsDto extends Model<ContributorsDto> {
constructor(
public readonly contributors: ContributorDto[],
public readonly maxContributors: number,
public readonly version: Version
) {
super();
}
}
export type ContributorsDto = Versioned<{
readonly contributors: ContributorDto[],
readonly maxContributors: number
}>;
export class ContributorDto extends Model<AssignContributorDto> {
constructor(
@ -63,18 +59,18 @@ export class ContributorsService {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/contributors`);
return HTTP.getVersioned<any>(this.http, url).pipe(
map(response => {
const body = response.payload.body;
mapVersioned(payload => {
const body = payload.body;
const items: any[] = body.contributors;
return new ContributorsDto(
items.map(item => {
return new ContributorDto(
const contributors =
items.map(item =>
new ContributorDto(
item.contributorId,
item.role);
}),
body.maxContributors, response.version);
item.role));
return { contributors, maxContributors: body.maxContributors };
}),
pretifyError('Failed to load contributors. Please reload.'));
}
@ -83,10 +79,8 @@ export class ContributorsService {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/contributors`);
return HTTP.postVersioned(this.http, url, dto, version).pipe(
map(response => {
const body: any = response.payload.body;
return new Versioned(response.version, body);
mapVersioned(payload => {
return <ContributorAssignedDto>payload.body;
}),
tap(() => {
this.analytics.trackEvent('Contributor', 'Configured', appName);

11
src/Squidex/app/shared/services/history.service.ts

@ -82,15 +82,16 @@ export class HistoryService {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/history?channel=${channel}`);
return this.http.get<any[]>(url).pipe(
map(response => {
return response.map(item => {
return new HistoryEventDto(
map(body => {
const history = body.map(item =>
new HistoryEventDto(
item.eventId,
item.actor,
item.message,
item.version,
DateTime.parseISO_UTC(item.created));
});
DateTime.parseISO_UTC(item.created)));
return history;
}),
pretifyError('Failed to load history. Please reload.'));
}

21
src/Squidex/app/shared/services/languages.service.ts

@ -10,11 +10,7 @@ import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import {
ApiUrlConfig,
HTTP,
pretifyError
} from '@app/framework';
import { ApiUrlConfig, pretifyError } from '@app/framework';
export class LanguageDto {
constructor(
@ -35,15 +31,14 @@ export class LanguagesService {
public getLanguages(): Observable<LanguageDto[]> {
const url = this.apiUrl.buildUrl('api/languages');
return HTTP.getVersioned(this.http, url).pipe(
map(response => {
const items: any[] = <any>response.payload.body;
return items.map(item => {
return new LanguageDto(
return this.http.get<any[]>(url).pipe(
map(body => {
const languages = body.map(item =>
new LanguageDto(
item.iso2Code,
item.englishName);
});
item.englishName));
return languages;
}),
pretifyError('Failed to load languages. Please reload.'));
}

20
src/Squidex/app/shared/services/news.service.ts

@ -40,18 +40,18 @@ export class NewsService {
const url = this.apiUrl.buildUrl(`api/news/features?version=${version}`);
return this.http.get<any>(url).pipe(
map(response => {
const items: any[] = response.features;
map(body => {
const items: any[] = body.features;
return new FeaturesDto(
items.map(item => {
return new FeatureDto(
const features = new FeaturesDto(
items.map(item =>
new FeatureDto(
item.name,
item.text
);
}),
response.version
);
item.text)
),
body.version);
return features;
}),
pretifyError('Failed to load features. Please reload.'));
}

10
src/Squidex/app/shared/services/patterns.service.spec.ts

@ -69,11 +69,13 @@ describe('PatternsService', () => {
}
});
expect(patterns!).toEqual(
new PatternsDto([
expect(patterns!).toEqual({
payload: [
new PatternDto('1', 'Number', '[0-9]', 'Message1'),
new PatternDto('2', 'Numbers', '[0-9]*', 'Message2')
], new Version('2')));
],
version: new Version('2')
});
}));
it('should make post request to add pattern',
@ -93,9 +95,9 @@ describe('PatternsService', () => {
expect(req.request.headers.get('If-Match')).toEqual(version.value);
req.flush({
name: 'Number',
patternId: '1',
pattern: '[0-9]',
name: 'Number',
message: 'Message1'
});

34
src/Squidex/app/shared/services/patterns.service.ts

@ -8,26 +8,20 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { tap } from 'rxjs/operators';
import {
AnalyticsService,
ApiUrlConfig,
HTTP,
mapVersioned,
Model,
pretifyError,
Version,
Versioned
} from '@app/framework';
export class PatternsDto extends Model<PatternsDto> {
constructor(
public readonly patterns: PatternDto[],
public readonly version: Version
) {
super();
}
}
export type PatternsDto = Versioned<PatternDto[]>;
export class PatternDto extends Model<PatternDto> {
constructor(
@ -59,20 +53,18 @@ export class PatternsService {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/patterns`);
return HTTP.getVersioned<any>(this.http, url).pipe(
map(response => {
const body = response.payload.body;
mapVersioned(({ body }) => {
const items: any[] = body;
return new PatternsDto(
items.map(item => {
return new PatternDto(
const patterns =
items.map(item =>
new PatternDto(
item.patternId,
item.name,
item.pattern,
item.message);
}),
response.version);
item.message));
return patterns;
}),
pretifyError('Failed to add pattern. Please reload.'));
}
@ -81,16 +73,14 @@ export class PatternsService {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/patterns`);
return HTTP.postVersioned<any>(this.http, url, dto, version).pipe(
map(response => {
const body = response.payload.body;
mapVersioned(({ body }) => {
const pattern = new PatternDto(
body.patternId,
body.name,
body.pattern,
body.message);
return new Versioned(response.version, pattern);
return pattern;
}),
tap(() => {
this.analytics.trackEvent('Patterns', 'Created', appName);

21
src/Squidex/app/shared/services/plans.service.spec.ts

@ -54,7 +54,6 @@ describe('PlansService', () => {
req.flush({
currentPlanId: '123',
hasPortal: true,
planOwner: '456',
plans: [
{
@ -77,24 +76,26 @@ describe('PlansService', () => {
maxAssetSize: 5500,
maxContributors: 6500
}
]
],
hasPortal: true
}, {
headers: {
etag: '2'
}
});
expect(plans!).toEqual(
new PlansDto(
'123',
'456',
true,
[
expect(plans!).toEqual({
payload: {
currentPlanId: '123',
planOwner: '456',
plans: [
new PlanDto('free', 'Free', '14 €', 'free_yearly', '12 €', 1000, 1500, 2500),
new PlanDto('prof', 'Prof', '18 €', 'prof_yearly', '16 €', 4000, 5500, 6500)
],
new Version('2')
));
hasPortal: true
},
version: new Version('2')
});
}));
it('should make put request to change plan',

51
src/Squidex/app/shared/services/plans.service.ts

@ -8,29 +8,25 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { tap } from 'rxjs/operators';
import {
AnalyticsService,
ApiUrlConfig,
HTTP,
mapVersioned,
Model,
pretifyError,
Version,
Versioned
} from '@app/framework';
export class PlansDto extends Model<PlansDto> {
constructor(
public readonly currentPlanId: string,
public readonly planOwner: string,
public readonly hasPortal: boolean,
public readonly plans: PlanDto[],
public readonly version: Version
) {
super();
}
}
export type PlansDto = Versioned<{
readonly currentPlanId: string,
readonly planOwner: string,
readonly hasPortal: boolean,
readonly plans: PlanDto[]
}>;
export class PlanDto extends Model<PlanDto> {
constructor(
@ -68,17 +64,16 @@ export class PlansService {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/plans`);
return HTTP.getVersioned<any>(this.http, url).pipe(
map(response => {
const body = response.payload.body;
mapVersioned(({ body }) => {
const items: any[] = body.plans;
return new PlansDto(
body.currentPlanId,
body.planOwner,
body.hasPortal,
items.map(item => {
return new PlanDto(
const { hasPortal, currentPlanId, planOwner } = body;
const plans = {
currentPlanId,
planOwner,
plans: items.map(item =>
new PlanDto(
item.id,
item.name,
item.costs,
@ -86,9 +81,11 @@ export class PlansService {
item.yearlyCosts,
item.maxApiCalls,
item.maxAssetSize,
item.maxContributors);
}),
response.version);
item.maxContributors)),
hasPortal
};
return plans;
}),
pretifyError('Failed to load plans. Please reload.'));
}
@ -97,10 +94,8 @@ export class PlansService {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/plan`);
return HTTP.putVersioned<any>(this.http, url, dto, version).pipe(
map(response => {
const body = response.payload.body;
return new Versioned(response.version, body);
mapVersioned(payload => {
return <PlanChangedDto>payload.body;
}),
tap(() => {
this.analytics.trackEvent('Plan', 'Changed', appName);

7
src/Squidex/app/shared/services/roles.service.spec.ts

@ -88,12 +88,13 @@ describe('RolesService', () => {
}
});
expect(roles!).toEqual(
new RolesDto([
expect(roles!).toEqual({
payload: [
new RoleDto('Role1', 3, 5, ['P1']),
new RoleDto('Role2', 7, 9, ['P2'])
],
new Version('2')));
version: new Version('2')
});
}));
it('should make post request to add role',

29
src/Squidex/app/shared/services/roles.service.ts

@ -8,26 +8,20 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { tap } from 'rxjs/operators';
import {
AnalyticsService,
ApiUrlConfig,
HTTP,
mapVersioned,
Model,
pretifyError,
Version,
Versioned
} from '@app/framework';
export class RolesDto extends Model<RolesDto> {
constructor(
public readonly roles: RoleDto[],
public readonly version: Version
) {
super();
}
}
export type RolesDto = Versioned<RoleDto[]>;
export class RoleDto extends Model<RoleDto> {
constructor(
@ -61,20 +55,17 @@ export class RolesService {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/roles`);
return HTTP.getVersioned<any>(this.http, url).pipe(
map(response => {
const body = response.payload.body;
mapVersioned(({ body }) => {
const items: any[] = body.roles;
const roles = items.map(item => {
return new RoleDto(
const roles = items.map(item =>
new RoleDto(
item.name,
item.numClients,
item.numContributors,
item.permissions);
});
item.permissions));
return new RolesDto(roles, response.version);
return roles;
}),
pretifyError('Failed to load roles. Please reload.'));
}
@ -83,10 +74,10 @@ export class RolesService {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/roles`);
return HTTP.postVersioned<any>(this.http, url, dto, version).pipe(
map(response => {
mapVersioned(() => {
const role = new RoleDto(dto.name, 0, 0, []);
return new Versioned(response.version, role);
return role;
}),
tap(() => {
this.analytics.trackEvent('Role', 'Created', appName);

42
src/Squidex/app/shared/services/rules.service.ts

@ -132,10 +132,10 @@ export class RulesService {
const url = this.apiUrl.buildUrl('api/rules/actions');
return HTTP.getVersioned<any>(this.http, url).pipe(
map(response => {
const items: { [name: string]: any } = response.payload.body;
map(({ payload }) => {
const items: { [name: string]: any } = payload.body;
const result: { [name: string]: RuleElementDto } = {};
const actions: { [name: string]: RuleElementDto } = {};
for (let key of Object.keys(items).sort()) {
const value = items[key];
@ -150,7 +150,7 @@ export class RulesService {
property.isRequired
));
result[key] = new RuleElementDto(
actions[key] = new RuleElementDto(
value.display,
value.description,
value.iconColor,
@ -159,7 +159,7 @@ export class RulesService {
properties);
}
return result;
return actions;
}),
pretifyError('Failed to load Rules. Please reload.'));
}
@ -168,11 +168,11 @@ export class RulesService {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/rules`);
return HTTP.getVersioned<any>(this.http, url).pipe(
map(response => {
const items: any[] = response.payload.body;
map(({ payload }) => {
const items: any[] = payload.body;
return items.map(item => {
return new RuleDto(
const rules = items.map(item =>
new RuleDto(
item.id,
item.createdBy,
item.lastModifiedBy,
@ -183,8 +183,9 @@ export class RulesService {
item.trigger,
item.trigger.triggerType,
item.action,
item.action.actionType);
});
item.action.actionType));
return rules;
}),
pretifyError('Failed to load Rules. Please reload.'));
}
@ -193,8 +194,8 @@ export class RulesService {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/rules`);
return HTTP.postVersioned<any>(this.http, url, dto).pipe(
map(response => {
const body = response.payload.body;
map(({ version, payload }) => {
const body = payload.body;
return new RuleDto(
body.id,
@ -202,7 +203,7 @@ export class RulesService {
user,
now,
now,
response.version,
version,
true,
dto.trigger,
dto.trigger.triggerType,
@ -259,13 +260,13 @@ export class RulesService {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/rules/events?take=${take}&skip=${skip}`);
return HTTP.getVersioned<any>(this.http, url).pipe(
map(response => {
const body = response.payload.body;
map(({ payload }) => {
const body = payload.body;
const items: any[] = body.items;
return new RuleEventsDto(body.total, items.map(item => {
return new RuleEventDto(
const ruleEvents = new RuleEventsDto(body.total, items.map(item =>
new RuleEventDto(
item.id,
DateTime.parseISO_UTC(item.created),
item.nextAttempt ? DateTime.parseISO_UTC(item.nextAttempt) : null,
@ -274,8 +275,9 @@ export class RulesService {
item.lastDump,
item.result,
item.jobResult,
item.numCalls);
}));
item.numCalls)));
return ruleEvents;
}),
pretifyError('Failed to load events. Please reload.'));
}

36
src/Squidex/app/shared/services/schemas.service.ts

@ -15,6 +15,7 @@ import {
ApiUrlConfig,
DateTime,
HTTP,
mapVersioned,
Model,
pretifyError,
StringHelper,
@ -215,12 +216,12 @@ export class SchemasService {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas`);
return HTTP.getVersioned<any>(this.http, url).pipe(
map(response => {
const body = response.payload.body;
map(({ payload }) => {
const body = payload.body;
const items: any[] = body;
return items.map(item => {
const schemas = items.map(item => {
const properties = new SchemaPropertiesDto(item.properties.label, item.properties.hints);
return new SchemaDto(
@ -233,6 +234,8 @@ export class SchemasService {
DateTime.parseISO_UTC(item.lastModified), item.lastModifiedBy,
new Version(item.version.toString()));
});
return schemas;
}),
pretifyError('Failed to load schemas. Please reload.'));
}
@ -241,8 +244,8 @@ export class SchemasService {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${id}`);
return HTTP.getVersioned<any>(this.http, url).pipe(
map(response => {
const body = response.payload.body;
map(({ version, payload }) => {
const body = payload.body;
const fields = body.fields.map((item: any) => {
const propertiesDto =
@ -292,7 +295,7 @@ export class SchemasService {
body.isPublished,
DateTime.parseISO_UTC(body.created), body.createdBy,
DateTime.parseISO_UTC(body.lastModified), body.lastModifiedBy,
response.version,
version,
fields,
body.scripts || {},
body.previewUrls || {});
@ -304,22 +307,23 @@ export class SchemasService {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas`);
return HTTP.postVersioned<any>(this.http, url, dto).pipe(
map(response => {
const body = response.payload.body;
map(({ version, payload }) => {
const body = payload.body;
now = now || DateTime.now();
return new SchemaDetailsDto(
const schema = new SchemaDetailsDto(
body.id,
dto.name,
'',
dto.name, '',
dto.properties || new SchemaPropertiesDto(),
dto.isSingleton === true,
false,
now, user,
now, user,
response.version,
version,
dto.fields || []);
return schema;
}),
tap(() => {
this.analytics.trackEvent('Schema', 'Created', appName);
@ -401,17 +405,15 @@ export class SchemasService {
const url = this.buildUrl(appName, schemaName, parentId, '');
return HTTP.postVersioned<any>(this.http, url, dto, version).pipe(
map(response => {
const body = response.payload.body;
mapVersioned(({ body }) => {
if (parentId) {
const field = new NestedFieldDto(body.id, dto.name, dto.properties, parentId);
return new Versioned(response.version, field);
return field;
} else {
const field = new RootFieldDto(body.id, dto.name, dto.properties, dto.partitioning);
return new Versioned(response.version, field);
return field;
}
}),
tap(() => {

4
src/Squidex/app/shared/services/translations.service.ts

@ -38,8 +38,8 @@ export class TranslationsService {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/translations`);
return this.http.post<any>(url, request).pipe(
map(response => {
return new TranslationDto(response.result, response.text);
map(body => {
return new TranslationDto(body.result, body.text);
}),
pretifyError('Failed to translate text. Please reload.'));
}

39
src/Squidex/app/shared/services/usages.service.ts

@ -62,8 +62,8 @@ export class UsagesService {
const url = this.apiUrl.buildUrl(`api/apps/${app}/usages/log`);
return this.http.get<any>(url).pipe(
map(response => {
return response.downloadUrl;
map(body => {
return body.downloadUrl;
}),
pretifyError('Failed to load monthly api calls. Please reload.'));
}
@ -72,8 +72,8 @@ export class UsagesService {
const url = this.apiUrl.buildUrl(`api/apps/${app}/usages/calls/month`);
return this.http.get<any>(url).pipe(
map(response => {
return new CurrentCallsDto(response.count, response.maxAllowed);
map(body => {
return new CurrentCallsDto(body.count, body.maxAllowed);
}),
pretifyError('Failed to load monthly api calls. Please reload.'));
}
@ -82,8 +82,8 @@ export class UsagesService {
const url = this.apiUrl.buildUrl(`api/apps/${app}/usages/storage/today`);
return this.http.get<any>(url).pipe(
map(response => {
return new CurrentStorageDto(response.size, response.maxAllowed);
map(body => {
return new CurrentStorageDto(body.size, body.maxAllowed);
}),
pretifyError('Failed to load todays storage size. Please reload.'));
}
@ -92,18 +92,18 @@ export class UsagesService {
const url = this.apiUrl.buildUrl(`api/apps/${app}/usages/calls/${fromDate.toUTCStringFormat('YYYY-MM-DD')}/${toDate.toUTCStringFormat('YYYY-MM-DD')}`);
return this.http.get<any>(url).pipe(
map(response => {
const result: { [category: string]: CallsUsageDto[] } = {};
map(body => {
const usages: { [category: string]: CallsUsageDto[] } = {};
for (let category of Object.keys(response)) {
result[category] = response[category].map((item: any) => {
return new CallsUsageDto(
for (let category of Object.keys(body)) {
usages[category] = body[category].map((item: any) =>
new CallsUsageDto(
DateTime.parseISO_UTC(item.date),
item.count,
item.averageMs);
});
item.averageMs));
}
return result;
return usages;
}),
pretifyError('Failed to load calls usage. Please reload.'));
}
@ -112,13 +112,14 @@ export class UsagesService {
const url = this.apiUrl.buildUrl(`api/apps/${app}/usages/storage/${fromDate.toUTCStringFormat('YYYY-MM-DD')}/${toDate.toUTCStringFormat('YYYY-MM-DD')}`);
return this.http.get<any[]>(url).pipe(
map(response => {
return response.map(item => {
return new StorageUsageDto(
map(body => {
const usages = body.map(item =>
new StorageUsageDto(
DateTime.parseISO_UTC(item.date),
item.count,
item.size);
});
item.size));
return usages;
}),
pretifyError('Failed to load storage usage. Please reload.'));
}

21
src/Squidex/app/shared/services/users.service.ts

@ -32,12 +32,13 @@ export class UsersService {
const url = this.apiUrl.buildUrl(`api/users?query=${query || ''}`);
return this.http.get<any[]>(url).pipe(
map(response => {
return response.map(item => {
return new UserDto(
map(body => {
const users = body.map(item =>
new UserDto(
item.id,
item.displayName);
});
item.displayName));
return users;
}),
pretifyError('Failed to load users. Please reload.'));
}
@ -46,10 +47,12 @@ export class UsersService {
const url = this.apiUrl.buildUrl(`api/users/${id}`);
return this.http.get<any>(url).pipe(
map(response => {
return new UserDto(
response.id,
response.displayName);
map(body => {
const user = new UserDto(
body.id,
body.displayName);
return user;
}),
pretifyError('Failed to load user. Please reload.'));
}

20
src/Squidex/app/shared/state/apps.state.spec.ts

@ -6,7 +6,7 @@
*/
import { of } from 'rxjs';
import { IMock, Mock, Times } from 'typemoq';
import { IMock, Mock } from 'typemoq';
import {
AppDto,
@ -95,7 +95,7 @@ describe('AppsState', () => {
expect(appsState.snapshot.apps.values).toEqual([newApp, ...oldApps]);
});
it('should remove app from snashot when archived', () => {
it('should remove app from snapshot when archived', () => {
const request = { ...newApp };
appsService.setup(x => x.postApp(request))
@ -115,4 +115,20 @@ describe('AppsState', () => {
expect(appsAfterCreate).toEqual([newApp, ...oldApps]);
expect(appsAfterDelete).toEqual(oldApps);
});
it('should selected app from snapshot when archived', () => {
const request = { ...newApp };
appsService.setup(x => x.postApp(request))
.returns(() => of(newApp)).verifiable();
appsService.setup(x => x.deleteApp(newApp.name))
.returns(() => of({})).verifiable();
appsState.create(request).subscribe();
appsState.select(newApp.name).subscribe();
appsState.delete(newApp.name).subscribe();
expect(appsState.snapshot.selectedApp).toBeNull();
});
});

109
src/Squidex/app/shared/state/apps.state.ts

@ -7,11 +7,12 @@
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { distinctUntilChanged, map, share } from 'rxjs/operators';
import { distinctUntilChanged, map, tap } from 'rxjs/operators';
import {
DialogService,
ImmutableArray,
shareSubscribed,
State
} from '@app/framework';
@ -23,14 +24,12 @@ import {
interface Snapshot {
// All apps, loaded once.
apps: AppsList;
apps: ImmutableArray<AppDto>;
// The selected app.
selectedApp: AppDto | null;
}
type AppsList = ImmutableArray<AppDto>;
function sameApp(lhs: AppDto, rhs?: AppDto): boolean {
return lhs === rhs || (!!lhs && !!rhs && lhs.id === rhs.id);
}
@ -57,78 +56,52 @@ export class AppsState extends State<Snapshot> {
}
public select(name: string | null): Observable<AppDto | null> {
const http$ =
this.loadApp(name)
.pipe(share());
http$.subscribe(selectedApp => {
this.next(s => ({ ...s, selectedApp }));
});
return http$;
}
private loadApp(name: string | null) {
return of(name ? this.snapshot.apps.find(x => x.name === name) || null : null);
const observable =
!name ?
of(null) :
of(this.snapshot.apps.find(x => x.name === name) || null);
return observable.pipe(
tap(selectedApp => {
this.next(s => ({ ...s, selectedApp }));
}));
}
public load(): Observable<any> {
const http$ =
this.appsService.getApps().pipe(
share());
http$.subscribe(response => {
this.next(s => {
const apps = ImmutableArray.of(response).sortByStringAsc(x => x.name);
return { ...s, apps };
});
}, error => {
this.dialogs.notifyError(error);
});
return http$;
return this.appsService.getApps().pipe(
tap((dto: AppDto[]) => {
this.next(s => {
const apps = ImmutableArray.of(dto);
return { ...s, apps };
});
}),
shareSubscribed(this.dialogs));
}
public create(request: CreateAppDto): Observable<AppDto> {
const http$ =
this.appsService.postApp(request).pipe(
share());
http$.subscribe(app => {
this.next(s => {
const apps = s.apps.push(app).sortByStringAsc(x => x.name);
return { ...s, apps };
});
}, error => {
this.dialogs.notifyError(error);
});
return http$;
return this.appsService.postApp(request).pipe(
tap(dto => {
this.next(s => {
const apps = s.apps.push(dto).sortByStringAsc(x => x.name);
return { ...s, apps };
});
}),
shareSubscribed(this.dialogs, { silent: true }));
}
public delete(name: string): Observable<any> {
const http$ =
this.appsService.deleteApp(name).pipe(
share());
http$.subscribe(() => {
this.next(s => {
const apps = s.apps.filter(x => x.name !== name);
const selectedApp =
s.selectedApp &&
s.selectedApp.name === name ?
null :
s.selectedApp;
return { ...s, apps, selectedApp };
});
}, error => {
this.dialogs.notifyError(error);
});
return http$;
return this.appsService.deleteApp(name).pipe(
tap(() => {
this.next(s => {
const apps = s.apps.filter(x => x.name !== name);
const selectedApp = s.selectedApp && s.selectedApp.name === name ? null : s.selectedApp;
return { ...s, apps, selectedApp };
});
}),
shareSubscribed(this.dialogs));
}
}

4
src/Squidex/app/shared/state/assets.state.spec.ts

@ -14,7 +14,7 @@ import {
AssetsService,
AssetsState,
DialogService,
Versioned
versioned
} from './../';
import { TestValues } from './_test-helpers';
@ -98,7 +98,7 @@ describe('AssetsState', () => {
it('should remove asset from snapshot when deleted', () => {
assetsService.setup(x => x.deleteAsset(app, oldAssets[0].id, version))
.returns(() => of(new Versioned<any>(newVersion, {})));
.returns(() => of(versioned(newVersion)));
assetsState.delete(oldAssets[0]).subscribe();

6
src/Squidex/app/shared/state/assets.state.ts

@ -12,8 +12,8 @@ import { distinctUntilChanged, map, tap } from 'rxjs/operators';
import {
DialogService,
ImmutableArray,
notify,
Pager,
shareSubscribed,
State
} from '@app/framework';
@ -107,7 +107,7 @@ export class AssetsState extends State<Snapshot> {
return { ...s, assets, assetsPager, isLoaded: true, tags: dtos[1] };
});
}),
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
public add(asset: AssetDto) {
@ -138,7 +138,7 @@ export class AssetsState extends State<Snapshot> {
return { ...s, assets, assetsPager, tags, tagsSelected };
});
}),
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
public update(asset: AssetDto) {

65
src/Squidex/app/shared/state/backups.state.ts

@ -7,11 +7,12 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { distinctUntilChanged, map, share } from 'rxjs/operators';
import { distinctUntilChanged, map, tap } from 'rxjs/operators';
import {
DialogService,
ImmutableArray,
shareSubscribed,
State
} from '@app/framework';
@ -56,55 +57,35 @@ export class BackupsState extends State<Snapshot> {
this.resetState();
}
const http$ =
this.backupsService.getBackups(this.appName).pipe(
share());
return this.backupsService.getBackups(this.appName).pipe(
tap(payload => {
if (isReload && !silent) {
this.dialogs.notifyInfo('Backups reloaded.');
}
http$.subscribe(dtos => {
if (isReload && !silent) {
this.dialogs.notifyInfo('Backups reloaded.');
}
this.next(s => {
const backups = ImmutableArray.of(payload);
this.next(s => {
const backups = ImmutableArray.of(dtos);
return { ...s, backups, isLoaded: true };
});
}, error => {
if (!silent) {
this.dialogs.notifyError(error);
}
});
return http$;
return { ...s, backups, isLoaded: true };
});
}),
shareSubscribed(this.dialogs, { silent }));
}
public start(): Observable<any> {
const http$ =
this.backupsService.postBackup(this.appsState.appName).pipe(
share());
http$.subscribe(() => {
this.dialogs.notifyInfo('Backup started, it can take several minutes to complete.');
}, error => {
this.dialogs.notifyError(error);
});
return http$;
return this.backupsService.postBackup(this.appsState.appName).pipe(
tap(() => {
this.dialogs.notifyInfo('Backup started, it can take several minutes to complete.');
}),
shareSubscribed(this.dialogs));
}
public delete(backup: BackupDto): Observable<any> {
const http$ =
this.backupsService.deleteBackup(this.appsState.appName, backup.id).pipe(
share());
http$.subscribe(() => {
this.dialogs.notifyInfo('Backup is about to be deleted.');
}, error => {
this.dialogs.notifyError(error);
});
return http$;
return this.backupsService.deleteBackup(this.appsState.appName, backup.id).pipe(
tap(() => {
this.dialogs.notifyInfo('Backup is about to be deleted.');
}),
shareSubscribed(this.dialogs));
}
private get appName() {

15
src/Squidex/app/shared/state/clients.state.spec.ts

@ -10,11 +10,10 @@ import { IMock, It, Mock, Times } from 'typemoq';
import {
ClientDto,
ClientsDto,
ClientsService,
ClientsState,
DialogService,
Versioned
versioned
} from './../';
import { TestValues } from './_test-helpers';
@ -50,7 +49,7 @@ describe('ClientsState', () => {
describe('Loading', () => {
it('should load clients', () => {
clientsService.setup(x => x.getClients(app))
.returns(() => of(new ClientsDto(oldClients, version))).verifiable();
.returns(() => of(versioned(version, oldClients))).verifiable();
clientsState.load().subscribe();
@ -63,7 +62,7 @@ describe('ClientsState', () => {
it('should show notification on load when reload is true', () => {
clientsService.setup(x => x.getClients(app))
.returns(() => of(new ClientsDto(oldClients, version))).verifiable();
.returns(() => of(versioned(version, oldClients))).verifiable();
clientsState.load(true).subscribe();
@ -76,7 +75,7 @@ describe('ClientsState', () => {
describe('Updates', () => {
beforeEach(() => {
clientsService.setup(x => x.getClients(app))
.returns(() => of(new ClientsDto(oldClients, version))).verifiable();
.returns(() => of(versioned(version, oldClients))).verifiable();
clientsState.load().subscribe();
});
@ -87,7 +86,7 @@ describe('ClientsState', () => {
const request = { id: 'id3' };
clientsService.setup(x => x.postClient(app, request, version))
.returns(() => of(new Versioned(newVersion, newClient))).verifiable();
.returns(() => of(versioned(newVersion, newClient))).verifiable();
clientsState.attach(request).subscribe();
@ -99,7 +98,7 @@ describe('ClientsState', () => {
const request = { name: 'NewName', role: 'NewRole' };
clientsService.setup(x => x.putClient(app, oldClients[0].id, request, version))
.returns(() => of(new Versioned(newVersion, {}))).verifiable();
.returns(() => of(versioned(newVersion))).verifiable();
clientsState.update(oldClients[0], request).subscribe();
@ -112,7 +111,7 @@ describe('ClientsState', () => {
it('should remove client from snapshot when revoked', () => {
clientsService.setup(x => x.deleteClient(app, oldClients[0].id, version))
.returns(() => of(new Versioned(newVersion, {}))).verifiable();
.returns(() => of(versioned(newVersion))).verifiable();
clientsState.revoke(oldClients[0]).subscribe();

97
src/Squidex/app/shared/state/clients.state.ts

@ -9,11 +9,13 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { distinctUntilChanged, map, share } from 'rxjs/operators';
import { distinctUntilChanged, map, tap } from 'rxjs/operators';
import {
DialogService,
ImmutableArray,
mapVersioned,
shareSubscribed,
State,
Version
} from '@app/framework';
@ -63,71 +65,56 @@ export class ClientsState extends State<Snapshot> {
this.resetState();
}
const http$ =
this.clientsService.getClients(this.appName).pipe(
share());
return this.clientsService.getClients(this.appName).pipe(
tap(({ version, payload: newClients }) => {
if (isReload) {
this.dialogs.notifyInfo('Clients reloaded.');
}
http$.subscribe(response => {
if (isReload) {
this.dialogs.notifyInfo('Clients reloaded.');
}
const clients = ImmutableArray.of(newClients);
const clients = ImmutableArray.of(response.clients);
this.next(s => {
return { ...s, clients, isLoaded: true, version: response.version };
});
});
return http$;
this.next(s => {
return { ...s, clients, isLoaded: true, version };
});
}),
shareSubscribed(this.dialogs));
}
public attach(request: CreateClientDto): Observable<ClientDto> {
const http$ =
this.clientsService.postClient(this.appName, request, this.version).pipe(
share());
http$.subscribe(({ version, payload }) => {
this.next(s => {
const clients = s.clients.push(payload);
return { ...s, clients, version: version };
});
});
return http$.pipe(map(x => x.payload));
return this.clientsService.postClient(this.appName, request, this.version).pipe(
tap(({ version, payload }) => {
this.next(s => {
const clients = s.clients.push(payload);
return { ...s, clients, version: version };
});
}),
shareSubscribed(this.dialogs, { project: x => x.payload }));
}
public revoke(client: ClientDto): Observable<any> {
const http$ =
this.clientsService.deleteClient(this.appName, client.id, this.version).pipe(
share());
http$.subscribe(({ version }) => {
this.next(s => {
const clients = s.clients.filter(c => c.id !== client.id);
return { ...s, clients, version };
});
});
return http$;
return this.clientsService.deleteClient(this.appName, client.id, this.version).pipe(
tap(({ version }) => {
this.next(s => {
const clients = s.clients.filter(c => c.id !== client.id);
return { ...s, clients, version };
});
}),
shareSubscribed(this.dialogs));
}
public update(client: ClientDto, request: UpdateClientDto): Observable<ClientDto> {
const http$ =
this.clientsService.putClient(this.appName, client.id, request, this.version).pipe(
map(({ version }) => ({ version, client: update(client, request) })), share());
http$.subscribe(({ version, client }) => {
this.next(s => {
const clients = s.clients.replaceBy('id', client);
return { ...s, clients, version };
});
});
return http$.pipe(map(x => x.client));
return this.clientsService.putClient(this.appName, client.id, request, this.version).pipe(
mapVersioned(() => update(client, request)),
tap(({ version, payload }) => {
this.next(s => {
const clients = s.clients.replaceBy('id', payload);
return { ...s, clients, version };
});
}),
shareSubscribed(this.dialogs, { project: x => x.payload }));
}
private get appName() {

124
src/Squidex/app/shared/state/comments.state.ts

@ -6,12 +6,13 @@
*/
import { Observable } from 'rxjs';
import { distinctUntilChanged, map, share } from 'rxjs/operators';
import { distinctUntilChanged, map, tap } from 'rxjs/operators';
import {
DateTime,
DialogService,
ImmutableArray,
shareSubscribed,
State,
Version
} from '@app/framework';
@ -51,89 +52,66 @@ export class CommentsState extends State<Snapshot> {
}
public load(): Observable<any> {
const http$ =
this.commentsService.getComments(this.appName, this.commentsId, this.version).pipe(
share());
http$.subscribe(response => {
this.next(s => {
let comments = s.comments;
for (let created of response.createdComments) {
if (!comments.find(x => x.id === created.id)) {
comments = comments.push(created);
return this.commentsService.getComments(this.appName, this.commentsId, this.version).pipe(
tap(payload => {
this.next(s => {
let comments = s.comments;
for (let created of payload.createdComments) {
if (!comments.find(x => x.id === created.id)) {
comments = comments.push(created);
}
}
}
for (let updated of response.updatedComments) {
comments = comments.replaceBy('id', updated);
}
for (let deleted of response.deletedComments) {
comments = comments.filter(x => x.id !== deleted);
}
for (let updated of payload.updatedComments) {
comments = comments.replaceBy('id', updated);
}
return { ...s, comments, isLoaded: true, version: response.version };
});
}, error => {
this.dialogs.notifyError(error);
});
for (let deleted of payload.deletedComments) {
comments = comments.filter(x => x.id !== deleted);
}
return http$;
return { ...s, comments, isLoaded: true, version: payload.version };
});
}),
shareSubscribed(this.dialogs));
}
public create(text: string): Observable<CommentDto> {
const http$ =
this.commentsService.postComment(this.appName, this.commentsId, { text }).pipe(
share());
http$.subscribe(comment => {
this.next(s => {
const comments = s.comments.push(comment);
return { ...s, comments };
});
}, error => {
this.dialogs.notifyError(error);
});
return http$;
}
public update(comment: CommentDto, text: string, now?: DateTime): Observable<CommentDto> {
const http$ =
this.commentsService.putComment(this.appName, this.commentsId, comment.id, { text }).pipe(
map(() => update(comment, text, now || DateTime.now())), share());
http$.subscribe(updated => {
this.next(s => {
const comments = s.comments.replaceBy('id', updated);
return { ...s, comments };
});
}, error => {
this.dialogs.notifyError(error);
});
return http$;
return this.commentsService.postComment(this.appName, this.commentsId, { text }).pipe(
tap(comment => {
this.next(s => {
const comments = s.comments.push(comment);
return { ...s, comments };
});
}),
shareSubscribed(this.dialogs));
}
public delete(comment: CommentDto): Observable<any> {
const http$ =
this.commentsService.deleteComment(this.appName, this.commentsId, comment.id).pipe(
share());
http$.subscribe(() => {
this.next(s => {
const comments = s.comments.removeBy('id', comment);
return { ...s, comments };
});
}, error => {
this.dialogs.notifyError(error);
});
return this.commentsService.deleteComment(this.appName, this.commentsId, comment.id).pipe(
tap(() => {
this.next(s => {
const comments = s.comments.removeBy('id', comment);
return { ...s, comments };
});
}),
shareSubscribed(this.dialogs));
}
return http$;
public update(comment: CommentDto, text: string, now?: DateTime): Observable<CommentDto> {
return this.commentsService.putComment(this.appName, this.commentsId, comment.id, { text }).pipe(
map(() => update(comment, text, now || DateTime.now())),
tap(updated => {
this.next(s => {
const comments = s.comments.replaceBy('id', updated);
return { ...s, comments };
});
}),
shareSubscribed(this.dialogs));
}
private get version() {

19
src/Squidex/app/shared/state/contents.state.ts

@ -16,6 +16,7 @@ import {
ImmutableArray,
notify,
Pager,
shareSubscribed,
State,
Version,
Versioned
@ -142,10 +143,10 @@ export abstract class ContentsStateBase extends State<Snapshot> {
return { ...s, contents, contentsPager, selectedContent, isLoaded: true };
});
}),
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
public create(request: any, publish: boolean) {
public create(request: any, publish: boolean): Observable<ContentDto> {
return this.contentsService.postContent(this.appName, this.schemaName, request, publish).pipe(
tap(dto => {
this.dialogs.notifyInfo('Contents created successfully.');
@ -157,7 +158,7 @@ export abstract class ContentsStateBase extends State<Snapshot> {
return { ...s, contents, contentsPager };
});
}),
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
public changeManyStatus(contents: ContentDto[], action: string, dueTime: string | null): Observable<any> {
@ -205,7 +206,7 @@ export abstract class ContentsStateBase extends State<Snapshot> {
this.replaceContent(confirmChanges(content, this.user, dto.version, now));
}
}),
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
public changeStatus(content: ContentDto, action: string, status: string, dueTime: string | null, now?: DateTime): Observable<any> {
@ -219,7 +220,7 @@ export abstract class ContentsStateBase extends State<Snapshot> {
this.replaceContent(changeStatus(content, status, this.user, dto.version, now));
}
}),
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
public update(content: ContentDto, request: any, now?: DateTime): Observable<any> {
@ -231,7 +232,7 @@ export abstract class ContentsStateBase extends State<Snapshot> {
this.replaceContent(updateData(content, dto.payload, this.user, dto.version, now));
}
}),
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
public proposeUpdate(content: ContentDto, request: any, now?: DateTime): Observable<any> {
@ -243,7 +244,7 @@ export abstract class ContentsStateBase extends State<Snapshot> {
this.replaceContent(updateDataDraft(content, dto.payload, this.user, dto.version, now));
}
}),
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
public discardChanges(content: ContentDto, now?: DateTime): Observable<any> {
@ -255,7 +256,7 @@ export abstract class ContentsStateBase extends State<Snapshot> {
this.replaceContent(discardChanges(content, this.user, dto.version, now));
}
}),
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
public patch(content: ContentDto, request: any, now?: DateTime): Observable<any> {
@ -267,7 +268,7 @@ export abstract class ContentsStateBase extends State<Snapshot> {
this.replaceContent(updateData(content, dto.payload, this.user, dto.version, now));
}
}),
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
private replaceContent(content: ContentDto) {

15
src/Squidex/app/shared/state/contributors.state.spec.ts

@ -10,11 +10,10 @@ import { IMock, It, Mock, Times } from 'typemoq';
import {
ContributorDto,
ContributorsDto,
ContributorsService,
ContributorsState,
DialogService,
Versioned
versioned
} from './../';
import { TestValues } from './_test-helpers';
@ -52,7 +51,7 @@ describe('ContributorsState', () => {
describe('Loading', () => {
it('should load contributors', () => {
contributorsService.setup(x => x.getContributors(app))
.returns(() => of(new ContributorsDto(oldContributors, 3, version))).verifiable();
.returns(() => of(versioned(version, { contributors: oldContributors, maxContributors: 3 }))).verifiable();
contributorsState.load().subscribe();
@ -70,7 +69,7 @@ describe('ContributorsState', () => {
it('should show notification on load when reload is true', () => {
contributorsService.setup(x => x.getContributors(app))
.returns(() => of(new ContributorsDto(oldContributors, 3, version))).verifiable();
.returns(() => of(versioned(version, { contributors: oldContributors, maxContributors: 3 }))).verifiable();
contributorsState.load(true).subscribe();
@ -83,7 +82,7 @@ describe('ContributorsState', () => {
describe('Updates', () => {
beforeEach(() => {
contributorsService.setup(x => x.getContributors(app))
.returns(() => of(new ContributorsDto(oldContributors, 3, version))).verifiable();
.returns(() => of(versioned(version, { contributors: oldContributors, maxContributors: 3 }))).verifiable();
contributorsState.load().subscribe();
});
@ -95,7 +94,7 @@ describe('ContributorsState', () => {
const response = { contributorId: newContributor.contributorId, isCreated: true };
contributorsService.setup(x => x.postContributor(app, request, version))
.returns(() => of(new Versioned(newVersion, response))).verifiable();
.returns(() => of(versioned(newVersion, response))).verifiable();
contributorsState.assign(request).subscribe();
@ -116,7 +115,7 @@ describe('ContributorsState', () => {
const response = { contributorId: newContributor.contributorId, isCreated: true };
contributorsService.setup(x => x.postContributor(app, request, version))
.returns(() => of(new Versioned(newVersion, response))).verifiable();
.returns(() => of(versioned(newVersion, response))).verifiable();
contributorsState.assign(request).subscribe();
@ -131,7 +130,7 @@ describe('ContributorsState', () => {
it('should remove contributor from snapshot when revoked', () => {
contributorsService.setup(x => x.deleteContributor(app, oldContributors[0].contributorId, version))
.returns(() => of(new Versioned(newVersion, {}))).verifiable();
.returns(() => of(versioned(newVersion))).verifiable();
contributorsState.revoke(oldContributors[0]).subscribe();

79
src/Squidex/app/shared/state/contributors.state.ts

@ -7,12 +7,13 @@
import { Injectable } from '@angular/core';
import { Observable, throwError } from 'rxjs';
import { catchError, distinctUntilChanged, map, share } from 'rxjs/operators';
import { catchError, distinctUntilChanged, map, tap } from 'rxjs/operators';
import {
DialogService,
ErrorDto,
ImmutableArray,
shareSubscribed,
State,
Types,
Version
@ -86,62 +87,44 @@ export class ContributorsState extends State<Snapshot> {
this.resetState();
}
const http$ =
this.contributorsService.getContributors(this.appName).pipe(
share());
return this.contributorsService.getContributors(this.appName).pipe(
tap(({ version, payload }) => {
if (isReload) {
this.dialogs.notifyInfo('Contributors reloaded.');
}
http$.subscribe(response => {
if (isReload) {
this.dialogs.notifyInfo('Contributors reloaded.');
}
const contributors = ImmutableArray.of(payload.contributors.map(x => this.createContributor(x)));
const contributors = ImmutableArray.of(response.contributors.map(x => this.createContributor(x)));
this.replaceContributors(contributors, response.version, response.maxContributors);
}, error => {
this.dialogs.notifyError(error);
});
return http$;
this.replaceContributors(contributors, version, payload.maxContributors);
}),
shareSubscribed(this.dialogs));
}
public revoke(contributor: ContributorDto): Observable<any> {
const http$ =
this.contributorsService.deleteContributor(this.appName, contributor.contributorId, this.version).pipe(
share());
http$.subscribe(({ version }) => {
const contributors = this.snapshot.contributors.filter(x => x.contributor.contributorId !== contributor.contributorId);
return this.contributorsService.deleteContributor(this.appName, contributor.contributorId, this.version).pipe(
tap(({ version }) => {
const contributors = this.snapshot.contributors.filter(x => x.contributor.contributorId !== contributor.contributorId);
this.replaceContributors(contributors, version);
}, error => {
this.dialogs.notifyError(error);
});
return http$;
this.replaceContributors(contributors, version);
}),
shareSubscribed(this.dialogs));
}
public assign(request: AssignContributorDto): Observable<boolean | undefined> {
const http$ =
this.contributorsService.postContributor(this.appName, request, this.version).pipe(
catchError(error => {
if (Types.is(error, ErrorDto) && error.statusCode === 404) {
return throwError(new ErrorDto(404, 'The user does not exist.'));
} else {
return throwError(error);
}
}),
share());
http$.subscribe(({ payload, version }) => {
const contributors = this.updateContributors(payload.contributorId, request.role);
this.replaceContributors(contributors, version);
}, error => {
this.dialogs.notifyError(error);
});
return http$.pipe(map(x => x.payload.isCreated));
return this.contributorsService.postContributor(this.appName, request, this.version).pipe(
catchError(error => {
if (Types.is(error, ErrorDto) && error.statusCode === 404) {
return throwError(new ErrorDto(404, 'The user does not exist.'));
} else {
return throwError(error);
}
}),
tap(({ version, payload }) => {
const contributors = this.updateContributors(payload.contributorId, request.role);
this.replaceContributors(contributors, version);
}),
shareSubscribed(this.dialogs, { project: x => x.payload.isCreated }));
}
private updateContributors(id: string, role: string) {

11
src/Squidex/app/shared/state/languages.state.spec.ts

@ -10,14 +10,13 @@ import { IMock, It, Mock, Times } from 'typemoq';
import {
AppLanguageDto,
AppLanguagesDto,
AppLanguagesService,
DialogService,
ImmutableArray,
LanguageDto,
LanguagesService,
LanguagesState,
Versioned
versioned
} from './../';
import { TestValues } from './_test-helpers';
@ -56,7 +55,7 @@ describe('LanguagesState', () => {
languagesService = Mock.ofType<AppLanguagesService>();
languagesService.setup(x => x.getLanguages(app))
.returns(() => of(new AppLanguagesDto(oldLanguages, version))).verifiable();
.returns(() => of({ payload: oldLanguages, version })).verifiable();
languagesState = new LanguagesState(languagesService.object, appsState.object, dialogs.object, allLanguagesService.object);
});
@ -107,7 +106,7 @@ describe('LanguagesState', () => {
const newLanguage = new AppLanguageDto(languageIT.iso2Code, languageIT.englishName, false, false, []);
languagesService.setup(x => x.postLanguage(app, It.isAny(), version))
.returns(() => of(new Versioned(newVersion, newLanguage))).verifiable();
.returns(() => of(versioned(newVersion, newLanguage))).verifiable();
languagesState.add(languageIT).subscribe();
@ -134,7 +133,7 @@ describe('LanguagesState', () => {
const request = { isMaster: true, isOptional: false, fallback: [] };
languagesService.setup(x => x.putLanguage(app, oldLanguages[1].iso2Code, request, version))
.returns(() => of(new Versioned(newVersion, {}))).verifiable();
.returns(() => of(versioned(newVersion))).verifiable();
languagesState.update(oldLanguages[1], request).subscribe();
@ -158,7 +157,7 @@ describe('LanguagesState', () => {
it('should remove language from snapshot when deleted', () => {
languagesService.setup(x => x.deleteLanguage(app, oldLanguages[1].iso2Code, version))
.returns(() => of(new Versioned(newVersion, {}))).verifiable();
.returns(() => of(versioned(newVersion))).verifiable();
languagesState.remove(oldLanguages[1]).subscribe();

63
src/Squidex/app/shared/state/languages.state.ts

@ -7,12 +7,13 @@
import { Injectable } from '@angular/core';
import { forkJoin, Observable } from 'rxjs';
import { distinctUntilChanged, map, share, tap } from 'rxjs/operators';
import { distinctUntilChanged, map, tap } from 'rxjs/operators';
import {
DialogService,
ImmutableArray,
notify,
mapVersioned,
shareSubscribed,
State,
Version
} from '@app/framework';
@ -95,57 +96,51 @@ export class LanguagesState extends State<Snapshot> {
this.resetState();
}
const http$ =
forkJoin(
return forkJoin(
this.languagesService.getLanguages(),
this.appLanguagesService.getLanguages(this.appName)).pipe(
map(args => {
return { allLanguages: args[0], languages: args[1] };
}),
share());
http$.subscribe(response => {
if (isReload) {
this.dialogs.notifyInfo('Languages reloaded.');
}
const sorted = ImmutableArray.of(response.allLanguages).sortByStringAsc(x => x.englishName);
this.replaceLanguages(ImmutableArray.of(response.languages.languages), response.languages.version, sorted);
map(args => {
return { allLanguages: args[0], languages: args[1] };
}),
tap(({ allLanguages, languages }) => {
if (isReload) {
this.dialogs.notifyInfo('Languages reloaded.');
}
}, error => {
this.dialogs.notifyError(error);
});
const sorted = ImmutableArray.of(allLanguages).sortByStringAsc(x => x.englishName);
return http$;
this.replaceLanguages(ImmutableArray.of(languages.payload), languages.version, sorted);
}),
shareSubscribed(this.dialogs));
}
public add(language: LanguageDto): Observable<any> {
public add(language: LanguageDto): Observable<AppLanguageDto> {
return this.appLanguagesService.postLanguage(this.appName, { language: language.iso2Code }, this.version).pipe(
tap(dto => {
const languages = this.snapshot.plainLanguages.push(dto.payload).sortByStringAsc(x => x.englishName);
tap(({ version, payload }) => {
const languages = this.snapshot.plainLanguages.push(payload).sortByStringAsc(x => x.englishName);
this.replaceLanguages(languages, dto.version);
this.replaceLanguages(languages, version);
}),
notify(this.dialogs));
shareSubscribed(this.dialogs, { project: x => x.payload }));
}
public remove(language: AppLanguageDto): Observable<any> {
return this.appLanguagesService.deleteLanguage(this.appName, language.iso2Code, this.version).pipe(
tap(dto => {
tap(({ version }) => {
const languages = this.snapshot.plainLanguages.filter(x => x.iso2Code !== language.iso2Code);
this.replaceLanguages(languages, dto.version);
this.replaceLanguages(languages, version);
}),
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
public update(language: AppLanguageDto, request: UpdateAppLanguageDto): Observable<any> {
public update(language: AppLanguageDto, request: UpdateAppLanguageDto): Observable<AppLanguageDto> {
return this.appLanguagesService.putLanguage(this.appName, language.iso2Code, request, this.version).pipe(
tap(dto => {
mapVersioned(() => update(language, request)),
tap(({ version, payload }) => {
const languages = this.snapshot.plainLanguages.map(x => {
if (x.iso2Code === language.iso2Code) {
return update(x, request);
return payload;
} else if (x.isMaster && request.isMaster) {
return update(x, { isMaster: false });
} else {
@ -153,9 +148,9 @@ export class LanguagesState extends State<Snapshot> {
}
});
this.replaceLanguages(languages, dto.version);
this.replaceLanguages(languages, version);
}),
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
private replaceLanguages(languages: AppLanguagesList, version: Version, allLanguages?: LanguageList) {

15
src/Squidex/app/shared/state/patterns.state.spec.ts

@ -11,10 +11,9 @@ import { IMock, It, Mock, Times } from 'typemoq';
import {
DialogService,
PatternDto,
PatternsDto,
PatternsService,
PatternsState,
Versioned
versioned
} from './../';
import { TestValues } from './_test-helpers';
@ -50,7 +49,7 @@ describe('PatternsState', () => {
describe('Loading', () => {
it('should load patterns', () => {
patternsService.setup(x => x.getPatterns(app))
.returns(() => of(new PatternsDto(oldPatterns, version))).verifiable();
.returns(() => of({ payload: oldPatterns, version })).verifiable();
patternsState.load().subscribe();
@ -62,7 +61,7 @@ describe('PatternsState', () => {
it('should show notification on load when reload is true', () => {
patternsService.setup(x => x.getPatterns(app))
.returns(() => of(new PatternsDto(oldPatterns, version))).verifiable();
.returns(() => of({ payload: oldPatterns, version })).verifiable();
patternsState.load(true).subscribe();
@ -75,7 +74,7 @@ describe('PatternsState', () => {
describe('Updates', () => {
beforeEach(() => {
patternsService.setup(x => x.getPatterns(app))
.returns(() => of(new PatternsDto(oldPatterns, version))).verifiable();
.returns(() => of({ payload: oldPatterns, version })).verifiable();
patternsState.load().subscribe();
});
@ -86,7 +85,7 @@ describe('PatternsState', () => {
const request = { ...newPattern };
patternsService.setup(x => x.postPattern(app, request, version))
.returns(() => of(new Versioned(newVersion, newPattern))).verifiable();
.returns(() => of(versioned(newVersion, newPattern))).verifiable();
patternsState.create(request).subscribe();
@ -98,7 +97,7 @@ describe('PatternsState', () => {
const request = { name: 'name2_1', pattern: 'pattern2_1', message: 'message2_1' };
patternsService.setup(x => x.putPattern(app, oldPatterns[1].id, request, version))
.returns(() => of(new Versioned(newVersion, {}))).verifiable();
.returns(() => of(versioned(newVersion))).verifiable();
patternsState.update(oldPatterns[1], request).subscribe();
@ -112,7 +111,7 @@ describe('PatternsState', () => {
it('should remove pattern from snapshot when deleted', () => {
patternsService.setup(x => x.deletePattern(app, oldPatterns[0].id, version))
.returns(() => of(new Versioned(newVersion, {}))).verifiable();
.returns(() => of(versioned(newVersion))).verifiable();
patternsState.delete(oldPatterns[0]).subscribe();

105
src/Squidex/app/shared/state/patterns.state.ts

@ -7,11 +7,13 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { distinctUntilChanged, map, share } from 'rxjs/operators';
import { distinctUntilChanged, map, tap } from 'rxjs/operators';
import {
DialogService,
ImmutableArray,
mapVersioned,
shareSubscribed,
State,
Version
} from '@app/framework';
@ -60,79 +62,56 @@ export class PatternsState extends State<Snapshot> {
this.resetState();
}
const update$ =
this.patternsService.getPatterns(this.appName).pipe(
share());
return this.patternsService.getPatterns(this.appName).pipe(
tap(({ version, payload }) => {
if (isReload) {
this.dialogs.notifyInfo('Patterns reloaded.');
}
update$.subscribe(({ version, patterns: items }) => {
if (isReload) {
this.dialogs.notifyInfo('Patterns reloaded.');
}
this.next(s => {
const patterns = ImmutableArray.of(payload).sortByStringAsc(x => x.name);
this.next(s => {
const patterns = ImmutableArray.of(items).sortByStringAsc(x => x.name);
return { ...s, patterns, isLoaded: true, version: version };
});
}, error => {
this.dialogs.notifyError(error);
});
return update$;
return { ...s, patterns, isLoaded: true, version: version };
});
}),
shareSubscribed(this.dialogs, { project: x => x.payload }));
}
public create(request: EditPatternDto): Observable<PatternDto> {
const update$ =
this.patternsService.postPattern(this.appName, request, this.version).pipe(
share());
update$.subscribe(({ version, payload: pattern }) => {
this.next(s => {
const patterns = s.patterns.push(pattern).sortByStringAsc(x => x.name);
return { ...s, patterns, version: version };
});
}, error => {
this.dialogs.notifyError(error);
});
return update$.pipe(map(x => x.payload));
return this.patternsService.postPattern(this.appName, request, this.version).pipe(
tap(({ version, payload }) => {
this.next(s => {
const patterns = s.patterns.push(payload).sortByStringAsc(x => x.name);
return { ...s, patterns, version: version };
});
}),
shareSubscribed(this.dialogs, { project: x => x.payload }));
}
public update(pattern: PatternDto, request: EditPatternDto): Observable<PatternDto> {
const update$ =
this.patternsService.putPattern(this.appName, pattern.id, request, this.version).pipe(
map(({ version }) => ({ version, payload: update(pattern, request) })), share());
update$.subscribe(({ version, payload }) => {
this.next(s => {
const patterns = s.patterns.replaceBy('id', payload).sortByStringAsc(x => x.name);
return { ...s, patterns, version: version };
});
}, error => {
this.dialogs.notifyError(error);
});
return update$.pipe(map(x => x.payload));
return this.patternsService.putPattern(this.appName, pattern.id, request, this.version).pipe(
mapVersioned(() => update(pattern, request)),
tap(({ version, payload }) => {
this.next(s => {
const patterns = s.patterns.replaceBy('id', payload).sortByStringAsc(x => x.name);
return { ...s, patterns, version: version };
});
}),
shareSubscribed(this.dialogs, { project: x => x.payload }));
}
public delete(pattern: PatternDto): Observable<any> {
const update$ =
this.patternsService.deletePattern(this.appName, pattern.id, this.version).pipe(
share());
update$.subscribe(({ version }) => {
this.next(s => {
const patterns = s.patterns.filter(c => c.id !== pattern.id);
return { ...s, patterns, version: version };
});
}, error => {
this.dialogs.notifyError(error);
});
return update$;
return this.patternsService.deletePattern(this.appName, pattern.id, this.version).pipe(
tap(({ version }) => {
this.next(s => {
const patterns = s.patterns.filter(c => c.id !== pattern.id);
return { ...s, patterns, version: version };
});
}),
shareSubscribed(this.dialogs));
}
private get appName() {

147
src/Squidex/app/shared/state/plans.state.spec.ts

@ -12,10 +12,9 @@ import { IMock, It, Mock, Times } from 'typemoq';
import {
DialogService,
PlanDto,
PlansDto,
PlansService,
PlansState,
Versioned
versioned
} from './../';
import { TestValues } from './_test-helpers';
@ -25,16 +24,20 @@ describe('PlansState', () => {
app,
appsState,
authService,
creator,
newVersion,
version
} = TestValues;
const oldPlans =
new PlansDto('id1', 'id2', true, [
const oldPlans = {
currentPlanId: 'id1',
planOwner: creator,
plans: [
new PlanDto('id1', 'name1', '100€', 'id1_yearly', '200€', 1, 1, 1),
new PlanDto('id2', 'name2', '400€', 'id2_yearly', '800€', 2, 2, 2)
],
version);
hasPortal: true
};
let dialogs: IMock<DialogService>;
let plansService: IMock<PlansService>;
@ -44,84 +47,102 @@ describe('PlansState', () => {
dialogs = Mock.ofType<DialogService>();
plansService = Mock.ofType<PlansService>();
plansService.setup(x => x.getPlans(app))
.returns(() => of(oldPlans));
plansState = new PlansState(appsState.object, authService.object, dialogs.object, plansService.object);
});
it('should load plans', () => {
plansState.load().pipe(onErrorResumeNext()).subscribe();
afterEach(() => {
plansService.verifyAll();
});
describe('Loading', () => {
it('should load plans', () => {
plansService.setup(x => x.getPlans(app))
.returns(() => of(versioned(version, oldPlans))).verifiable();
expect(plansState.snapshot.plans.values).toEqual([
{ isSelected: true, isYearlySelected: false, plan: oldPlans.plans[0] },
{ isSelected: false, isYearlySelected: false, plan: oldPlans.plans[1] }
]);
expect(plansState.snapshot.isOwner).toBeFalsy();
expect(plansState.snapshot.isLoaded).toBeTruthy();
expect(plansState.snapshot.hasPortal).toBeTruthy();
expect(plansState.snapshot.version).toEqual(version);
plansState.load().subscribe();
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.never());
});
expect(plansState.snapshot.plans.values).toEqual([
{ isSelected: true, isYearlySelected: false, plan: oldPlans.plans[0] },
{ isSelected: false, isYearlySelected: false, plan: oldPlans.plans[1] }
]);
expect(plansState.snapshot.isOwner).toBeFalsy();
expect(plansState.snapshot.isLoaded).toBeTruthy();
expect(plansState.snapshot.hasPortal).toBeTruthy();
expect(plansState.snapshot.version).toEqual(version);
it('should load plans with overriden id', () => {
plansState.load(false, 'id2_yearly').pipe(onErrorResumeNext()).subscribe();
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.never());
});
expect(plansState.snapshot.plans.values).toEqual([
{ isSelected: false, isYearlySelected: false, plan: oldPlans.plans[0] },
{ isSelected: false, isYearlySelected: true, plan: oldPlans.plans[1] }
]);
expect(plansState.snapshot.isOwner).toBeFalsy();
expect(plansState.snapshot.isLoaded).toBeTruthy();
expect(plansState.snapshot.hasPortal).toBeTruthy();
expect(plansState.snapshot.version).toEqual(version);
it('should load plans with overriden id', () => {
plansService.setup(x => x.getPlans(app))
.returns(() => of(versioned(version, oldPlans))).verifiable();
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.never());
});
plansState.load(false, 'id2_yearly').subscribe();
it('should show notification on load when reload is true', () => {
plansState.load(true).subscribe();
expect(plansState.snapshot.plans.values).toEqual([
{ isSelected: false, isYearlySelected: false, plan: oldPlans.plans[0] },
{ isSelected: false, isYearlySelected: true, plan: oldPlans.plans[1] }
]);
expect(plansState.snapshot.isOwner).toBeFalsy();
expect(plansState.snapshot.isLoaded).toBeTruthy();
expect(plansState.snapshot.hasPortal).toBeTruthy();
expect(plansState.snapshot.version).toEqual(version);
expect().nothing();
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.never());
});
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.once());
it('should show notification on load when reload is true', () => {
plansService.setup(x => x.getPlans(app))
.returns(() => of(versioned(version, oldPlans))).verifiable();
plansState.load(true).subscribe();
expect().nothing();
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.once());
});
});
it('should redirect when returning url', () => {
plansState.window = <any>{ location: {} };
describe('Updates', () => {
beforeEach(() => {
plansState.window = <any>{ location: {} };
const result = { redirectUri: 'http://url' };
plansService.setup(x => x.getPlans(app))
.returns(() => of(versioned(version, oldPlans))).verifiable();
plansService.setup(x => x.putPlan(app, It.isAny(), version))
.returns(() => of(new Versioned(newVersion, result)));
plansState.load().subscribe();
});
plansState.load().subscribe();
plansState.change('free').pipe(onErrorResumeNext()).subscribe();
it('should redirect when returning url', () => {
plansState.window = <any>{ location: {} };
expect(plansState.snapshot.plans.values).toEqual([
{ isSelected: true, isYearlySelected: false, plan: oldPlans.plans[0] },
{ isSelected: false, isYearlySelected: false, plan: oldPlans.plans[1] }
]);
expect(plansState.window.location.href).toBe(result.redirectUri);
expect(plansState.snapshot.version).toEqual(version);
});
const result = { redirectUri: 'http://url' };
plansService.setup(x => x.putPlan(app, It.isAny(), version))
.returns(() => of(versioned(newVersion, result)));
plansState.change('free').pipe(onErrorResumeNext()).subscribe();
it('should update plans when no returning url', () => {
plansState.window = <any>{ location: {} };
expect(plansState.snapshot.plans.values).toEqual([
{ isSelected: true, isYearlySelected: false, plan: oldPlans.plans[0] },
{ isSelected: false, isYearlySelected: false, plan: oldPlans.plans[1] }
]);
expect(plansState.window.location.href).toBe(result.redirectUri);
expect(plansState.snapshot.version).toEqual(version);
});
plansService.setup(x => x.putPlan(app, It.isAny(), version))
.returns(() => of(new Versioned(newVersion, { redirectUri: '' })));
it('should update plans when no returning url', () => {
plansService.setup(x => x.putPlan(app, It.isAny(), version))
.returns(() => of(versioned(newVersion, { redirectUri: '' })));
plansState.load().subscribe();
plansState.change('id2_yearly').pipe(onErrorResumeNext()).subscribe();
plansState.change('id2_yearly').pipe(onErrorResumeNext()).subscribe();
expect(plansState.snapshot.plans.values).toEqual([
{ isSelected: false, isYearlySelected: false, plan: oldPlans.plans[0] },
{ isSelected: false, isYearlySelected: true, plan: oldPlans.plans[1] }
]);
expect(plansState.snapshot.isOwner).toBeTruthy();
expect(plansState.snapshot.version).toEqual(newVersion);
expect(plansState.snapshot.plans.values).toEqual([
{ isSelected: false, isYearlySelected: false, plan: oldPlans.plans[0] },
{ isSelected: false, isYearlySelected: true, plan: oldPlans.plans[1] }
]);
expect(plansState.snapshot.isOwner).toBeTruthy();
expect(plansState.snapshot.version).toEqual(newVersion);
});
});
});

18
src/Squidex/app/shared/state/plans.state.ts

@ -12,7 +12,7 @@ import { distinctUntilChanged, map, tap } from 'rxjs/operators';
import {
DialogService,
ImmutableArray,
notify,
shareSubscribed,
State,
Version
} from '@app/framework';
@ -88,26 +88,26 @@ export class PlansState extends State<Snapshot> {
}
return this.plansService.getPlans(this.appName).pipe(
tap(dto => {
tap(({ version, payload }) => {
if (isReload) {
this.dialogs.notifyInfo('Plans reloaded.');
}
this.next(s => {
const planId = overridePlanId || dto.currentPlanId;
const plans = ImmutableArray.of(dto.plans.map(x => this.createPlan(x, planId)));
const planId = overridePlanId || payload.currentPlanId;
const plans = ImmutableArray.of(payload.plans.map(x => this.createPlan(x, planId)));
return {
...s,
plans: plans,
isOwner: !dto.planOwner || dto.planOwner === this.userId,
isOwner: !payload.planOwner || payload.planOwner === this.userId,
isLoaded: true,
version: dto.version,
hasPortal: dto.hasPortal
version,
hasPortal: payload.hasPortal
};
});
}),
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
public change(planId: string): Observable<any> {
@ -123,7 +123,7 @@ export class PlansState extends State<Snapshot> {
});
}
}),
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
private createPlan(plan: PlanDto, id: string) {

15
src/Squidex/app/shared/state/roles.state.spec.ts

@ -11,10 +11,9 @@ import { IMock, It, Mock, Times } from 'typemoq';
import {
DialogService,
RoleDto,
RolesDto,
RolesService,
RolesState,
Versioned
versioned
} from './../';
import { TestValues } from './_test-helpers';
@ -46,7 +45,7 @@ describe('RolesState', () => {
describe('Loading', () => {
it('should load roles', () => {
rolesService.setup(x => x.getRoles(app))
.returns(() => of(new RolesDto(oldRoles, version))).verifiable();
.returns(() => of({ payload: oldRoles, version })).verifiable();
rolesState.load().subscribe();
@ -59,7 +58,7 @@ describe('RolesState', () => {
it('should show notification on load when reload is true', () => {
rolesService.setup(x => x.getRoles(app))
.returns(() => of(new RolesDto(oldRoles, version))).verifiable();
.returns(() => of(versioned(version, oldRoles))).verifiable();
rolesState.load(true).subscribe();
@ -72,7 +71,7 @@ describe('RolesState', () => {
describe('Updates', () => {
beforeEach(() => {
rolesService.setup(x => x.getRoles(app))
.returns(() => of(new RolesDto(oldRoles, version)));
.returns(() => of(versioned(version, oldRoles))).verifiable();
rolesState.load().subscribe();
});
@ -83,7 +82,7 @@ describe('RolesState', () => {
const request = { name: newRole.name };
rolesService.setup(x => x.postRole(app, request, version))
.returns(() => of(new Versioned(newVersion, newRole)));
.returns(() => of(versioned(newVersion, newRole)));
rolesState.add(request).subscribe();
@ -95,7 +94,7 @@ describe('RolesState', () => {
const request = { permissions: ['P4', 'P5'] };
rolesService.setup(x => x.putRole(app, oldRoles[1].name, request, version))
.returns(() => of(new Versioned(newVersion, {})));
.returns(() => of(versioned(newVersion)));
rolesState.update(oldRoles[1], request).subscribe();
@ -107,7 +106,7 @@ describe('RolesState', () => {
it('should remove role from snapshot when deleted', () => {
rolesService.setup(x => x.deleteRole(app, oldRoles[0].name, version))
.returns(() => of(new Versioned(newVersion, {})));
.returns(() => of(versioned(newVersion)));
rolesState.delete(oldRoles[0]).subscribe();

40
src/Squidex/app/shared/state/roles.state.ts

@ -12,7 +12,8 @@ import { distinctUntilChanged, map, tap } from 'rxjs/operators';
import {
DialogService,
ImmutableArray,
notify,
mapVersioned,
shareSubscribed,
State,
Version
} from '@app/framework';
@ -62,55 +63,56 @@ export class RolesState extends State<Snapshot> {
this.resetState();
}
return this.rolesService.getRoles(this.appName).pipe(
tap(dtos => {
return this.rolesService.getRoles(this.appName).pipe(
tap(({ payload, version }) => {
if (isReload) {
this.dialogs.notifyInfo('Roles reloaded.');
}
this.next(s => {
const roles = ImmutableArray.of(dtos.roles).sortByStringAsc(x => x.name);
const roles = ImmutableArray.of(payload).sortByStringAsc(x => x.name);
return { ...s, roles, isLoaded: true, version: dtos.version };
return { ...s, roles, isLoaded: true, version };
});
}),
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
public add(request: CreateRoleDto): Observable<any> {
public add(request: CreateRoleDto): Observable<RoleDto> {
return this.rolesService.postRole(this.appName, request, this.version).pipe(
tap(dto => {
tap(({ version, payload }) => {
this.next(s => {
const roles = s.roles.push(dto.payload).sortByStringAsc(x => x.name);
const roles = s.roles.push(payload).sortByStringAsc(x => x.name);
return { ...s, roles, version: dto.version };
return { ...s, roles, version };
});
}),
notify(this.dialogs));
shareSubscribed(this.dialogs, { project: x => x.payload }));
}
public delete(role: RoleDto): Observable<any> {
return this.rolesService.deleteRole(this.appName, role.name, this.version).pipe(
tap(dto => {
tap(({ version }) => {
this.next(s => {
const roles = s.roles.removeBy('name', role);
return { ...s, roles, version: dto.version };
return { ...s, roles, version };
});
}),
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
public update(role: RoleDto, request: UpdateRoleDto): Observable<any> {
public update(role: RoleDto, request: UpdateRoleDto): Observable<RoleDto> {
return this.rolesService.putRole(this.appName, role.name, request, this.version).pipe(
tap(dto => {
mapVersioned(() => update(role, request)),
tap(({ version, payload }) => {
this.next(s => {
const roles = s.roles.replaceBy('name', update(role, request));
const roles = s.roles.replaceBy('name', payload);
return { ...s, roles, version: dto.version };
return { ...s, roles, version };
});
}),
notify(this.dialogs));
shareSubscribed(this.dialogs, { project: x => x.payload }));
}
private get appName() {

8
src/Squidex/app/shared/state/rule-events.state.ts

@ -12,8 +12,8 @@ import { distinctUntilChanged, map, tap } from 'rxjs/operators';
import {
DialogService,
ImmutableArray,
notify,
Pager,
shareSubscribed,
State
} from '@app/framework';
@ -78,7 +78,7 @@ export class RuleEventsState extends State<Snapshot> {
return { ...s, ruleEvents, ruleEventsPager, isLoaded: true };
});
}),
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
public enqueue(event: RuleEventDto): Observable<any> {
@ -86,7 +86,7 @@ export class RuleEventsState extends State<Snapshot> {
tap(() => {
this.dialogs.notifyInfo('Events enqueued. Will be resend in a few seconds.');
}),
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
public cancel(event: RuleEventDto): Observable<any> {
@ -98,7 +98,7 @@ export class RuleEventsState extends State<Snapshot> {
return { ...s, ruleEvents, isLoaded: true };
});
}),
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
public goNext(): Observable<any> {

12
src/Squidex/app/shared/state/rules.state.spec.ts

@ -14,7 +14,7 @@ import {
DialogService,
RuleDto,
RulesService,
Versioned
versioned
} from './../';
import { TestValues } from './_test-helpers';
@ -103,7 +103,7 @@ describe('RulesState', () => {
const newAction = {};
rulesService.setup(x => x.putRule(app, oldRules[0].id, It.isAny(), version))
.returns(() => of(new Versioned(newVersion, {}))).verifiable();
.returns(() => of(versioned(newVersion))).verifiable();
rulesState.updateAction(oldRules[0], newAction, modified).subscribe();
@ -117,7 +117,7 @@ describe('RulesState', () => {
const newTrigger = {};
rulesService.setup(x => x.putRule(app, oldRules[0].id, It.isAny(), version))
.returns(() => of(new Versioned(newVersion, {}))).verifiable();
.returns(() => of(versioned(newVersion))).verifiable();
rulesState.updateTrigger(oldRules[0], newTrigger, modified).subscribe();
@ -129,7 +129,7 @@ describe('RulesState', () => {
it('should mark as enabled and update and user info when enabled', () => {
rulesService.setup(x => x.enableRule(app, oldRules[0].id, version))
.returns(() => of(new Versioned(newVersion, {}))).verifiable();
.returns(() => of(versioned(newVersion))).verifiable();
rulesState.enable(oldRules[0], modified).subscribe();
@ -141,7 +141,7 @@ describe('RulesState', () => {
it('should mark as disabled and update and user info when disabled', () => {
rulesService.setup(x => x.disableRule(app, oldRules[1].id, version))
.returns(() => of(new Versioned(newVersion, {}))).verifiable();
.returns(() => of(versioned(newVersion))).verifiable();
rulesState.disable(oldRules[1], modified).subscribe();
@ -153,7 +153,7 @@ describe('RulesState', () => {
it('should remove rule from snapshot when deleted', () => {
rulesService.setup(x => x.deleteRule(app, oldRules[0].id, version))
.returns(() => of(new Versioned(newVersion, {}))).verifiable();
.returns(() => of(versioned(newVersion))).verifiable();
rulesState.delete(oldRules[0]).subscribe();

143
src/Squidex/app/shared/state/rules.state.ts

@ -7,12 +7,13 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { distinctUntilChanged, map, share } from 'rxjs/operators';
import { distinctUntilChanged, map, tap } from 'rxjs/operators';
import {
DateTime,
DialogService,
ImmutableArray,
shareSubscribed,
State,
Version
} from '@app/framework';
@ -60,112 +61,86 @@ export class RulesState extends State<Snapshot> {
this.resetState();
}
const http$ =
this.rulesService.getRules(this.appName).pipe(
share());
return this.rulesService.getRules(this.appName).pipe(
tap(payload => {
if (isReload) {
this.dialogs.notifyInfo('Rules reloaded.');
}
http$.subscribe(response => {
if (isReload) {
this.dialogs.notifyInfo('Rules reloaded.');
}
this.next(s => {
const rules = ImmutableArray.of(payload);
this.next(s => {
const rules = ImmutableArray.of(response);
return { ...s, rules, isLoaded: true };
});
}, error => {
this.dialogs.notifyError(error);
});
return http$;
return { ...s, rules, isLoaded: true };
});
}),
shareSubscribed(this.dialogs));
}
public create(request: CreateRuleDto, now?: DateTime): Observable<RuleDto> {
const http$ =
this.rulesService.postRule(this.appName, request, this.user, now || DateTime.now()).pipe(
share());
http$.subscribe(rule => {
this.next(s => {
const rules = s.rules.push(rule);
return { ...s, rules };
});
}, error => {
this.dialogs.notifyError(error);
});
return http$;
return this.rulesService.postRule(this.appName, request, this.user, now || DateTime.now()).pipe(
tap(rule => {
this.next(s => {
const rules = s.rules.push(rule);
return { ...s, rules };
});
}),
shareSubscribed(this.dialogs));
}
public delete(rule: RuleDto): Observable<any> {
const http$ =
this.rulesService.deleteRule(this.appName, rule.id, rule.version).pipe(
share());
http$.subscribe(() => {
this.next(s => {
const rules = s.rules.removeAll(x => x.id === rule.id);
return { ...s, rules };
});
}, error => {
this.dialogs.notifyError(error);
});
return http$;
return this.rulesService.deleteRule(this.appName, rule.id, rule.version).pipe(
tap(() => {
this.next(s => {
const rules = s.rules.removeAll(x => x.id === rule.id);
return { ...s, rules };
});
}),
shareSubscribed(this.dialogs));
}
public updateAction(rule: RuleDto, action: any, now?: DateTime): Observable<any> {
const http$ =
this.rulesService.putRule(this.appName, rule.id, { action }, rule.version).pipe(
map(({ version }) => updateAction(rule, action, this.user, version, now)), share());
this.replaceRule(http$);
return http$;
return this.rulesService.putRule(this.appName, rule.id, { action }, rule.version).pipe(
map(({ version }) => updateAction(rule, action, this.user, version, now)),
tap(updated => {
this.replaceRule(updated);
}),
shareSubscribed(this.dialogs));
}
public updateTrigger(rule: RuleDto, trigger: any, now?: DateTime): Observable<any> {
const http$ =
this.rulesService.putRule(this.appName, rule.id, { trigger }, rule.version).pipe(
map(({ version }) => updateTrigger(rule, trigger, this.user, version, now)), share());
this.replaceRule(http$);
return http$;
return this.rulesService.putRule(this.appName, rule.id, { trigger }, rule.version).pipe(
map(({ version }) => updateTrigger(rule, trigger, this.user, version, now)),
tap(updated => {
this.replaceRule(updated);
}),
shareSubscribed(this.dialogs));
}
public enable(rule: RuleDto, now?: DateTime): Observable<any> {
const http$ =
this.rulesService.enableRule(this.appName, rule.id, rule.version).pipe(
map(({ version }) => setEnabled(rule, true, this.user, version, now)), share());
this.replaceRule(http$);
return http$;
return this.rulesService.enableRule(this.appName, rule.id, rule.version).pipe(
map(({ version }) => setEnabled(rule, true, this.user, version, now)),
tap(updated => {
this.replaceRule(updated);
}),
shareSubscribed(this.dialogs));
}
public disable(rule: RuleDto, now?: DateTime): Observable<any> {
const http$ =
this.rulesService.disableRule(this.appName, rule.id, rule.version).pipe(
map(({ version }) => setEnabled(rule, false, this.user, version, now)), share());
this.replaceRule(http$);
return http$;
return this.rulesService.disableRule(this.appName, rule.id, rule.version).pipe(
map(({ version }) => setEnabled(rule, false, this.user, version, now)),
tap(updated => {
this.replaceRule(updated);
}),
shareSubscribed(this.dialogs));
}
private replaceRule(http$: Observable<RuleDto>) {
http$.subscribe(rule => {
this.next(s => {
const rules = s.rules.replaceBy('id', rule);
private replaceRule(rule: RuleDto) {
this.next(s => {
const rules = s.rules.replaceBy('id', rule);
return { ...s, rules };
});
}, error => {
this.dialogs.notifyError(error);
return { ...s, rules };
});
}

630
src/Squidex/app/shared/state/schemas.state.spec.ts

@ -21,7 +21,7 @@ import {
SchemaDto,
SchemasService,
UpdateSchemaCategoryDto,
Versioned
versioned
} from './../';
import { TestValues } from './_test-helpers';
@ -65,167 +65,152 @@ describe('SchemasState', () => {
dialogs = Mock.ofType<DialogService>();
schemasService = Mock.ofType<SchemasService>();
schemasService.setup(x => x.getSchemas(app))
.returns(() => of(oldSchemas));
schemasService.setup(x => x.getSchema(app, schema.name))
.returns(() => of(schema));
schemasService.setup(x => x.getSchema(app, schema.name))
.returns(() => of(schema));
schemasState = new SchemasState(appsState.object, authService.object, dialogs.object, schemasService.object);
schemasState.load().subscribe();
});
it('should load schemas', () => {
expect(schemasState.snapshot.schemas.values).toEqual(oldSchemas);
expect(schemasState.snapshot.isLoaded).toBeTruthy();
expect(schemasState.snapshot.categories).toEqual({ 'category1': false, 'category2': false });
afterEach(() => {
schemasService.verifyAll();
});
it('should not remove custom category when loading schemas', () => {
schemasState.addCategory('category3');
schemasState.load(true).subscribe();
describe('Loading', () => {
it('should load schemas', () => {
schemasService.setup(x => x.getSchemas(app))
.returns(() => of(oldSchemas)).verifiable();
expect(schemasState.snapshot.schemas.values).toEqual(oldSchemas);
expect(schemasState.snapshot.isLoaded).toBeTruthy();
expect(schemasState.snapshot.categories).toEqual({ 'category1': false, 'category2': false, 'category3': true });
schemasState.load().subscribe();
schemasService.verifyAll();
});
expect(schemasState.snapshot.schemas.values).toEqual(oldSchemas);
expect(schemasState.snapshot.isLoaded).toBeTruthy();
expect(schemasState.snapshot.categories).toEqual({ 'category1': false, 'category2': false });
it('should show notification on load when reload is true', () => {
schemasState.load(true).subscribe();
schemasService.verifyAll();
});
expect().nothing();
it('should not remove custom category when loading schemas', () => {
schemasService.setup(x => x.getSchemas(app))
.returns(() => of(oldSchemas)).verifiable();
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.once());
});
schemasState.addCategory('category3');
schemasState.load(true).subscribe();
it('should add category', () => {
schemasState.addCategory('category3');
expect(schemasState.snapshot.schemas.values).toEqual(oldSchemas);
expect(schemasState.snapshot.isLoaded).toBeTruthy();
expect(schemasState.snapshot.categories).toEqual({ 'category1': false, 'category2': false, 'category3': true });
expect(schemasState.snapshot.categories).toEqual({ 'category1': false, 'category2': false, 'category3': true });
});
schemasService.verifyAll();
});
it('should remove category', () => {
schemasState.removeCategory('category1');
it('should show notification on load when reload is true', () => {
schemasService.setup(x => x.getSchemas(app))
.returns(() => of(oldSchemas)).verifiable();
expect(schemasState.snapshot.categories).toEqual({ 'category2': false });
});
schemasState.load(true).subscribe();
it('should return schema on select and reload when already loaded', () => {
schemasState.select('name2').subscribe();
schemasState.select('name2').subscribe();
expect().nothing();
schemasService.verify(x => x.getSchema(app, 'name2'), Times.exactly(2));
expect().nothing();
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.once());
});
});
it('should return schema on select and load when not loaded', () => {
let selectedSchema: SchemaDetailsDto;
describe('Updates', () => {
beforeEach(() => {
schemasService.setup(x => x.getSchemas(app))
.returns(() => of(oldSchemas)).verifiable();
schemasState.select('name2').subscribe(x => {
selectedSchema = x!;
schemasState.load().subscribe();
});
expect(selectedSchema!).toBe(schema);
expect(schemasState.snapshot.selectedSchema).toBe(schema);
expect(schemasState.snapshot.selectedSchema).toBe(<SchemaDetailsDto>schemasState.snapshot.schemas.at(1));
});
it('should add category', () => {
schemasState.addCategory('category3');
it('should return null on select when loading failed', () => {
schemasService.setup(x => x.getSchema(app, 'failed'))
.returns(() => throwError({}));
expect(schemasState.snapshot.categories).toEqual({ 'category1': false, 'category2': false, 'category3': true });
});
let selectedSchema: SchemaDetailsDto;
it('should remove category', () => {
schemasState.removeCategory('category1');
schemasState.select('failed').subscribe(x => {
selectedSchema = x!;
expect(schemasState.snapshot.categories).toEqual({ 'category2': false });
});
expect(selectedSchema!).toBeNull();
expect(schemasState.snapshot.selectedSchema).toBeNull();
});
it('should return schema on select and reload when already loaded', () => {
schemasService.setup(x => x.getSchema(app, schema.name))
.returns(() => of(schema)).verifiable(Times.exactly(2));
it('should return null on select when unselecting schema', () => {
let selectedSchema: SchemaDetailsDto;
schemasState.select('name2').subscribe();
schemasState.select('name2').subscribe();
schemasState.select(null).subscribe(x => {
selectedSchema = x!;
expect().nothing();
});
expect(selectedSchema!).toBeNull();
expect(schemasState.snapshot.selectedSchema).toBeNull();
it('should return schema on select and load when not loaded', () => {
schemasService.setup(x => x.getSchema(app, schema.name))
.returns(() => of(schema)).verifiable();
schemasService.verify(x => x.getSchema(app, It.isAnyString()), Times.never());
});
let selectedSchema: SchemaDetailsDto;
it('should mark published and update user info when published', () => {
schemasService.setup(x => x.publishSchema(app, oldSchemas[0].name, version))
.returns(() => of(new Versioned(newVersion, {})));
schemasState.select('name2').subscribe(x => {
selectedSchema = x!;
});
schemasState.publish(oldSchemas[0], modified).subscribe();
expect(selectedSchema!).toBe(schema);
expect(schemasState.snapshot.selectedSchema).toBe(schema);
expect(schemasState.snapshot.selectedSchema).toBe(<SchemaDetailsDto>schemasState.snapshot.schemas.at(1));
});
const schema_1 = schemasState.snapshot.schemas.at(0);
it('should return null on select when loading failed', () => {
schemasService.setup(x => x.getSchema(app, 'failed'))
.returns(() => throwError({})).verifiable();
expect(schema_1.isPublished).toBeTruthy();
expectToBeModified(schema_1);
});
let selectedSchema: SchemaDetailsDto;
it('should unmark published and update user info when unpublished', () => {
schemasService.setup(x => x.unpublishSchema(app, oldSchemas[1].name, version))
.returns(() => of(new Versioned(newVersion, {})));
schemasState.select('failed').subscribe(x => {
selectedSchema = x!;
});
schemasState.unpublish(oldSchemas[1], modified).subscribe();
const schema_1 = schemasState.snapshot.schemas.at(1);
expect(selectedSchema!).toBeNull();
expect(schemasState.snapshot.selectedSchema).toBeNull();
});
expect(schema_1.isPublished).toBeFalsy();
expectToBeModified(schema_1);
});
it('should return null on select when unselecting schema', () => {
let selectedSchema: SchemaDetailsDto;
it('should change category and update user info when category changed', () => {
const category = 'my-new-category';
schemasState.select(null).subscribe(x => {
selectedSchema = x!;
});
schemasService.setup(x => x.putCategory(app, oldSchemas[0].name, It.is<UpdateSchemaCategoryDto>(i => i.name === category), version))
.returns(() => of(new Versioned(newVersion, {})));
expect(selectedSchema!).toBeNull();
expect(schemasState.snapshot.selectedSchema).toBeNull();
});
schemasState.changeCategory(oldSchemas[0], category, modified).subscribe();
it('should mark published and update user info when published', () => {
schemasService.setup(x => x.publishSchema(app, oldSchemas[0].name, version))
.returns(() => of(versioned(newVersion))).verifiable();
const schema_1 = schemasState.snapshot.schemas.at(0);
schemasState.publish(oldSchemas[0], modified).subscribe();
expect(schema_1.category).toEqual(category);
expectToBeModified(schema_1);
});
const schema_1 = schemasState.snapshot.schemas.at(0);
describe('with selection', () => {
beforeEach(() => {
schemasState.select(schema.name).subscribe();
expect(schema_1.isPublished).toBeTruthy();
expectToBeModified(schema_1);
});
it('should nmark published and update user info when published selected schema', () => {
schemasService.setup(x => x.publishSchema(app, schema.name, version))
.returns(() => of(new Versioned(newVersion, {})));
it('should unmark published and update user info when unpublished', () => {
schemasService.setup(x => x.unpublishSchema(app, oldSchemas[1].name, version))
.returns(() => of(versioned(newVersion))).verifiable();
schemasState.publish(schema, modified).subscribe();
schemasState.unpublish(oldSchemas[1], modified).subscribe();
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
const schema_1 = schemasState.snapshot.schemas.at(1);
expect(schema_1.isPublished).toBeTruthy();
expect(schema_1.isPublished).toBeFalsy();
expectToBeModified(schema_1);
});
it('should change category and update user info when category of selected schema changed', () => {
it('should change category and update user info when category changed', () => {
const category = 'my-new-category';
schemasService.setup(x => x.putCategory(app, oldSchemas[0].name, It.is<UpdateSchemaCategoryDto>(i => i.name === category), version))
.returns(() => of(new Versioned(newVersion, {})));
.returns(() => of(versioned(newVersion))).verifiable();
schemasState.changeCategory(oldSchemas[0], category, modified).subscribe();
@ -235,305 +220,340 @@ describe('SchemasState', () => {
expectToBeModified(schema_1);
});
it('should update properties and update user info when updated', () => {
const request = { label: 'name2_label', hints: 'name2_hints' };
describe('with selection', () => {
beforeEach(() => {
schemasService.setup(x => x.getSchema(app, schema.name))
.returns(() => of(schema)).verifiable();
schemasService.setup(x => x.putSchema(app, schema.name, It.isAny(), version))
.returns(() => of(new Versioned(newVersion, {})));
schemasState.select(schema.name).subscribe();
});
schemasState.update(schema, request, modified).subscribe();
it('should nmark published and update user info when published selected schema', () => {
schemasService.setup(x => x.publishSchema(app, schema.name, version))
.returns(() => of(versioned(newVersion))).verifiable();
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
schemasState.publish(schema, modified).subscribe();
expect(schema_1.properties.label).toEqual(request.label);
expect(schema_1.properties.hints).toEqual(request.hints);
expectToBeModified(schema_1);
});
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
it('should update script properties and update user info when scripts configured', () => {
const request = { query: '<query-script>' };
expect(schema_1.isPublished).toBeTruthy();
expectToBeModified(schema_1);
});
schemasService.setup(x => x.putScripts(app, schema.name, It.isAny(), version))
.returns(() => of(new Versioned(newVersion, {})));
it('should change category and update user info when category of selected schema changed', () => {
const category = 'my-new-category';
schemasState.configureScripts(schema, request, modified).subscribe();
schemasService.setup(x => x.putCategory(app, oldSchemas[0].name, It.is<UpdateSchemaCategoryDto>(i => i.name === category), version))
.returns(() => of(versioned(newVersion))).verifiable();
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
schemasState.changeCategory(oldSchemas[0], category, modified).subscribe();
expect(schema_1.scripts['query']).toEqual('<query-script>');
expectToBeModified(schema_1);
});
const schema_1 = schemasState.snapshot.schemas.at(0);
it('should update script properties and update user info when preview urls configured', () => {
const request = { web: 'url' };
expect(schema_1.category).toEqual(category);
expectToBeModified(schema_1);
});
schemasService.setup(x => x.putPreviewUrls(app, schema.name, It.isAny(), version))
.returns(() => of(new Versioned(newVersion, {})));
it('should update properties and update user info when updated', () => {
const request = { label: 'name2_label', hints: 'name2_hints' };
schemasState.configurePreviewUrls(schema, request, modified).subscribe();
schemasService.setup(x => x.putSchema(app, schema.name, It.isAny(), version))
.returns(() => of(versioned(newVersion))).verifiable();
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
schemasState.update(schema, request, modified).subscribe();
expect(schema_1.previewUrls).toEqual(request);
expectToBeModified(schema_1);
});
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
it('should add schema to snapshot when created', () => {
const request = new CreateSchemaDto('newName');
expect(schema_1.properties.label).toEqual(request.label);
expect(schema_1.properties.hints).toEqual(request.hints);
expectToBeModified(schema_1);
});
const result = new SchemaDetailsDto('id4', 'newName', '', {}, false, false, modified, modifier, modified, modifier, version);
it('should update script properties and update user info when scripts configured', () => {
const request = { query: '<query-script>' };
schemasService.setup(x => x.postSchema(app, request, modifier, modified))
.returns(() => of(result));
schemasService.setup(x => x.putScripts(app, schema.name, It.isAny(), version))
.returns(() => of(versioned(newVersion))).verifiable();
schemasState.create(request, modified).subscribe();
schemasState.configureScripts(schema, request, modified).subscribe();
expect(schemasState.snapshot.schemas.values.length).toBe(3);
expect(schemasState.snapshot.schemas.at(2)).toBe(result);
});
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
it('should remove schema from snapshot when deleted', () => {
schemasService.setup(x => x.deleteSchema(app, schema.name, version))
.returns(() => of(new Versioned(newVersion, {})));
expect(schema_1.scripts['query']).toEqual('<query-script>');
expectToBeModified(schema_1);
});
schemasState.delete(schema).subscribe();
it('should update script properties and update user info when preview urls configured', () => {
const request = { web: 'url' };
expect(schemasState.snapshot.schemas.values.length).toBe(1);
expect(schemasState.snapshot.selectedSchema).toBeNull();
});
schemasService.setup(x => x.putPreviewUrls(app, schema.name, It.isAny(), version))
.returns(() => of(versioned(newVersion))).verifiable();
it('should add field and update user info when field added', () => {
const request = new AddFieldDto(field1.name, field1.partitioning, field1.properties);
schemasState.configurePreviewUrls(schema, request, modified).subscribe();
const newField = new RootFieldDto(3, '3', createProperties('String'), 'invariant');
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
schemasService.setup(x => x.postField(app, schema.name, It.isAny(), undefined, version))
.returns(() => of(new Versioned(newVersion, newField)));
expect(schema_1.previewUrls).toEqual(request);
expectToBeModified(schema_1);
});
schemasState.addField(schema, request, undefined, modified).subscribe();
it('should add schema to snapshot when created', () => {
const request = new CreateSchemaDto('newName');
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
const result = new SchemaDetailsDto('id4', 'newName', '', {}, false, false, modified, modifier, modified, modifier, version);
expect(schema_1.fields).toEqual([field1, field2, newField]);
expectToBeModified(schema_1);
});
schemasService.setup(x => x.postSchema(app, request, modifier, modified))
.returns(() => of(result)).verifiable();
it('should add field and update user info when nested field added', () => {
const request = new AddFieldDto(field1.name, field1.partitioning, field1.properties);
schemasState.create(request, modified).subscribe();
const newField = new NestedFieldDto(3, '3', createProperties('String'), 2);
expect(schemasState.snapshot.schemas.values.length).toBe(3);
expect(schemasState.snapshot.schemas.at(2)).toBe(result);
});
schemasService.setup(x => x.postField(app, schema.name, It.isAny(), 2, version))
.returns(() => of(new Versioned(newVersion, newField)));
it('should remove schema from snapshot when deleted', () => {
schemasService.setup(x => x.deleteSchema(app, schema.name, version))
.returns(() => of(versioned(newVersion))).verifiable();
schemasState.addField(schema, request, field2, modified).subscribe();
schemasState.delete(schema).subscribe();
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
expect(schemasState.snapshot.schemas.values.length).toBe(1);
expect(schemasState.snapshot.selectedSchema).toBeNull();
});
expect(schema_1.fields[1].nested).toEqual([nested1, nested2, newField]);
expectToBeModified(schema_1);
});
it('should add field and update user info when field added', () => {
const request = new AddFieldDto(field1.name, field1.partitioning, field1.properties);
it('should remove field and update user info when field removed', () => {
schemasService.setup(x => x.deleteField(app, schema.name, field1.fieldId, undefined, version))
.returns(() => of(new Versioned(newVersion, {})));
const newField = new RootFieldDto(3, '3', createProperties('String'), 'invariant');
schemasState.deleteField(schema, field1, modified).subscribe();
schemasService.setup(x => x.postField(app, schema.name, It.isAny(), undefined, version))
.returns(() => of(versioned(newVersion, newField))).verifiable();
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
schemasState.addField(schema, request, undefined, modified).subscribe();
expect(schema_1.fields).toEqual([field2]);
expectToBeModified(schema_1);
});
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
it('should remove field and update user info when nested field removed', () => {
schemasService.setup(x => x.deleteField(app, schema.name, nested1.fieldId, 2, version))
.returns(() => of(new Versioned(newVersion, {})));
expect(schema_1.fields).toEqual([field1, field2, newField]);
expectToBeModified(schema_1);
});
schemasState.deleteField(schema, nested1, modified).subscribe();
it('should add field and update user info when nested field added', () => {
const request = new AddFieldDto(field1.name, field1.partitioning, field1.properties);
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
const newField = new NestedFieldDto(3, '3', createProperties('String'), 2);
expect(schema_1.fields[1].nested).toEqual([nested2]);
expectToBeModified(schema_1);
});
schemasService.setup(x => x.postField(app, schema.name, It.isAny(), 2, version))
.returns(() => of(versioned(newVersion, newField))).verifiable();
it('should sort fields and update user info when fields sorted', () => {
schemasService.setup(x => x.putFieldOrdering(app, schema.name, [field2.fieldId, field1.fieldId], undefined, version))
.returns(() => of(new Versioned(newVersion, {})));
schemasState.addField(schema, request, field2, modified).subscribe();
schemasState.sortFields(schema, [field2, field1], undefined, modified).subscribe();
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
expect(schema_1.fields[1].nested).toEqual([nested1, nested2, newField]);
expectToBeModified(schema_1);
});
expect(schema_1.fields).toEqual([field2, field1]);
expectToBeModified(schema_1);
});
it('should remove field and update user info when field removed', () => {
schemasService.setup(x => x.deleteField(app, schema.name, field1.fieldId, undefined, version))
.returns(() => of(versioned(newVersion))).verifiable();
it('should sort fields and update user info when nested fields sorted', () => {
schemasService.setup(x => x.putFieldOrdering(app, schema.name, [nested2.fieldId, nested1.fieldId], 2, version))
.returns(() => of(new Versioned(newVersion, {})));
schemasState.deleteField(schema, field1, modified).subscribe();
schemasState.sortFields(schema, [nested2, nested1], field2, modified).subscribe();
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
expect(schema_1.fields).toEqual([field2]);
expectToBeModified(schema_1);
});
expect(schema_1.fields[1].nested).toEqual([nested2, nested1]);
expectToBeModified(schema_1);
});
it('should remove field and update user info when nested field removed', () => {
schemasService.setup(x => x.deleteField(app, schema.name, nested1.fieldId, 2, version))
.returns(() => of(versioned(newVersion))).verifiable();
it('should update field properties and update user info when field updated', () => {
const request = { properties: createProperties('String') };
schemasState.deleteField(schema, nested1, modified).subscribe();
schemasService.setup(x => x.putField(app, schema.name, field1.fieldId, request, undefined, version))
.returns(() => of(new Versioned(newVersion, {})));
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
schemasState.updateField(schema, field1, request, modified).subscribe();
expect(schema_1.fields[1].nested).toEqual([nested2]);
expectToBeModified(schema_1);
});
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
it('should sort fields and update user info when fields sorted', () => {
schemasService.setup(x => x.putFieldOrdering(app, schema.name, [field2.fieldId, field1.fieldId], undefined, version))
.returns(() => of(versioned(newVersion))).verifiable();
expect(schema_1.fields[0].properties).toBe(request.properties);
expectToBeModified(schema_1);
});
schemasState.sortFields(schema, [field2, field1], undefined, modified).subscribe();
it('should update field properties and update user info when nested field updated', () => {
const request = { properties: createProperties('String') };
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
schemasService.setup(x => x.putField(app, schema.name, nested1.fieldId, request, 2, version))
.returns(() => of(new Versioned(newVersion, {})));
expect(schema_1.fields).toEqual([field2, field1]);
expectToBeModified(schema_1);
});
schemasState.updateField(schema, nested1, request, modified).subscribe();
it('should sort fields and update user info when nested fields sorted', () => {
schemasService.setup(x => x.putFieldOrdering(app, schema.name, [nested2.fieldId, nested1.fieldId], 2, version))
.returns(() => of(versioned(newVersion))).verifiable();
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
schemasState.sortFields(schema, [nested2, nested1], field2, modified).subscribe();
expect(schema_1.fields[1].nested[0].properties).toBe(request.properties);
expectToBeModified(schema_1);
});
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
it('should mark field hidden and update user info when field hidden', () => {
schemasService.setup(x => x.hideField(app, schema.name, field1.fieldId, undefined, version))
.returns(() => of(new Versioned(newVersion, {})));
expect(schema_1.fields[1].nested).toEqual([nested2, nested1]);
expectToBeModified(schema_1);
});
schemasState.hideField(schema, field1, modified).subscribe();
it('should update field properties and update user info when field updated', () => {
const request = { properties: createProperties('String') };
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
schemasService.setup(x => x.putField(app, schema.name, field1.fieldId, request, undefined, version))
.returns(() => of(versioned(newVersion))).verifiable();
expect(schema_1.fields[0].isHidden).toBeTruthy();
expectToBeModified(schema_1);
});
schemasState.updateField(schema, field1, request, modified).subscribe();
it('should mark field hidden and update user info when nested field hidden', () => {
schemasService.setup(x => x.hideField(app, schema.name, nested1.fieldId, 2, version))
.returns(() => of(new Versioned(newVersion, {})));
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
schemasState.hideField(schema, nested1, modified).subscribe();
expect(schema_1.fields[0].properties).toBe(request.properties);
expectToBeModified(schema_1);
});
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
it('should update field properties and update user info when nested field updated', () => {
const request = { properties: createProperties('String') };
expect(schema_1.fields[1].nested[0].isHidden).toBeTruthy();
expectToBeModified(schema_1);
});
schemasService.setup(x => x.putField(app, schema.name, nested1.fieldId, request, 2, version))
.returns(() => of(versioned(newVersion))).verifiable();
it('should mark field disabled and update user info when field disabled', () => {
schemasService.setup(x => x.disableField(app, schema.name, field1.fieldId, undefined, version))
.returns(() => of(new Versioned(newVersion, {})));
schemasState.updateField(schema, nested1, request, modified).subscribe();
schemasState.disableField(schema, field1, modified).subscribe();
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
expect(schema_1.fields[1].nested[0].properties).toBe(request.properties);
expectToBeModified(schema_1);
});
expect(schema_1.fields[0].isDisabled).toBeTruthy();
expectToBeModified(schema_1);
});
it('should mark field hidden and update user info when field hidden', () => {
schemasService.setup(x => x.hideField(app, schema.name, field1.fieldId, undefined, version))
.returns(() => of(versioned(newVersion))).verifiable();
it('should mark field disabled and update user info when nested disabled', () => {
schemasService.setup(x => x.disableField(app, schema.name, nested1.fieldId, 2, version))
.returns(() => of(new Versioned(newVersion, {})));
schemasState.hideField(schema, field1, modified).subscribe();
schemasState.disableField(schema, nested1, modified).subscribe();
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
expect(schema_1.fields[0].isHidden).toBeTruthy();
expectToBeModified(schema_1);
});
expect(schema_1.fields[1].nested[0].isDisabled).toBeTruthy();
expectToBeModified(schema_1);
});
it('should mark field hidden and update user info when nested field hidden', () => {
schemasService.setup(x => x.hideField(app, schema.name, nested1.fieldId, 2, version))
.returns(() => of(versioned(newVersion))).verifiable();
it('should mark field locked and update user info when field locked', () => {
schemasService.setup(x => x.lockField(app, schema.name, field1.fieldId, undefined, version))
.returns(() => of(new Versioned(newVersion, {})));
schemasState.hideField(schema, nested1, modified).subscribe();
schemasState.lockField(schema, field1, modified).subscribe();
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
expect(schema_1.fields[1].nested[0].isHidden).toBeTruthy();
expectToBeModified(schema_1);
});
expect(schema_1.fields[0].isLocked).toBeTruthy();
expectToBeModified(schema_1);
});
it('should mark field disabled and update user info when field disabled', () => {
schemasService.setup(x => x.disableField(app, schema.name, field1.fieldId, undefined, version))
.returns(() => of(versioned(newVersion)));
it('should mark field locked and update user info when nested field locked', () => {
schemasService.setup(x => x.lockField(app, schema.name, nested1.fieldId, 2, version))
.returns(() => of(new Versioned(newVersion, {})));
schemasState.disableField(schema, field1, modified).subscribe();
schemasState.lockField(schema, nested1, modified).subscribe();
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
expect(schema_1.fields[0].isDisabled).toBeTruthy();
expectToBeModified(schema_1);
});
expect(schema_1.fields[1].nested[0].isLocked).toBeTruthy();
expectToBeModified(schema_1);
});
it('should mark field disabled and update user info when nested disabled', () => {
schemasService.setup(x => x.disableField(app, schema.name, nested1.fieldId, 2, version))
.returns(() => of(versioned(newVersion))).verifiable();
it('should unmark field hidden and update user info when field shown', () => {
schemasService.setup(x => x.showField(app, schema.name, field2.fieldId, undefined, version))
.returns(() => of(new Versioned(newVersion, {})));
schemasState.disableField(schema, nested1, modified).subscribe();
schemasState.showField(schema, field2, modified).subscribe();
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
expect(schema_1.fields[1].nested[0].isDisabled).toBeTruthy();
expectToBeModified(schema_1);
});
expect(schema_1.fields[1].isHidden).toBeFalsy();
expectToBeModified(schema_1);
});
it('should mark field locked and update user info when field locked', () => {
schemasService.setup(x => x.lockField(app, schema.name, field1.fieldId, undefined, version))
.returns(() => of(versioned(newVersion))).verifiable();
it('should unmark field hidden and update user info when nested field shown', () => {
schemasService.setup(x => x.showField(app, schema.name, nested2.fieldId, 2, version))
.returns(() => of(new Versioned(newVersion, {})));
schemasState.lockField(schema, field1, modified).subscribe();
schemasState.showField(schema, nested2, modified).subscribe();
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
expect(schema_1.fields[0].isLocked).toBeTruthy();
expectToBeModified(schema_1);
});
expect(schema_1.fields[1].nested[1].isHidden).toBeFalsy();
expectToBeModified(schema_1);
});
it('should mark field locked and update user info when nested field locked', () => {
schemasService.setup(x => x.lockField(app, schema.name, nested1.fieldId, 2, version))
.returns(() => of(versioned(newVersion))).verifiable();
it('should unmark field disabled and update user info when field enabled', () => {
schemasService.setup(x => x.enableField(app, schema.name, field2.fieldId, undefined, version))
.returns(() => of(new Versioned(newVersion, {})));
schemasState.lockField(schema, nested1, modified).subscribe();
schemasState.enableField(schema, field2, modified).subscribe();
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
expect(schema_1.fields[1].nested[0].isLocked).toBeTruthy();
expectToBeModified(schema_1);
});
expect(schema_1.fields[1].isDisabled).toBeFalsy();
expectToBeModified(schema_1);
});
it('should unmark field hidden and update user info when field shown', () => {
schemasService.setup(x => x.showField(app, schema.name, field2.fieldId, undefined, version))
.returns(() => of(versioned(newVersion))).verifiable();
it('should unmark field disabled and update user info when nested field enabled', () => {
schemasService.setup(x => x.enableField(app, schema.name, nested2.fieldId, 2, version))
.returns(() => of(new Versioned(newVersion, {})));
schemasState.showField(schema, field2, modified).subscribe();
schemasState.enableField(schema, nested2, modified).subscribe();
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
expect(schema_1.fields[1].isHidden).toBeFalsy();
expectToBeModified(schema_1);
});
expect(schema_1.fields[1].nested[1].isDisabled).toBeFalsy();
expectToBeModified(schema_1);
it('should unmark field hidden and update user info when nested field shown', () => {
schemasService.setup(x => x.showField(app, schema.name, nested2.fieldId, 2, version))
.returns(() => of(versioned(newVersion))).verifiable();
schemasState.showField(schema, nested2, modified).subscribe();
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
expect(schema_1.fields[1].nested[1].isHidden).toBeFalsy();
expectToBeModified(schema_1);
});
it('should unmark field disabled and update user info when field enabled', () => {
schemasService.setup(x => x.enableField(app, schema.name, field2.fieldId, undefined, version))
.returns(() => of(versioned(newVersion))).verifiable();
schemasState.enableField(schema, field2, modified).subscribe();
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
expect(schema_1.fields[1].isDisabled).toBeFalsy();
expectToBeModified(schema_1);
});
it('should unmark field disabled and update user info when nested field enabled', () => {
schemasService.setup(x => x.enableField(app, schema.name, nested2.fieldId, 2, version))
.returns(() => of(versioned(newVersion))).verifiable();
schemasState.enableField(schema, nested2, modified).subscribe();
const schema_1 = <SchemaDetailsDto>schemasState.snapshot.schemas.at(1);
expect(schema_1.fields[1].nested[1].isDisabled).toBeFalsy();
expectToBeModified(schema_1);
});
});
});
function expectToBeModified(schema_1: SchemaDto) {
expect(schema_1.lastModified).toEqual(modified);
expect(schema_1.lastModifiedBy).toEqual(modifier);
expect(schema_1.version).toEqual(newVersion);
}
function expectToBeModified(schema_1: SchemaDto) {
expect(schema_1.lastModified).toEqual(modified);
expect(schema_1.lastModifiedBy).toEqual(modifier);
expect(schema_1.version).toEqual(newVersion);
}
});
});

179
src/Squidex/app/shared/state/schemas.state.ts

@ -13,7 +13,8 @@ import {
DateTime,
DialogService,
ImmutableArray,
notify,
mapVersioned,
shareSubscribed,
State,
Types,
Version
@ -45,7 +46,7 @@ interface Snapshot {
categories: { [name: string]: boolean };
// The current schemas.
schemas: ImmutableArray<SchemaDto>;
schemas: SchemasList;
// Indicates if the schemas are loaded.
isLoaded?: boolean;
@ -54,6 +55,8 @@ interface Snapshot {
selectedSchema?: SchemaDetailsDto | null;
}
export type SchemasList = ImmutableArray<SchemaDto>;
function sameSchema(lhs: SchemaDetailsDto | null, rhs?: SchemaDetailsDto | null): boolean {
return lhs === rhs || (!!lhs && !!rhs && lhs.id === rhs.id && lhs.version === rhs.version);
}
@ -116,44 +119,45 @@ export class SchemasState extends State<Snapshot> {
}
return this.schemasService.getSchemas(this.appName).pipe(
tap(dtos => {
tap(payload => {
if (isReload) {
this.dialogs.notifyInfo('Schemas reloaded.');
}
return this.next(s => {
const schemas = ImmutableArray.of(dtos).sortByStringAsc(x => x.displayName);
const schemas = ImmutableArray.of(payload).sortByStringAsc(x => x.displayName);
const categories = buildCategories(s.categories, schemas);
return { ...s, schemas, isLoaded: true, categories };
});
}),
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
public create(request: CreateSchemaDto, now?: DateTime) {
public create(request: CreateSchemaDto, now?: DateTime): Observable<SchemaDetailsDto> {
return this.schemasService.postSchema(this.appName, request, this.user, now || DateTime.now()).pipe(
tap(dto => {
return this.next(s => {
this.next(s => {
const schemas = s.schemas.push(dto).sortByStringAsc(x => x.displayName);
return { ...s, schemas };
});
}));
}),
shareSubscribed(this.dialogs, { silent: true }));
}
public delete(schema: SchemaDto): Observable<any> {
return this.schemasService.deleteSchema(this.appName, schema.name, schema.version).pipe(
tap(() => {
return this.next(s => {
this.next(s => {
const schemas = s.schemas.filter(x => x.id !== schema.id);
const selectedSchema = s.selectedSchema && s.selectedSchema.id === schema.id ? null : s.selectedSchema;
return { ...s, schemas, selectedSchema };
});
}),
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
public addCategory(name: string) {
@ -172,138 +176,165 @@ export class SchemasState extends State<Snapshot> {
});
}
public publish(schema: SchemaDto, now?: DateTime): Observable<any> {
public publish(schema: SchemaDto, now?: DateTime): Observable<SchemaDto> {
return this.schemasService.publishSchema(this.appName, schema.name, schema.version).pipe(
tap(dto => {
this.replaceSchema(setPublished(schema, true, this.user, dto.version, now));
map(({ version }) => setPublished(schema, true, this.user, version, now)),
tap(newSchema => {
this.replaceSchema(newSchema);
}),
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
public unpublish(schema: SchemaDto, now?: DateTime): Observable<any> {
public unpublish(schema: SchemaDto, now?: DateTime): Observable<SchemaDto> {
return this.schemasService.unpublishSchema(this.appName, schema.name, schema.version).pipe(
tap(dto => {
this.replaceSchema(setPublished(schema, false, this.user, dto.version, now));
map(({ version }) => setPublished(schema, false, this.user, version, now)),
tap(newSchema => {
this.replaceSchema(newSchema);
}),
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
public changeCategory(schema: SchemaDto, name: string, now?: DateTime): Observable<any> {
public changeCategory(schema: SchemaDto, name: string, now?: DateTime): Observable<SchemaDto> {
return this.schemasService.putCategory(this.appName, schema.name, { name }, schema.version).pipe(
tap(dto => {
this.replaceSchema(changeCategory(schema, name, this.user, dto.version, now));
map(({ version }) => changeCategory(schema, name, this.user, version, now)),
tap(newSchema => {
this.replaceSchema(newSchema);
}),
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
public configurePreviewUrls(schema: SchemaDetailsDto, request: {}, now?: DateTime): Observable<any> {
public configurePreviewUrls(schema: SchemaDetailsDto, request: {}, now?: DateTime): Observable<SchemaDetailsDto> {
return this.schemasService.putPreviewUrls(this.appName, schema.name, request, schema.version).pipe(
tap(dto => {
this.replaceSchema(configurePreviewUrls(schema, request, this.user, dto.version, now));
map(({ version }) => configurePreviewUrls(schema, request, this.user, version, now)),
tap(newSchema => {
this.replaceSchema(newSchema);
}),
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
public configureScripts(schema: SchemaDetailsDto, request: {}, now?: DateTime): Observable<any> {
public configureScripts(schema: SchemaDetailsDto, request: {}, now?: DateTime): Observable<SchemaDetailsDto> {
return this.schemasService.putScripts(this.appName, schema.name, request, schema.version).pipe(
tap(dto => {
this.replaceSchema(configureScripts(schema, request, this.user, dto.version, now));
map(({ version }) => configureScripts(schema, request, this.user, version, now)),
tap(newSchema => {
this.replaceSchema(newSchema);
}),
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
public update(schema: SchemaDetailsDto, request: UpdateSchemaDto, now?: DateTime): Observable<any> {
public update(schema: SchemaDetailsDto, request: UpdateSchemaDto, now?: DateTime): Observable<SchemaDetailsDto> {
return this.schemasService.putSchema(this.appName, schema.name, request, schema.version).pipe(
tap(dto => {
this.replaceSchema(updateProperties(schema, request, this.user, dto.version, now));
map(({ version }) => updateProperties(schema, request, this.user, version, now)),
tap(newSchema => {
this.replaceSchema(newSchema);
}),
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
public addField(schema: SchemaDetailsDto, request: AddFieldDto, parent?: RootFieldDto, now?: DateTime): Observable<FieldDto> {
return this.schemasService.postField(this.appName, schema.name, request, pid(parent), schema.version).pipe(
tap(dto => {
if (Types.is(dto.payload, NestedFieldDto)) {
this.replaceSchema(updateField(schema, addNested(parent!, dto.payload), this.user, dto.version, now));
map(({ version, payload }) => {
let newSchema: SchemaDto;
if (Types.is(payload, NestedFieldDto)) {
newSchema = updateField(schema, addNested(parent!, payload), this.user, version, now);
} else {
this.replaceSchema(addField(schema, dto.payload, this.user, dto.version, now));
newSchema = addField(schema, payload, this.user, version, now);
}
return { newSchema, field: payload };
}),
tap(({ newSchema }) => {
this.replaceSchema(newSchema);
}),
map(d => d.payload));
shareSubscribed(this.dialogs, { silent: true, project: x => x.field }));
}
public sortFields(schema: SchemaDetailsDto, fields: any[], parent?: RootFieldDto, now?: DateTime): Observable<any> {
public sortFields(schema: SchemaDetailsDto, fields: any[], parent?: RootFieldDto, now?: DateTime): Observable<SchemaDetailsDto> {
return this.schemasService.putFieldOrdering(this.appName, schema.name, fields.map(t => t.fieldId), pid(parent), schema.version).pipe(
tap(dto => {
map(({ version }) => {
let newSchema: SchemaDto;
if (!parent) {
this.replaceSchema(replaceFields(schema, fields, this.user, dto.version, now));
newSchema = replaceFields(schema, fields, this.user, version, now);
} else {
this.replaceSchema(updateField(schema, replaceNested(parent, fields), this.user, dto.version, now));
newSchema = updateField(schema, replaceNested(parent, fields), this.user, version, now);
}
return newSchema;
}),
tap(newSchema => {
this.replaceSchema(newSchema);
}),
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
public lockField(schema: SchemaDetailsDto, field: AnyFieldDto, now?: DateTime): Observable<any> {
public lockField<T extends FieldDto>(schema: SchemaDetailsDto, field: T, now?: DateTime): Observable<T> {
return this.schemasService.lockField(this.appName, schema.name, field.fieldId, pidof(field), schema.version).pipe(
tap(dto => {
this.replaceField(schema, setLocked(field, true), dto.version, now);
mapVersioned(() => setLocked(field, true)),
tap(({ payload, version }) => {
this.replaceField(schema, payload, version, now);
}),
notify(this.dialogs));
shareSubscribed(this.dialogs, { project: x => x.payload }));
}
public enableField(schema: SchemaDetailsDto, field: AnyFieldDto, now?: DateTime): Observable<any> {
public enableField<T extends FieldDto>(schema: SchemaDetailsDto, field: T, now?: DateTime): Observable<T> {
return this.schemasService.enableField(this.appName, schema.name, field.fieldId, pidof(field), schema.version).pipe(
tap(dto => {
this.replaceField(schema, setDisabled(field, false), dto.version, now);
mapVersioned(() => setDisabled(field, false)),
tap(({ payload, version }) => {
this.replaceField(schema, payload, version, now);
}),
notify(this.dialogs));
shareSubscribed(this.dialogs, { project: x => x.payload }));
}
public disableField(schema: SchemaDetailsDto, field: AnyFieldDto, now?: DateTime): Observable<any> {
public disableField<T extends FieldDto>(schema: SchemaDetailsDto, field: T, now?: DateTime): Observable<T> {
return this.schemasService.disableField(this.appName, schema.name, field.fieldId, pidof(field), schema.version).pipe(
tap(dto => {
this.replaceField(schema, setDisabled(field, true), dto.version, now);
mapVersioned(() => setDisabled(field, true)),
tap(({ payload, version }) => {
this.replaceField(schema, payload, version, now);
}),
notify(this.dialogs));
shareSubscribed(this.dialogs, { project: x => x.payload }));
}
public showField(schema: SchemaDetailsDto, field: AnyFieldDto, now?: DateTime): Observable<any> {
public showField<T extends FieldDto>(schema: SchemaDetailsDto, field: T, now?: DateTime): Observable<T> {
return this.schemasService.showField(this.appName, schema.name, field.fieldId, pidof(field), schema.version).pipe(
tap(dto => {
this.replaceField(schema, setHidden(field, false), dto.version, now);
mapVersioned(() => setHidden(field, false)),
tap(({ payload, version }) => {
this.replaceField(schema, payload, version, now);
}),
notify(this.dialogs));
shareSubscribed(this.dialogs, { project: x => x.payload }));
}
public hideField(schema: SchemaDetailsDto, field: AnyFieldDto, now?: DateTime): Observable<any> {
public hideField<T extends FieldDto>(schema: SchemaDetailsDto, field: T, now?: DateTime): Observable<T> {
return this.schemasService.hideField(this.appName, schema.name, field.fieldId, pidof(field), schema.version).pipe(
tap(dto => {
this.replaceField(schema, setHidden(field, true), dto.version, now);
mapVersioned(() => setHidden(field, true)),
tap(({ payload, version }) => {
this.replaceField(schema, payload, version, now);
}),
notify(this.dialogs));
shareSubscribed(this.dialogs, { project: x => x.payload }));
}
public updateField(schema: SchemaDetailsDto, field: AnyFieldDto, request: UpdateFieldDto, now?: DateTime): Observable<any> {
public updateField<T extends FieldDto>(schema: SchemaDetailsDto, field: T, request: UpdateFieldDto, now?: DateTime): Observable<T> {
return this.schemasService.putField(this.appName, schema.name, field.fieldId, request, pidof(field), schema.version).pipe(
tap(dto => {
this.replaceField(schema, update(field, request.properties), dto.version, now);
mapVersioned(() => update(field, request.properties)),
tap(({ payload, version }) => {
this.replaceField(schema, payload, version, now);
}),
notify(this.dialogs));
shareSubscribed(this.dialogs, { project: x => x.payload }));
}
public deleteField(schema: SchemaDetailsDto, field: AnyFieldDto, now?: DateTime): Observable<any> {
return this.schemasService.deleteField(this.appName, schema.name, field.fieldId, pidof(field), schema.version).pipe(
tap(dto => {
this.removeField(schema, field, dto.version, now);
mapVersioned(() => field),
tap(({ payload, version }) => {
this.removeField(schema, payload, version, now);
}),
notify(this.dialogs));
shareSubscribed(this.dialogs));
}
private replaceField(schema: SchemaDetailsDto, field: AnyFieldDto, version: Version, now?: DateTime) {
private replaceField<T extends FieldDto>(schema: SchemaDetailsDto, field: T, version: Version, now?: DateTime) {
if (Types.is(field, RootFieldDto)) {
this.replaceSchema(updateField(schema, field, this.user, version, now));
} else {
} else if (Types.is(field, NestedFieldDto)) {
const parent = schema.fields.find(x => x.fieldId === field.parentId);
if (parent) {
@ -344,7 +375,7 @@ export class SchemasState extends State<Snapshot> {
}
}
function buildCategories(categories: { [name: string]: boolean }, schemas: ImmutableArray<SchemaDto>) {
function buildCategories(categories: { [name: string]: boolean }, schemas: SchemasList) {
categories = { ...categories };
for (let category in categories) {

10700
src/Squidex/package-lock.json

File diff suppressed because it is too large

93
src/Squidex/package.json

@ -15,91 +15,92 @@
"build:clean": "rimraf wwwroot/build"
},
"dependencies": {
"@angular/animations": "7.2.3",
"@angular/common": "7.2.3",
"@angular/core": "7.2.3",
"@angular/forms": "7.2.3",
"@angular/http": "7.2.3",
"@angular/platform-browser": "7.2.3",
"@angular/platform-browser-dynamic": "7.2.3",
"@angular/platform-server": "7.2.3",
"@angular/router": "7.2.3",
"@angular/animations": "7.2.14",
"@angular/common": "7.2.14",
"@angular/core": "7.2.14",
"@angular/forms": "7.2.14",
"@angular/http": "7.2.14",
"@angular/platform-browser": "7.2.14",
"@angular/platform-browser-dynamic": "7.2.14",
"@angular/platform-server": "7.2.14",
"@angular/router": "7.2.14",
"angular2-chartjs": "0.5.1",
"babel-polyfill": "6.26.0",
"bootstrap": "4.2.1",
"bootstrap": "4.3.1",
"core-js": "2.6.3",
"graphiql": "0.12.0",
"graphql": "14.1.1",
"marked": "0.6.0",
"graphiql": "0.13.0",
"graphql": "14.2.1",
"marked": "0.6.2",
"moment": "2.24.0",
"mousetrap": "1.6.2",
"mousetrap": "1.6.3",
"ng2-dnd": "5.0.2",
"ngx-color-picker": "7.3.0",
"oidc-client": "1.6.1",
"ngx-color-picker": "7.5.0",
"oidc-client": "1.7.1",
"pikaday": "1.8.0",
"progressbar.js": "1.0.1",
"react": "16.7.0",
"react-dom": "16.7.0",
"rxjs": "6.3.3",
"react": "16.8.6",
"react-dom": "16.8.6",
"rxjs": "6.5.1",
"slugify": "1.3.4",
"sortablejs": "1.8.1",
"sortablejs": "1.9.0",
"tslib": "1.9.3",
"zone.js": "0.8.29"
"zone.js": "0.9.0"
},
"devDependencies": {
"@angular/compiler": "7.2.3",
"@angular/compiler-cli": "7.2.3",
"@ngtools/webpack": "7.3.0",
"@angular/compiler": "7.2.14",
"@angular/compiler-cli": "7.2.14",
"@ngtools/webpack": "7.3.8",
"@types/core-js": "2.5.0",
"@types/jasmine": "3.3.8",
"@types/marked": "0.6.0",
"@types/jasmine": "3.3.12",
"@types/marked": "0.6.5",
"@types/mousetrap": "1.6",
"@types/node": "10.12.21",
"@types/react": "16.8.1",
"@types/react-dom": "16.0.11",
"@types/node": "11.13.8",
"@types/react": "16.8.14",
"@types/react-dom": "16.8.4",
"@types/sortablejs": "1.7.2",
"angular-router-loader": "0.8.5",
"angular2-template-loader": "0.6.2",
"awesome-typescript-loader": "5.2.1",
"babel-core": "6.26.3",
"codelyzer": "4.5.0",
"codelyzer": "5.0.1",
"cpx": "1.5.0",
"css-loader": "2.1.0",
"css-loader": "2.1.1",
"file-loader": "3.0.1",
"html-loader": "0.5.5",
"html-webpack-plugin": "3.2.0",
"ignore-loader": "0.1.2",
"istanbul-instrumenter-loader": "3.0.1",
"jasmine-core": "3.3.0",
"karma": "4.0.0",
"jasmine-core": "3.4.0",
"karma": "4.1.0",
"karma-chrome-launcher": "2.2.0",
"karma-cli": "2.0.0",
"karma-coverage-istanbul-reporter": "2.0.4",
"karma-coverage-istanbul-reporter": "2.0.5",
"karma-htmlfile-reporter": "0.3.8",
"karma-jasmine": "2.0.1",
"karma-jasmine-html-reporter": "1.4.0",
"karma-jasmine-html-reporter": "1.4.2",
"karma-mocha-reporter": "2.2.5",
"karma-sourcemap-loader": "0.3.7",
"karma-webpack": "3.0.5",
"mini-css-extract-plugin": "0.5.0",
"node-sass": "4.11.0",
"mini-css-extract-plugin": "0.6.0",
"node-sass": "4.12.0",
"optimize-css-assets-webpack-plugin": "5.0.1",
"raw-loader": "1.0.0",
"raw-loader": "2.0.0",
"rimraf": "2.6.3",
"rxjs-tslint": "0.1.6",
"sass-lint": "1.12.1",
"rxjs-tslint": "0.1.7",
"sass-lint": "1.13.1",
"sass-loader": "7.1.0",
"style-loader": "0.23.1",
"ts-loader": "^5.4.4",
"tsconfig-paths-webpack-plugin": "3.2.0",
"tslint": "5.12.1",
"tslint-webpack-plugin": "2.0.2",
"tslint": "5.16.0",
"tslint-webpack-plugin": "2.0.4",
"typemoq": "2.1.0",
"typescript": "3.2.4",
"uglifyjs-webpack-plugin": "2.1.1",
"uglifyjs-webpack-plugin": "2.1.2",
"underscore": "1.9.1",
"webpack": "4.29.0",
"webpack-cli": "3.2.1",
"webpack-dev-server": "3.1.14",
"webpack": "4.30.0",
"webpack-cli": "3.3.1",
"webpack-dev-server": "3.3.1",
"webpack-merge": "4.2.1"
}
}

8
src/Squidex/tsconfig.json

@ -21,8 +21,12 @@
]
}
},
"awesomeTypescriptLoaderOptions": {
"useBabel": true,
"useCache": true,
"emitRequireType": false
},
"angularCompilerOptions": {
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true
"fullTemplateTypeCheck": true
}
}
Loading…
Cancel
Save