Browse Source

Started with cancelling events.

pull/336/head
Sebastian Stehle 7 years ago
parent
commit
451a5f24f8
  1. 13
      src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs
  2. 4
      src/Squidex.Domain.Apps.Entities/Rules/Repositories/IRuleEventRepository.cs
  3. 3
      src/Squidex.Domain.Apps.Entities/Rules/RuleJobResult.cs
  4. 28
      src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs
  5. 9
      src/Squidex/Areas/IdentityServer/Config/LazyClientStore.cs
  6. 15
      src/Squidex/Pipeline/Extensions.cs
  7. 2
      src/Squidex/Pipeline/Robots/RobotsTxtMiddleware.cs
  8. 14
      src/Squidex/app/features/rules/pages/events/rule-events-page.component.html
  9. 4
      src/Squidex/app/features/rules/pages/events/rule-events-page.component.ts
  10. 2
      src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html
  11. 2
      src/Squidex/app/features/schemas/pages/schema/field-wizard.component.html
  12. 4
      src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html
  13. 2
      src/Squidex/app/features/settings/pages/more/more-page.component.html
  14. 13
      src/Squidex/app/shared/services/rules.service.spec.ts
  15. 14
      src/Squidex/app/shared/services/rules.service.ts
  16. 11
      src/Squidex/app/shared/state/rule-events.state.spec.ts
  17. 15
      src/Squidex/app/shared/state/rule-events.state.ts

13
src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs

@ -66,11 +66,6 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
return ruleEvent;
}
public Task RemoveAsync(Guid appId)
{
return Collection.DeleteManyAsync(x => x.AppId == appId);
}
public async Task<int> CountByAppAsync(Guid appId)
{
return (int)await Collection.CountDocumentsAsync(x => x.AppId == appId);
@ -88,6 +83,14 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
return Collection.InsertOneIfNotExistsAsync(entity);
}
public Task CancelAsync(Guid id)
{
return Collection.UpdateOneAsync(x => x.Id == id,
Update
.Set(x => x.NextAttempt, null)
.Set(x => x.JobResult, RuleJobResult.Cancelled));
}
public Task MarkSentAsync(Guid jobId, string dump, RuleResult result, RuleJobResult jobResult, TimeSpan elapsed, Instant? nextAttempt)
{
return Collection.UpdateOneAsync(x => x.Id == jobId,

4
src/Squidex.Domain.Apps.Entities/Rules/Repositories/IRuleEventRepository.cs

@ -21,12 +21,12 @@ namespace Squidex.Domain.Apps.Entities.Rules.Repositories
Task EnqueueAsync(Guid id, Instant nextAttempt);
Task CancelAsync(Guid id);
Task MarkSentAsync(Guid jobId, string dump, RuleResult result, RuleJobResult jobResult, TimeSpan elapsed, Instant? nextCall);
Task QueryPendingAsync(Instant now, Func<IRuleEventEntity, Task> callback, CancellationToken ct = default(CancellationToken));
Task RemoveAsync(Guid appId);
Task<int> CountByAppAsync(Guid appId);
Task<IReadOnlyList<IRuleEventEntity>> QueryByAppAsync(Guid appId, int skip = 0, int take = 20);

3
src/Squidex.Domain.Apps.Entities/Rules/RuleJobResult.cs

@ -12,6 +12,7 @@ namespace Squidex.Domain.Apps.Entities.Rules
Pending,
Success,
Retry,
Failed
Failed,
Cancelled
}
}

28
src/Squidex/Areas/Api/Controllers/Rules/RulesController.cs

@ -250,7 +250,7 @@ namespace Squidex.Areas.Api.Controllers.Rules
}
/// <summary>
/// Enqueue the event to be send.
/// Enqueue the event to retry it immediate
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="id">The event to enqueue.</param>
@ -274,5 +274,31 @@ namespace Squidex.Areas.Api.Controllers.Rules
return NoContent();
}
/// <summary>
/// Cancels the event to not retry it again.
/// </summary>
/// <param name="app">The name of the app.</param>
/// <param name="id">The event to enqueue.</param>
/// <returns>
/// 200 => Rule deqeued.
/// 404 => App or rule event not found.
/// </returns>
[HttpDelete]
[Route("apps/{app}/rules/events/{id}/")]
[ApiCosts(0)]
public async Task<IActionResult> DeleteEvent(string app, Guid id)
{
var entity = await ruleEventsRepository.FindAsync(id);
if (entity == null)
{
return NotFound();
}
await ruleEventsRepository.CancelAsync(id);
return NoContent();
}
}
}

