Browse Source

Minor fixes.

pull/282/head
Sebastian Stehle 8 years ago
parent
commit
977408320d
  1. 1
      src/Squidex/Areas/Api/Controllers/ApiController.cs
  2. 2
      src/Squidex/Areas/Api/Controllers/Users/UserManagementController.cs
  3. 2
      src/Squidex/Config/Orleans/SiloWrapper.cs
  4. 10
      src/Squidex/Pipeline/ApiModelValidationAttribute.cs
  5. 2
      src/Squidex/Program.cs
  6. 4
      src/Squidex/app/features/content/pages/contents/contents-page.component.ts
  7. 2
      src/Squidex/app/features/content/shared/references-editor.component.scss
  8. 10
      src/Squidex/app/features/rules/pages/events/rule-events-page.component.html
  9. 54
      src/Squidex/app/features/rules/pages/events/rule-events-page.component.ts
  10. 14
      src/Squidex/app/features/rules/pages/rules/rule-wizard.component.ts
  11. 2
      src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.html
  12. 2
      src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts
  13. 2
      src/Squidex/app/features/settings/pages/languages/language.component.html
  14. 100
      src/Squidex/app/features/settings/pages/plans/plans-page.component.html
  15. 1
      src/Squidex/app/shared/internal.ts
  16. 2
      src/Squidex/app/shared/module.ts
  17. 11
      src/Squidex/app/shared/state/contents.state.ts
  18. 88
      src/Squidex/app/shared/state/rule-events.state.spec.ts
  19. 95
      src/Squidex/app/shared/state/rule-events.state.ts

1
src/Squidex/Areas/Api/Controllers/ApiController.cs