9
src/Squidex/Areas/IdentityServer/Config/LazyClientStore.cs

@ -16,6 +16,7 @@ using Squidex.Config;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Entities;
using Squidex.Infrastructure;
using Squidex.Pipeline;
namespace Squidex.Areas.IdentityServer.Config
{
@ -43,16 +44,16 @@ namespace Squidex.Areas.IdentityServer.Config
return client;
}
var token = clientId.Split(':', '~');
var (appName, appClientId) = clientId.GetClientParts();
if (token.Length != 2)
if (appName == null)
{
return null;
}
var app = await appProvider.GetAppAsync(token[0]);
var app = await appProvider.GetAppAsync(appName);
var appClient = app?.Clients.GetOrDefault(token[1]);
var appClient = app?.Clients.GetOrDefault(appClientId);
if (appClient == null)
{

15
src/Squidex/Pipeline/Extensions.cs

@ -22,16 +22,19 @@ namespace Squidex.Pipeline
{
var clientId = principal.FindFirst(OpenIdClaims.ClientId)?.Value;
var clientIdParts = clientId?.Split(':');
return clientId?.GetClientParts().ClientId;
}
public static (string App, string ClientId) GetClientParts(this string clientId)
{
var parts = clientId.Split(':', '~');
if (clientIdParts?.Length != 2)
if (parts.Length != 2)
{
return null;
return (null, null);
}
clientId = clientIdParts[1];
return clientId;
return (parts[0], parts[1]);
}
}
}

2
src/Squidex/Pipeline/Robots/RobotsTxtMiddleware.cs

@ -12,7 +12,7 @@ using Squidex.Infrastructure;
namespace Squidex.Pipeline.Robots
{
public class RobotsTxtMiddleware : IMiddleware
public sealed class RobotsTxtMiddleware : IMiddleware
{
private readonly RobotsTxtOptions robotsTxtOptions;

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

@ -60,23 +60,27 @@
</div>
<div class="row event-stats">
<div class="col-3">
<div class="col col-4">
<span class="badge badge-pill badge-{{event.result | sqxRuleEventBadgeClass}}">{{event.result}}</span>
</div>
<div class="col-3">
<div class="col col-4">
Attempts: {{event.numCalls}}
</div>
<div class="col-3">
<div class="col col-4">
Next: <ng-container *ngIf="event.nextAttempt">{{event.nextAttempt | sqxFromNow}}</ng-container>
</div>
<div class="col-3 text-right">
<div class="col col-auto text-right">
<button class="btn btn-success btn-sm" (click)="enqueue(event)">
Enqueue
</button>
<button class="btn btn-success btn-sm" (click)="enqueue(event)">
Enqueue
</button>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="col col-12">
<textarea class="event-dump form-control" readonly>{{event.lastDump}}</textarea>
</div>
</div>

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

@ -48,6 +48,10 @@ export class RuleEventsPageComponent implements OnInit {
this.ruleEventsState.enqueue(event).pipe(onErrorResumeNext()).subscribe();
}
public cancel(event: RuleEventDto) {
this.ruleEventsState.cancel(event).pipe(onErrorResumeNext()).subscribe();
}
public selectEvent(id: string) {
this.selectedEventId = this.selectedEventId !== id ? id : null;
}

2
src/Squidex/app/features/rules/pages/rules/rule-wizard.component.html

@ -22,7 +22,7 @@
<ng-container content>
<div class="row no-gutters" *ngIf="step === 1">
<div *ngFor="let triggerType of ruleTriggers | sqxKeys" class="col-12 col-md-6">
<div *ngFor="let triggerType of ruleTriggers | sqxKeys" class="col col-12 col-md-6">
<div class="rule-element" (click)="selectTriggerType(triggerType)">
<sqx-rule-element [type]="triggerType" [isSmall]="false" [element]="ruleTriggers[triggerType]"></sqx-rule-element>
</div>

2
src/Squidex/app/features/schemas/pages/schema/field-wizard.component.html

@ -16,7 +16,7 @@
<div class="form-group">
<div class="row">
<div class="col-4 type" *ngFor="let fieldType of fieldTypes">
<div class="col col-4 type" *ngFor="let fieldType of fieldTypes">
<label>
<input type="radio" class="radio-input" formControlName="type" value="{{fieldType.type}}" />

4
src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html

@ -29,7 +29,7 @@
<div class="form-group">
<div class="row no-gutters">
<div class="col-6 type">
<div class="col col-6 type">
<label>
<input type="radio" class="radio-input" formControlName="singleton" [value]="false" />
@ -47,7 +47,7 @@
</label>
</div>
<div class="col-6 type">
<div class="col col-6 type">
<label>
<input type="radio" class="radio-input" formControlName="singleton" [value]="true" />

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

@ -16,7 +16,7 @@
<div>Once you archive an app, there is no going back. Please be certain.</div>
</div>
<div class="col-auto">
<div class="col col-auto">
<button class="btn btn-danger"
(sqxConfirmClick)="archiveApp()"
confirmTitle="Archive App"

13
src/Squidex/app/shared/services/rules.service.spec.ts

@ -346,4 +346,17 @@ describe('RulesService', () => {
req.flush({});
}));
it('should make delete request to cancel rule event',
inject([RulesService, HttpTestingController], (rulesService: RulesService, httpMock: HttpTestingController) => {
rulesService.cancelEvent('my-app', '123').subscribe();
const req = httpMock.expectOne('http://service/p/api/apps/my-app/rules/events/123');
expect(req.request.method).toEqual('DELETE');
expect(req.request.headers.get('If-Match')).toBeNull();
req.flush({});
}));
});

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

@ -77,6 +77,10 @@ export class RuleEventDto extends Model {
) {
super();
}
public with(value: Partial<RuleEventDto>): RuleEventDto {
return this.clone(value);
}
}
export class CreateRuleDto {
@ -269,4 +273,14 @@ export class RulesService {
}),
pretifyError('Failed to enqueue rule event. Please reload.'));
}
public cancelEvent(appName: string, id: string): Observable<any> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/rules/events/${id}`);
return HTTP.deleteVersioned(this.http, url).pipe(
tap(() => {
this.analytics.trackEvent('Rule', 'EventDequeued', appName);
}),
pretifyError('Failed to cancel rule event. Please reload.'));
}
}

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

@ -91,4 +91,15 @@ describe('RuleEventsState', () => {
rulesService.verify(x => x.enqueueEvent(app, oldRuleEvents[0].id), Times.once());
});
it('should call service when cancelling event', () => {
rulesService.setup(x => x.cancelEvent(app, oldRuleEvents[0].id))
.returns(() => of({}));
ruleEventsState.cancel(oldRuleEvents[0]).subscribe();
expect().nothing();
rulesService.verify(x => x.cancelEvent(app, oldRuleEvents[0].id), Times.once());
});
});

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

@ -85,6 +85,18 @@ export class RuleEventsState extends State<Snapshot> {
notify(this.dialogs));
}
public cancel(event: RuleEventDto): Observable<any> {
return this.rulesService.cancelEvent(this.appsState.appName, event.id).pipe(
tap(() => {
return this.next(s => {
const ruleEvents = s.ruleEvents.replaceBy('id', setCancelled(event));
return { ...s, ruleEvents, isLoaded: true };
});
}),
notify(this.dialogs));
}
public goNext(): Observable<any> {
this.next(s => ({ ...s, ruleEventsPager: s.ruleEventsPager.goNext() }));
@ -101,3 +113,6 @@ export class RuleEventsState extends State<Snapshot> {
return this.appsState.appName;
}
}
const setCancelled = (event: RuleEventDto) =>
event.with({ nextAttempt: null, jobResult: 'Cancelled' });
Loading…
Cancel
Save