@ -16,6 +16,7 @@ using Squidex.Pipeline;
namespace Squidex.Areas.Api.Controllers namespace Squidex.Areas.Api.Controllers
{ {
[Area("Api")] [Area("Api")]
[ApiModelValidation(false)]
public abstract class ApiController : Controller public abstract class ApiController : Controller
{ {
protected ICommandBus CommandBus { get; } protected ICommandBus CommandBus { get; }

2
src/Squidex/Areas/Api/Controllers/Users/UserManagementController.cs

@ -24,7 +24,7 @@ namespace Squidex.Areas.Api.Controllers.Users
{ {
[ApiAuthorize] [ApiAuthorize]
[ApiExceptionFilter] [ApiExceptionFilter]
[ApiModelValidation] [ApiModelValidation(true)]
[MustBeAdministrator] [MustBeAdministrator]
[SwaggerIgnore] [SwaggerIgnore]
public sealed class UserManagementController : ApiController public sealed class UserManagementController : ApiController

2
src/Squidex/Config/Orleans/SiloWrapper.cs

@ -75,7 +75,7 @@ namespace Squidex.Config.Orleans
{ {
builder.AddConfiguration(hostingContext.Configuration.GetSection("logging")); builder.AddConfiguration(hostingContext.Configuration.GetSection("logging"));
builder.AddSemanticLog(); builder.AddSemanticLog();
builder.AddFilter("Orleans.Runtime.SiloControl", LogLevel.Warning); builder.AddFilter((category, level) => !category.StartsWith("Orleans.", StringComparison.CurrentCultureIgnoreCase) || level >= LogLevel.Warning);
}) })
.ConfigureApplicationParts(builder => .ConfigureApplicationParts(builder =>
{ {

10
src/Squidex/Pipeline/ApiModelValidationAttribute.cs

@ -7,12 +7,20 @@
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Newtonsoft.Json;
using Squidex.Infrastructure; using Squidex.Infrastructure;
namespace Squidex.Pipeline namespace Squidex.Pipeline
{ {
public sealed class ApiModelValidationAttribute : ActionFilterAttribute public sealed class ApiModelValidationAttribute : ActionFilterAttribute
{ {
private readonly bool allErrors;
public ApiModelValidationAttribute(bool allErrors)
{
this.allErrors = allErrors;
}
public override void OnActionExecuting(ActionExecutingContext context) public override void OnActionExecuting(ActionExecutingContext context)
{ {
if (!context.ModelState.IsValid) if (!context.ModelState.IsValid)
@ -23,7 +31,7 @@ namespace Squidex.Pipeline
{ {
foreach (var e in m.Value.Errors) foreach (var e in m.Value.Errors)
{ {
if (!string.IsNullOrWhiteSpace(e.ErrorMessage)) if (!string.IsNullOrWhiteSpace(e.ErrorMessage) && (allErrors || e.Exception is JsonException))
{ {
errors.Add(new ValidationError(e.ErrorMessage, m.Key)); errors.Add(new ValidationError(e.ErrorMessage, m.Key));
} }

2
src/Squidex/Program.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System;
using System.IO; using System.IO;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
@ -28,6 +29,7 @@ namespace Squidex
{ {
builder.AddConfiguration(hostingContext.Configuration.GetSection("logging")); builder.AddConfiguration(hostingContext.Configuration.GetSection("logging"));
builder.AddSemanticLog(); builder.AddSemanticLog();
builder.AddFilter((category, level) => !category.StartsWith("Orleans.", StringComparison.CurrentCultureIgnoreCase) || level >= LogLevel.Warning);
}) })
.ConfigureAppConfiguration((hostContext, builder) => .ConfigureAppConfiguration((hostContext, builder) =>
{ {

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

@ -73,7 +73,7 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
this.schema = schema!; this.schema = schema!;
this.contentsState.load().onErrorResumeNext().subscribe(); this.contentsState.init().onErrorResumeNext().subscribe();
}); });
this.contentsSubscription = this.contentsSubscription =
@ -88,8 +88,6 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
this.languages = languages.map(x => x.language); this.languages = languages.map(x => x.language);
this.language = this.languages.at(0); this.language = this.languages.at(0);
}); });
this.contentsState.load().onErrorResumeNext().subscribe();
} }
public reload() { public reload() {

2
src/Squidex/app/features/content/shared/references-editor.component.scss

@ -18,7 +18,7 @@
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
padding: 1rem; padding: 1rem;
min-height: 2rem; min-height: 6rem;
max-height: 20rem; max-height: 20rem;
} }

10
src/Squidex/app/features/rules/pages/events/rule-events-page.component.html

@ -6,11 +6,11 @@
</ng-container> </ng-container>
<ng-container menu> <ng-container menu>
<button class="btn btn-link btn-secondary" (click)="load(true)" title="Refresh Events (CTRL + SHIFT + R)"> <button class="btn btn-link btn-secondary" (click)="reload()" title="Refresh Events (CTRL + SHIFT + R)">
<i class="icon-reset"></i> Refresh <i class="icon-reset"></i> Refresh
</button> </button>
<sqx-shortcut keys="ctrl+shift+r" (trigger)="load(true)"></sqx-shortcut> <sqx-shortcut keys="ctrl+shift+r" (trigger)="reload()"></sqx-shortcut>
</ng-container> </ng-container>
<ng-container content> <ng-container content>
@ -34,7 +34,7 @@
</thead> </thead>
<tbody> <tbody>
<ng-template ngFor let-event [ngForOf]="eventsItems"> <ng-template ngFor let-event [ngForOf]="ruleEventsState.ruleEvents | async" [ngForOf]="trackByRuleEvent">
<tr [class.expanded]="selectedEventId === event.id"> <tr [class.expanded]="selectedEventId === event.id">
<td class="cell-label"> <td class="cell-label">
<span class="badge badge-pill badge-{{event.jobResult | sqxRuleEventBadgeClass}}">{{event.jobResult}}</span> <span class="badge badge-pill badge-{{event.jobResult | sqxRuleEventBadgeClass}}">{{event.jobResult}}</span>
@ -71,7 +71,7 @@
Next: <ng-container *ngIf="event.nextAttempt">{{event.nextAttempt | sqxFromNow}}</ng-container> Next: <ng-container *ngIf="event.nextAttempt">{{event.nextAttempt | sqxFromNow}}</ng-container>
</div> </div>
<div class="col-3 text-right"> <div class="col-3 text-right">
<button class="btn btn-success btn-sm" (click)="enqueueEvent(event)"> <button class="btn btn-success btn-sm" (click)="enqueue(event)">
Enqueue Enqueue
</button> </button>
</div> </div>
@ -88,7 +88,7 @@
</tbody> </tbody>
</table> </table>
<sqx-pager [pager]="eventsPager"></sqx-pager> <sqx-pager [pager]="ruleEventsState.ruleEventsPager | async"></sqx-pager>
</ng-container> </ng-container>
</sqx-panel> </sqx-panel>

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

@ -9,11 +9,8 @@ import { Component, OnInit } from '@angular/core';
import { import {
AppsState, AppsState,
DialogService,
ImmutableArray,
Pager,
RuleEventDto, RuleEventDto,
RulesService RuleEventsState
} from '@app/shared'; } from '@app/shared';
@Component({ @Component({
@ -22,59 +19,40 @@ import {
templateUrl: './rule-events-page.component.html' templateUrl: './rule-events-page.component.html'
}) })
export class RuleEventsPageComponent implements OnInit { export class RuleEventsPageComponent implements OnInit {
public eventsItems = ImmutableArray.empty<RuleEventDto>();
public eventsPager = new Pager(0);
public selectedEventId: string | null = null; public selectedEventId: string | null = null;
constructor( constructor(
public readonly appsState: AppsState, public readonly appsState: AppsState,
private readonly dialogs: DialogService, public readonly ruleEventsState: RuleEventsState
private readonly rulesService: RulesService
) { ) {
} }
public ngOnInit() { public ngOnInit() {
this.load(); this.ruleEventsState.load().onErrorResumeNext().subscribe();
} }
public load(notifyLoad = false) { public reload() {
this.rulesService.getEvents(this.appsState.appName, this.eventsPager.pageSize, this.eventsPager.skip) this.ruleEventsState.load(true).onErrorResumeNext().subscribe();
.subscribe(dtos => {
this.eventsItems = ImmutableArray.of(dtos.items);
this.eventsPager = this.eventsPager.setCount(dtos.total);
if (notifyLoad) {
this.dialogs.notifyInfo('Events reloaded.');
}
}, error => {
this.dialogs.notifyError(error);
});
} }
public enqueueEvent(event: RuleEventDto) { public goNext() {
this.rulesService.enqueueEvent(this.appsState.appName, event.id) this.ruleEventsState.goNext().onErrorResumeNext().subscribe();
.subscribe(() => {
this.dialogs.notifyInfo('Events enqueued. Will be resend in a few seconds.');
}, error => {
this.dialogs.notifyError(error);
});
} }
public selectEvent(id: string) { public goPrev() {
this.selectedEventId = this.selectedEventId !== id ? id : null; this.ruleEventsState.goPrev().onErrorResumeNext().subscribe();
} }
public goNext() { public enqueue(event: RuleEventDto) {
this.eventsPager = this.eventsPager.goNext(); this.ruleEventsState.enqueue(event).onErrorResumeNext().subscribe();
this.load();
} }
public goPrev() { public selectEvent(id: string) {
this.eventsPager = this.eventsPager.goPrev(); this.selectedEventId = this.selectedEventId !== id ? id : null;
}
this.load(); public trackByRuleEvent(index: number, ruleEvent: RuleEventDto) {
return ruleEvent.id;
} }
} }

14
src/Squidex/app/features/rules/pages/rules/rule-wizard.component.ts

@ -121,6 +121,12 @@ export class RuleWizardComponent implements OnInit {
this.rulesState.create(requestDto) this.rulesState.create(requestDto)
.subscribe(dto => { .subscribe(dto => {
this.complete(); this.complete();
this.actionForm.submitCompleted();
this.triggerForm.submitCompleted();
}, error => {
this.actionForm.submitFailed(error);
this.triggerForm.submitFailed(error);
}); });
} }
@ -128,6 +134,10 @@ export class RuleWizardComponent implements OnInit {
this.rulesState.updateTrigger(this.rule, this.trigger) this.rulesState.updateTrigger(this.rule, this.trigger)
.subscribe(dto => { .subscribe(dto => {
this.complete(); this.complete();
this.triggerForm.submitCompleted();
}, error => {
this.triggerForm.submitFailed(error);
}); });
} }
@ -135,6 +145,10 @@ export class RuleWizardComponent implements OnInit {
this.rulesState.updateAction(this.rule, this.action) this.rulesState.updateAction(this.rule, this.action)
.subscribe(dto => { .subscribe(dto => {
this.complete(); this.complete();
this.actionForm.submitCompleted();
}, error => {
this.actionForm.submitFailed(error);
}); });
} }
} }

2
src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.html

@ -1,6 +1,6 @@
<h3 class="wizard-title">Trigger rule when an events for a schemas happens</h3> <h3 class="wizard-title">Trigger rule when an events for a schemas happens</h3>
<ng-container *ngIf="!!triggerForm.controls.handleAll.value"> <ng-container *ngIf="!triggerForm.controls.handleAll.value">
<table class="table table-middle table-sm table-fixed table-borderless"> <table class="table table-middle table-sm table-fixed table-borderless">
<colgroup> <colgroup>
<col style="width: 100%" /> <col style="width: 100%" />

2
src/Squidex/app/features/rules/pages/rules/triggers/content-changed-trigger.component.ts

@ -52,7 +52,7 @@ export class ContentChangedTriggerComponent implements OnInit {
public ngOnInit() { public ngOnInit() {
this.triggerForm.setControl('schemas', this.triggerForm.setControl('schemas',
new FormControl(this.trigger.schemas || {})); new FormControl(this.trigger.schemas || []));
this.triggerForm.setControl('handleAll', this.triggerForm.setControl('handleAll',
new FormControl(Types.isBoolean(this.trigger.handleAll) ? this.trigger.handleAll : false)); new FormControl(Types.isBoolean(this.trigger.handleAll) ? this.trigger.handleAll : false));

2
src/Squidex/app/features/settings/pages/languages/language.component.html

@ -2,7 +2,7 @@
<div class="table-items-row-summary"> <div class="table-items-row-summary">
<div class="row"> <div class="row">
<div class="col col-2"> <div class="col col-2">
<span class="language-code" [class.language-optional]="language.isOptional" [class.language-master]="language.isMaster"></span> <span class="language-code" [class.language-optional]="language.isOptional" [class.language-master]="language.isMaster">{{language.iso2Code}}</span>
</div> </div>
<div class="col col-6"> <div class="col col-6">
<span class="language-name table-cell" [class.language-optional]="language.isOptional" [class.language-master]="language.isMaster">{{language.englishName}}</span> <span class="language-name table-cell" [class.language-optional]="language.isOptional" [class.language-master]="language.isMaster">{{language.englishName}}</span>

100
src/Squidex/app/features/settings/pages/plans/plans-page.component.html

@ -14,65 +14,67 @@
</ng-container> </ng-container>
<ng-container content> <ng-container content>
<ng-container *ngIf="plansState.plans | async; let plans"> <ng-container *ngIf="plansState.isLoaded | async">
<div class="panel-alert panel-alert-danger" *ngIf="(plansState.isOwner | async) === false"> <ng-container *ngIf="plansState.plans | async; let plans">
You have not created the subscription. Therefore you cannot change the planInfo.plan. <div class="panel-alert panel-alert-danger" *ngIf="(plansState.isOwner | async) === false">
</div> You have not created the subscription. Therefore you cannot change the planInfo.plan.
</div>
<div class="text-muted text-center empty" *ngIf="plans.length === 0"> <div class="text-muted text-center empty" *ngIf="plans.length === 0">
No plan configured, this app has unlimited usage. No plan configured, this app has unlimited usage.
</div> </div>
<div class="clearfix"> <div class="clearfix">
<div class="card plan float-left" *ngFor="let planInfo of plans; trackBy: trackByPlan"> <div class="card plan float-left" *ngFor="let planInfo of plans; trackBy: trackByPlan">
<div class="card-header text-center"> <div class="card-header text-center">
<h4 class="card-title">{{planInfo.plan.name}}</h4> <h4 class="card-title">{{planInfo.plan.name}}</h4>
<h5 class="plan-price">{{planInfo.plan.costs}}</h5> <h5 class="plan-price">{{planInfo.plan.costs}}</h5>
<small class="text-muted">Per Month</small> <small class="text-muted">Per Month</small>
</div>
<div class="card-body">
<div class="plan-fact text-center">
<div>
<strong>{{planInfo.plan.maxApiCalls | sqxKNumber}}</strong> API Calls
</div>
<div>
{{planInfo.plan.maxAssetSize | sqxFileSize}} Storage
</div>
<div>
{{planInfo.plan.maxContributors}} Contributors
</div>
</div> </div>
<div class="card-body">
<button *ngIf="planInfo.isSelected" class="btn btn-block btn-link btn-success plan-selected"> <div class="plan-fact text-center">
&#10003; Selected <div>
</button> <strong>{{planInfo.plan.maxApiCalls | sqxKNumber}}</strong> API Calls
</div>
<div>
{{planInfo.plan.maxAssetSize | sqxFileSize}} Storage
</div>
<div>
{{planInfo.plan.maxContributors}} Contributors
</div>
</div>
<button *ngIf="!planInfo.isSelected" class="btn btn-block btn-success" [disabled]="plansState.isDisabled | async" (click)="change(planInfo.plan.id)"> <button *ngIf="planInfo.isSelected" class="btn btn-block btn-link btn-success plan-selected">
Change &#10003; Selected
</button> </button>
</div>
<div class="card-footer" *ngIf="planInfo.plan.yearlyId"> <button *ngIf="!planInfo.isSelected" class="btn btn-block btn-success" [disabled]="plansState.isDisabled | async" (click)="change(planInfo.plan.id)">
<div class="text-center"> Change
<h5 class="plan-price">{{planInfo.plan.yearlyCosts}}</h5> </button>
<small class="text-muted">Per Year</small>
</div> </div>
<div class="card-footer" *ngIf="planInfo.plan.yearlyId">
<div class="text-center">
<h5 class="plan-price">{{planInfo.plan.yearlyCosts}}</h5>
<button *ngIf="planInfo.isYearlySelected" class="btn btn-block btn-link btn-success plan-selected"> <small class="text-muted">Per Year</small>
&#10003; Selected </div>
</button>
<button *ngIf="planInfo.isYearlySelected" class="btn btn-block btn-link btn-success plan-selected">
<button *ngIf="!planInfo.isYearlySelected" class="btn btn-block btn-success" [disabled]="plansState.isDisabled | async" (click)="change(planInfo.plan.yearlyId)"> &#10003; Selected
Change </button>
</button>
<button *ngIf="!planInfo.isYearlySelected" class="btn btn-block btn-success" [disabled]="plansState.isDisabled | async" (click)="change(planInfo.plan.yearlyId)">
Change
</button>
</div>
</div> </div>
</div> </div>
</div>
<div *ngIf="plansState.hasPortal | async" class="billing-portal-link"> <div *ngIf="plansState.hasPortal | async" class="billing-portal-link">
Go to <a target="_blank" href="{{portalUrl}}">Billing Portal</a> for payment history and subscription overview. Go to <a target="_blank" href="{{portalUrl}}">Billing Portal</a> for payment history and subscription overview.
</div> </div>
</ng-container>
</ng-container> </ng-container>
</ng-container> </ng-container>
</sqx-panel> </sqx-panel>

1
src/Squidex/app/shared/internal.ts

@ -48,6 +48,7 @@ export * from './state/contributors.state';
export * from './state/languages.state'; export * from './state/languages.state';
export * from './state/patterns.state'; export * from './state/patterns.state';
export * from './state/plans.state'; export * from './state/plans.state';
export * from './state/rule-events.state';
export * from './state/rules.state'; export * from './state/rules.state';
export * from './state/schemas.state'; export * from './state/schemas.state';

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

@ -57,6 +57,7 @@ import {
PlansService, PlansService,
PlansState, PlansState,
RichEditorComponent, RichEditorComponent,
RuleEventsState,
RulesService, RulesService,
RulesState, RulesState,
SchemaMustExistGuard, SchemaMustExistGuard,
@ -164,6 +165,7 @@ export class SqxSharedModule {
PatternsState, PatternsState,
PlansService, PlansService,
PlansState, PlansState,
RuleEventsState,
RulesService, RulesService,
RulesState, RulesState,
SchemaMustExistGuard, SchemaMustExistGuard,

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

@ -29,10 +29,7 @@ import { fieldInvariant, SchemaDetailsDto, SchemaDto } from './../services/schem
import { AppsState } from './apps.state'; import { AppsState } from './apps.state';
import { SchemasState } from './schemas.state'; import { SchemasState } from './schemas.state';
import { import { ContentDto, ContentsService } from './../services/contents.service';
ContentDto,
ContentsService
} from './../services/contents.service';
export class EditContentForm extends Form<FormGroup> { export class EditContentForm extends Form<FormGroup> {
constructor( constructor(
@ -317,6 +314,12 @@ export abstract class ContentsStateBase extends State<Snapshot> {
return this.load(); return this.load();
} }
public init(): Observable<any> {
this.next(s => ({ ...s, contentsPager: new Pager(0), contentsQuery: '', isArchive: false, isLoaded: false }));
return this.load();
}
public search(query: string): Observable<any> { public search(query: string): Observable<any> {
this.next(s => ({ ...s, contentsPager: new Pager(0), contentsQuery: query })); this.next(s => ({ ...s, contentsPager: new Pager(0), contentsQuery: query }));

88
src/Squidex/app/shared/state/rule-events.state.spec.ts

@ -0,0 +1,88 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Observable } from 'rxjs';
import { IMock, It, Mock, Times } from 'typemoq';
import {
AppsState,
DateTime,
DialogService
} from '@app/shared';
import { RuleEventsState } from './rule-events.state';
import {
RuleEventDto,
RuleEventsDto,
RulesService
} from './../services/rules.service';
describe('RuleEventsState', () => {
const app = 'my-app';
const oldRuleEvents = [
new RuleEventDto('id1', DateTime.now(), null, 'event1', 'description', 'dump1', 'result1', 'result1', 1),
new RuleEventDto('id2', DateTime.now(), null, 'event2', 'description', 'dump2', 'result2', 'result2', 2)
];
let appsState: IMock<AppsState>;
let dialogs: IMock<DialogService>;
let rulesService: IMock<RulesService>;
let ruleEventsState: RuleEventsState;
beforeEach(() => {
dialogs = Mock.ofType<DialogService>();
appsState = Mock.ofType<AppsState>();
appsState.setup(x => x.appName)
.returns(() => app);
rulesService = Mock.ofType<RulesService>();
rulesService.setup(x => x.getEvents(app, 10, 0))
.returns(() => Observable.of(new RuleEventsDto(200, oldRuleEvents)));
ruleEventsState = new RuleEventsState(appsState.object, dialogs.object, rulesService.object);
ruleEventsState.load().subscribe();
});
it('should load ruleEvents', () => {
expect(ruleEventsState.snapshot.ruleEvents.values).toEqual(oldRuleEvents);
expect(ruleEventsState.snapshot.ruleEventsPager.numberOfItems).toEqual(200);
expect(ruleEventsState.snapshot.isLoaded).toBeTruthy();
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.never());
});
it('should show notification on load when flag is true', () => {
ruleEventsState.load(true).subscribe();
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.once());
});
it('should load next page and prev page when paging', () => {
rulesService.setup(x => x.getEvents(app, 10, 10))
.returns(() => Observable.of(new RuleEventsDto(200, [])));
ruleEventsState.goNext().subscribe();
ruleEventsState.goPrev().subscribe();
rulesService.verify(x => x.getEvents(app, 10, 10), Times.once());
rulesService.verify(x => x.getEvents(app, 10, 0), Times.exactly(2));
});
it('should call service when enqueuing event', () => {
rulesService.setup(x => x.enqueueEvent(app, oldRuleEvents[0].id))
.returns(() => Observable.of({}));
ruleEventsState.enqueue(oldRuleEvents[0]).subscribe();
rulesService.verify(x => x.enqueueEvent(app, oldRuleEvents[0].id), Times.once());
});
});

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

@ -0,0 +1,95 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import '@app/framework/utils/rxjs-extensions';
import {
DialogService,
ImmutableArray,
Pager,
State
} from '@app/framework';
import { AppsState } from './apps.state';
import { RuleEventDto, RulesService } from './../services/rules.service';
interface Snapshot {
ruleEvents: ImmutableArray<RuleEventDto>;
ruleEventsPager: Pager;
isLoaded?: boolean;
}
@Injectable()
export class RuleEventsState extends State<Snapshot> {
public ruleEvents =
this.changes.map(x => x.ruleEvents)
.distinctUntilChanged();
public ruleEventsPager =
this.changes.map(x => x.ruleEventsPager)
.distinctUntilChanged();
public isLoaded =
this.changes.map(x => !!x.isLoaded)
.distinctUntilChanged();
constructor(
private readonly appsState: AppsState,
private readonly dialogs: DialogService,
private readonly rulesService: RulesService
) {
super({ ruleEvents: ImmutableArray.of(), ruleEventsPager: new Pager(0) });
}
public load(notifyLoad = false): Observable<any> {
return this.rulesService.getEvents(this.appName,
this.snapshot.ruleEventsPager.pageSize,
this.snapshot.ruleEventsPager.skip)
.do(dtos => {
if (notifyLoad) {
this.dialogs.notifyInfo('RuleEvents reloaded.');
}
return this.next(s => {
const ruleEvents = ImmutableArray.of(dtos.items);
const ruleEventsPager = s.ruleEventsPager.setCount(dtos.total);
return { ...s, ruleEvents, ruleEventsPager, isLoaded: true };
});
})
.notify(this.dialogs);
}
public enqueue(event: RuleEventDto): Observable<any> {
return this.rulesService.enqueueEvent(this.appsState.appName, event.id)
.do(() => {
this.dialogs.notifyInfo('Events enqueued. Will be resend in a few seconds.');
})
.notify(this.dialogs);
}
public goNext(): Observable<any> {
this.next(s => ({ ...s, ruleEventsPager: s.ruleEventsPager.goNext() }));
return this.load();
}
public goPrev(): Observable<any> {
this.next(s => ({ ...s, ruleEventsPager: s.ruleEventsPager.goPrev() }));
return this.load();
}
private get appName() {
return this.appsState.appName;
}
}
Loading…
Cancel
Save