Browse Source

HATEOS for event consumers.

pull/363/head
Sebastian 7 years ago
parent
commit
368cfc7b82
  1. 29
      src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs
  2. 12
      src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerManagerGrain.cs
  3. 6
      src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerGrain.cs
  4. 10
      src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerManagerGrain.cs
  5. 1
      src/Squidex.Web/Resource.cs
  6. 2
      src/Squidex.Web/UrlHelperExtensions.cs
  7. 1
      src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs
  8. 31
      src/Squidex/Areas/Api/Controllers/EventConsumers/EventConsumersController.cs
  9. 37
      src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumerDto.cs
  10. 39
      src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumersDto.cs
  11. 6
      src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html
  12. 122
      src/Squidex/app/features/administration/services/event-consumers.service.spec.ts
  13. 78
      src/Squidex/app/features/administration/services/event-consumers.service.ts
  14. 3
      src/Squidex/app/features/administration/services/users.service.spec.ts
  15. 60
      src/Squidex/app/features/administration/state/event-consumers.state.spec.ts
  16. 19
      src/Squidex/app/features/administration/state/event-consumers.state.ts
  17. 10
      src/Squidex/app/features/administration/state/users.state.spec.ts

29
src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs

@ -58,7 +58,12 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
public Task<Immutable<EventConsumerInfo>> GetStateAsync() public Task<Immutable<EventConsumerInfo>> GetStateAsync()
{ {
return Task.FromResult(State.ToInfo(eventConsumer.Name).AsImmutable()); return Task.FromResult(CreateInfo());
}
private Immutable<EventConsumerInfo> CreateInfo()
{
return State.ToInfo(eventConsumer.Name).AsImmutable();
} }
public Task OnEventAsync(Immutable<IEventSubscription> subscription, Immutable<StoredEvent> storedEvent) public Task OnEventAsync(Immutable<IEventSubscription> subscription, Immutable<StoredEvent> storedEvent)
@ -109,39 +114,43 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
return TaskHelper.Done; return TaskHelper.Done;
} }
public Task StartAsync() public async Task<Immutable<EventConsumerInfo>> StartAsync()
{ {
if (!State.IsStopped) if (!State.IsStopped)
{ {
return TaskHelper.Done; return CreateInfo();
} }
return DoAndUpdateStateAsync(() => await DoAndUpdateStateAsync(() =>
{ {
Subscribe(State.Position); Subscribe(State.Position);
State = State.Started(); State = State.Started();
}); });
return CreateInfo();
} }
public Task StopAsync() public async Task<Immutable<EventConsumerInfo>> StopAsync()
{ {
if (State.IsStopped) if (State.IsStopped)
{ {
return TaskHelper.Done; return CreateInfo();
} }
return DoAndUpdateStateAsync(() => await DoAndUpdateStateAsync(() =>
{ {
Unsubscribe(); Unsubscribe();
State = State.Stopped(); State = State.Stopped();
}); });
return CreateInfo();
} }
public Task ResetAsync() public async Task<Immutable<EventConsumerInfo>> ResetAsync()
{ {
return DoAndUpdateStateAsync(async () => await DoAndUpdateStateAsync(async () =>
{ {
Unsubscribe(); Unsubscribe();
@ -151,6 +160,8 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
State = State.Reset(); State = State.Reset();
}); });
return CreateInfo();
} }
private Task DoAndUpdateStateAsync(Action action, [CallerMemberName] string caller = null) private Task DoAndUpdateStateAsync(Action action, [CallerMemberName] string caller = null)

12
src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerManagerGrain.cs

@ -74,33 +74,31 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
{ {
return Task.WhenAll( return Task.WhenAll(
eventConsumers eventConsumers
.Select(c => GrainFactory.GetGrain<IEventConsumerGrain>(c.Name)) .Select(c => StartAsync(c.Name)));
.Select(c => c.StartAsync()));
} }
public Task StopAllAsync() public Task StopAllAsync()
{ {
return Task.WhenAll( return Task.WhenAll(
eventConsumers eventConsumers
.Select(c => GrainFactory.GetGrain<IEventConsumerGrain>(c.Name)) .Select(c => StopAsync(c.Name)));
.Select(c => c.StopAsync()));
} }
public Task ResetAsync(string consumerName) public Task<Immutable<EventConsumerInfo>> ResetAsync(string consumerName)
{ {
var eventConsumer = GrainFactory.GetGrain<IEventConsumerGrain>(consumerName); var eventConsumer = GrainFactory.GetGrain<IEventConsumerGrain>(consumerName);
return eventConsumer.ResetAsync(); return eventConsumer.ResetAsync();
} }
public Task StartAsync(string consumerName) public Task<Immutable<EventConsumerInfo>> StartAsync(string consumerName)
{ {
var eventConsumer = GrainFactory.GetGrain<IEventConsumerGrain>(consumerName); var eventConsumer = GrainFactory.GetGrain<IEventConsumerGrain>(consumerName);
return eventConsumer.StartAsync(); return eventConsumer.StartAsync();
} }
public Task StopAsync(string consumerName) public Task<Immutable<EventConsumerInfo>> StopAsync(string consumerName)
{ {
var eventConsumer = GrainFactory.GetGrain<IEventConsumerGrain>(consumerName); var eventConsumer = GrainFactory.GetGrain<IEventConsumerGrain>(consumerName);

6
src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerGrain.cs

@ -16,11 +16,11 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
{ {
Task<Immutable<EventConsumerInfo>> GetStateAsync(); Task<Immutable<EventConsumerInfo>> GetStateAsync();
Task StopAsync(); Task<Immutable<EventConsumerInfo>> StopAsync();
Task StartAsync(); Task<Immutable<EventConsumerInfo>> StartAsync();
Task ResetAsync(); Task<Immutable<EventConsumerInfo>> ResetAsync();
Task OnEventAsync(Immutable<IEventSubscription> subscription, Immutable<StoredEvent> storedEvent); Task OnEventAsync(Immutable<IEventSubscription> subscription, Immutable<StoredEvent> storedEvent);

10
src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerManagerGrain.cs

@ -16,15 +16,15 @@ namespace Squidex.Infrastructure.EventSourcing.Grains
{ {
Task ActivateAsync(string streamName); Task ActivateAsync(string streamName);
Task StopAllAsync(); Task StartAllAsync();
Task StopAsync(string consumerName); Task StopAllAsync();
Task StartAllAsync(); Task<Immutable<EventConsumerInfo>> StopAsync(string consumerName);
Task StartAsync(string consumerName); Task<Immutable<EventConsumerInfo>> StartAsync(string consumerName);
Task ResetAsync(string consumerName); Task<Immutable<EventConsumerInfo>> ResetAsync(string consumerName);
Task<Immutable<List<EventConsumerInfo>>> GetConsumersAsync(); Task<Immutable<List<EventConsumerInfo>>> GetConsumersAsync();
} }

1
src/Squidex.Web/Resource.cs

@ -8,7 +8,6 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Net.Http;
namespace Squidex.Web namespace Squidex.Web
{ {

2
src/Squidex.Web/UrlHelperExtensions.cs

@ -7,8 +7,6 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System; using System;
using System.Linq.Expressions;
using System.Reflection;
namespace Squidex.Web namespace Squidex.Web
{ {

1
src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs

@ -21,7 +21,6 @@ using Squidex.Domain.Apps.Entities.Contents.Commands;
using Squidex.Domain.Apps.Entities.Contents.GraphQL; using Squidex.Domain.Apps.Entities.Contents.GraphQL;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Shared; using Squidex.Shared;
using Squidex.Shared.Identity;
using Squidex.Web; using Squidex.Web;
namespace Squidex.Areas.Api.Controllers.Contents namespace Squidex.Areas.Api.Controllers.Contents

31
src/Squidex/Areas/Api/Controllers/EventConsumers/EventConsumersController.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Orleans; using Orleans;
@ -30,44 +29,54 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers
[HttpGet] [HttpGet]
[Route("event-consumers/")] [Route("event-consumers/")]
[ProducesResponseType(typeof(EventConsumersDto), 200)]
[ApiPermission(Permissions.AdminEventsRead)] [ApiPermission(Permissions.AdminEventsRead)]
public async Task<IActionResult> GetEventConsumers() public async Task<IActionResult> GetEventConsumers()
{ {
var entities = await GetGrain().GetConsumersAsync(); var entities = await GetGrain().GetConsumersAsync();
var response = entities.Value.OrderBy(x => x.Name).Select(EventConsumerDto.FromEventConsumerInfo).ToArray(); var response = EventConsumersDto.FromResults(entities.Value, this);
return Ok(response); return Ok(response);
} }
[HttpPut] [HttpPut]
[Route("event-consumers/{name}/start/")] [Route("event-consumers/{name}/start/")]
[ProducesResponseType(typeof(EventConsumerDto), 200)]
[ApiPermission(Permissions.AdminEventsManage)] [ApiPermission(Permissions.AdminEventsManage)]
public async Task<IActionResult> Start(string name) public async Task<IActionResult> StartEventConsumer(string name)
{ {
await GetGrain().StartAsync(name); var entity = await GetGrain().StartAsync(name);
return NoContent(); var response = EventConsumerDto.FromEventConsumerInfo(entity.Value, this);
return Ok(response);
} }
[HttpPut] [HttpPut]
[Route("event-consumers/{name}/stop/")] [Route("event-consumers/{name}/stop/")]
[ProducesResponseType(typeof(EventConsumerDto), 200)]
[ApiPermission(Permissions.AdminEventsManage)] [ApiPermission(Permissions.AdminEventsManage)]
public async Task<IActionResult> Stop(string name) public async Task<IActionResult> StopEventConsumer(string name)
{ {
await GetGrain().StopAsync(name); var entity = await GetGrain().StopAsync(name);
var response = EventConsumerDto.FromEventConsumerInfo(entity.Value, this);
return NoContent(); return Ok(response);
} }
[HttpPut] [HttpPut]
[Route("event-consumers/{name}/reset/")] [Route("event-consumers/{name}/reset/")]
[ProducesResponseType(typeof(EventConsumerDto), 200)]
[ApiPermission(Permissions.AdminEventsManage)] [ApiPermission(Permissions.AdminEventsManage)]
public async Task<IActionResult> Reset(string name) public async Task<IActionResult> ResetEventConsumer(string name)
{ {
await GetGrain().ResetAsync(name); var entity = await GetGrain().ResetAsync(name);
var response = EventConsumerDto.FromEventConsumerInfo(entity.Value, this);
return NoContent(); return Ok(response);
} }
private IEventConsumerManagerGrain GetGrain() private IEventConsumerManagerGrain GetGrain()

37
src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumerDto.cs

@ -7,11 +7,16 @@
using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
using Squidex.Infrastructure.Security;
using Squidex.Shared;
using Squidex.Web;
namespace Squidex.Areas.Api.Controllers.EventConsumers.Models namespace Squidex.Areas.Api.Controllers.EventConsumers.Models
{ {
public sealed class EventConsumerDto public sealed class EventConsumerDto : Resource
{ {
private static readonly Permission EventsManagePermission = new Permission(Permissions.AdminEventsManage);
public bool IsStopped { get; set; } public bool IsStopped { get; set; }
public bool IsResetting { get; set; } public bool IsResetting { get; set; }
@ -22,9 +27,35 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers.Models
public string Position { get; set; } public string Position { get; set; }
public static EventConsumerDto FromEventConsumerInfo(EventConsumerInfo eventConsumerInfo) public static EventConsumerDto FromEventConsumerInfo(EventConsumerInfo eventConsumerInfo, ApiController controller)
{
var result = SimpleMapper.Map(eventConsumerInfo, new EventConsumerDto());
return CreateLinks(result, controller);
}
private static EventConsumerDto CreateLinks(EventConsumerDto result, ApiController controller)
{
if (controller.HasPermission(EventsManagePermission))
{
var values = new { name = result.Name };
if (!result.IsResetting)
{ {
return SimpleMapper.Map(eventConsumerInfo, new EventConsumerDto()); result.AddPutLink("reset", controller.Url<EventConsumersController>(x => nameof(x.ResetEventConsumer), values));
}
if (result.IsStopped)
{
result.AddPutLink("start", controller.Url<EventConsumersController>(x => nameof(x.StartEventConsumer), values));
}
else
{
result.AddPutLink("stop", controller.Url<EventConsumersController>(x => nameof(x.StopEventConsumer), values));
}
}
return result;
} }
} }
} }

39
src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumersDto.cs

@ -0,0 +1,39 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Linq;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Web;
namespace Squidex.Areas.Api.Controllers.EventConsumers.Models
{
public sealed class EventConsumersDto : Resource
{
/// <summary>
/// The event consumers.
/// </summary>
public EventConsumerDto[] Items { get; set; }
public static EventConsumersDto FromResults(IEnumerable<EventConsumerInfo> items, ApiController controller)
{
var result = new EventConsumersDto
{
Items = items.Select(x => EventConsumerDto.FromEventConsumerInfo(x, controller)).ToArray()
};
return CreateLinks(result, controller);
}
private static EventConsumersDto CreateLinks(EventConsumersDto result, ApiController controller)
{
result.AddSelfLink(controller.Url<EventConsumersController>(c => nameof(c.GetEventConsumers)));
return result;
}
}
}

6
src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html

@ -42,13 +42,13 @@
<span>{{eventConsumer.position}}</span> <span>{{eventConsumer.position}}</span>
</td> </td>
<td class="cell-actions-lg"> <td class="cell-actions-lg">
<button type="button" class="btn btn-text" (click)="reset(eventConsumer)" *ngIf="!eventConsumer.isResetting" title="Reset Event Consumer"> <button type="button" class="btn btn-text" (click)="reset(eventConsumer)" *ngIf="eventConsumer | sqxHasLink:'reset'" title="Reset Event Consumer">
<i class="icon icon-reset"></i> <i class="icon icon-reset"></i>
</button> </button>
<button type="button" class="btn btn-text" (click)="start(eventConsumer)" *ngIf="eventConsumer.isStopped" title="Start Event Consumer"> <button type="button" class="btn btn-text" (click)="start(eventConsumer)" *ngIf="eventConsumer | sqxHasLink:'start'" title="Start Event Consumer">
<i class="icon icon-play"></i> <i class="icon icon-play"></i>
</button> </button>
<button type="button" class="btn btn-text" (click)="stop(eventConsumer)" *ngIf="!eventConsumer.isStopped" title="Stop Event Consumer"> <button type="button" class="btn btn-text" (click)="stop(eventConsumer)" *ngIf="eventConsumer | sqxHasLink:'stop'" title="Stop Event Consumer">
<i class="icon icon-pause"></i> <i class="icon icon-pause"></i>
</button> </button>
</td> </td>

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

@ -8,9 +8,13 @@
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { inject, TestBed } from '@angular/core/testing'; import { inject, TestBed } from '@angular/core/testing';
import { ApiUrlConfig } from '@app/framework'; import { ApiUrlConfig, Resource } from '@app/framework';
import { EventConsumerDto, EventConsumersService } from './event-consumers.service'; import {
EventConsumerDto,
EventConsumersDto,
EventConsumersService
} from './event-consumers.service';
describe('EventConsumersService', () => { describe('EventConsumersService', () => {
beforeEach(() => { beforeEach(() => {
@ -32,7 +36,7 @@ describe('EventConsumersService', () => {
it('should make get request to get event consumers', it('should make get request to get event consumers',
inject([EventConsumersService, HttpTestingController], (eventConsumersService: EventConsumersService, httpMock: HttpTestingController) => { inject([EventConsumersService, HttpTestingController], (eventConsumersService: EventConsumersService, httpMock: HttpTestingController) => {
let eventConsumers: EventConsumerDto[]; let eventConsumers: EventConsumersDto;
eventConsumersService.getEventConsumers().subscribe(result => { eventConsumersService.getEventConsumers().subscribe(result => {
eventConsumers = result; eventConsumers = result;
@ -43,66 +47,120 @@ describe('EventConsumersService', () => {
expect(req.request.method).toEqual('GET'); expect(req.request.method).toEqual('GET');
expect(req.request.headers.get('If-Match')).toBeNull(); expect(req.request.headers.get('If-Match')).toBeNull();
req.flush([ req.flush({
{ items: [
name: 'event-consumer1', eventConsumerResponse(12),
position: '13', eventConsumerResponse(13)
isStopped: true, ]
isResetting: true, });
error: 'an error 1'
},
{
name: 'event-consumer2',
position: '29',
isStopped: true,
isResetting: true,
error: 'an error 2'
}
]);
expect(eventConsumers!).toEqual( expect(eventConsumers!).toEqual(
[ new EventConsumersDto([
new EventConsumerDto('event-consumer1', true, true, 'an error 1', '13'), createEventConsumer(12),
new EventConsumerDto('event-consumer2', true, true, 'an error 2', '29') createEventConsumer(13)
]); ]));
})); }));
it('should make put request to start event consumer', it('should make put request to start event consumer',
inject([EventConsumersService, HttpTestingController], (eventConsumersService: EventConsumersService, httpMock: HttpTestingController) => { inject([EventConsumersService, HttpTestingController], (eventConsumersService: EventConsumersService, httpMock: HttpTestingController) => {
eventConsumersService.putStart('event-consumer1').subscribe(); const resource: Resource = {
_links: {
start: { method: 'PUT', href: 'api/event-consumers/event-consumer123/start' }
}
};
const req = httpMock.expectOne('http://service/p/api/event-consumers/event-consumer1/start'); let eventConsumer: EventConsumerDto;
eventConsumersService.putStart(resource).subscribe(response => {
eventConsumer = response;
});
const req = httpMock.expectOne('http://service/p/api/event-consumers/event-consumer123/start');
expect(req.request.method).toEqual('PUT'); expect(req.request.method).toEqual('PUT');
expect(req.request.headers.get('If-Match')).toBeNull(); expect(req.request.headers.get('If-Match')).toBeNull();
req.flush({}); req.flush(eventConsumerResponse(123));
expect(eventConsumer!).toEqual(createEventConsumer(123));
})); }));
it('should make put request to stop event consumer', it('should make put request to stop event consumer',
inject([EventConsumersService, HttpTestingController], (eventConsumersService: EventConsumersService, httpMock: HttpTestingController) => { inject([EventConsumersService, HttpTestingController], (eventConsumersService: EventConsumersService, httpMock: HttpTestingController) => {
eventConsumersService.putStop('event-consumer1').subscribe(); const resource: Resource = {
_links: {
stop: { method: 'PUT', href: 'api/event-consumers/event-consumer123/stop' }
}
};
let eventConsumer: EventConsumerDto;
eventConsumersService.putStop(resource).subscribe(response => {
eventConsumer = response;
});
const req = httpMock.expectOne('http://service/p/api/event-consumers/event-consumer1/stop'); const req = httpMock.expectOne('http://service/p/api/event-consumers/event-consumer123/stop');
expect(req.request.method).toEqual('PUT'); expect(req.request.method).toEqual('PUT');
expect(req.request.headers.get('If-Match')).toBeNull(); expect(req.request.headers.get('If-Match')).toBeNull();
req.flush({}); req.flush(eventConsumerResponse(12));
expect(eventConsumer!).toEqual(createEventConsumer(12));
})); }));
it('should make put request to reset event consumer', it('should make put request to reset event consumer',
inject([EventConsumersService, HttpTestingController], (eventConsumersService: EventConsumersService, httpMock: HttpTestingController) => { inject([EventConsumersService, HttpTestingController], (eventConsumersService: EventConsumersService, httpMock: HttpTestingController) => {
eventConsumersService.putReset('event-consumer1').subscribe(); const resource: Resource = {
_links: {
reset: { method: 'PUT', href: 'api/event-consumers/event-consumer123/reset' }
}
};
let eventConsumer: EventConsumerDto;
const req = httpMock.expectOne('http://service/p/api/event-consumers/event-consumer1/reset'); eventConsumersService.putReset(resource).subscribe(response => {
eventConsumer = response;
});
const req = httpMock.expectOne('http://service/p/api/event-consumers/event-consumer123/reset');
expect(req.request.method).toEqual('PUT'); expect(req.request.method).toEqual('PUT');
expect(req.request.headers.get('If-Match')).toBeNull(); expect(req.request.headers.get('If-Match')).toBeNull();
req.flush({}); req.flush(eventConsumerResponse(12));
expect(eventConsumer!).toEqual(createEventConsumer(12));
})); }));
function eventConsumerResponse(id: number) {
return {
name: `event-consumer${id}`,
position: `position-${id}`,
isStopped: true,
isResetting: true,
error: `failure-${id}`,
_links: {
reset: { method: 'PUT', href: `/event-consumers/${id}/reset` }
}
};
}
}); });
export function createEventConsumer(id: number, suffix = '') {
const result = new EventConsumerDto(
`event-consumer${id}`,
true,
true,
`failure-${id}${suffix}`,
`position-${id}${suffix}`);
result._links['reset'] = {
method: 'PUT', href: `/event-consumers/${id}/reset`
};
return result;
}

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

@ -12,11 +12,24 @@ import { map } from 'rxjs/operators';
import { import {
ApiUrlConfig, ApiUrlConfig,
Model, pretifyError,
pretifyError Resource,
ResourceLinks,
withLinks
} from '@app/shared'; } from '@app/shared';
export class EventConsumerDto extends Model<EventConsumerDto> { export class EventConsumersDto {
public readonly _links: ResourceLinks = {};
constructor(
public readonly items: EventConsumerDto[]
) {
}
}
export class EventConsumerDto {
public readonly _links: ResourceLinks = {};
constructor( constructor(
public readonly name: string, public readonly name: string,
public readonly isStopped?: boolean, public readonly isStopped?: boolean,
@ -24,7 +37,6 @@ export class EventConsumerDto extends Model<EventConsumerDto> {
public readonly error?: string, public readonly error?: string,
public readonly position?: string public readonly position?: string
) { ) {
super();
} }
} }
@ -36,42 +48,62 @@ export class EventConsumersService {
) { ) {
} }
public getEventConsumers(): Observable<EventConsumerDto[]> { public getEventConsumers(): Observable<EventConsumersDto> {
const url = this.apiUrl.buildUrl('/api/event-consumers'); const url = this.apiUrl.buildUrl('/api/event-consumers');
return this.http.get<any[]>(url).pipe( return this.http.get<{ items: any[] } & Resource>(url).pipe(
map(body => { map(body => {
const eventConsumers = body.map(item => const eventConsumers = body.items.map(item => parseEventConsumer(item));
new EventConsumerDto(
item.name,
item.isStopped,
item.isResetting,
item.error,
item.position));
return eventConsumers; return withLinks(new EventConsumersDto(eventConsumers), body);
}), }),
pretifyError('Failed to load event consumers. Please reload.')); pretifyError('Failed to load event consumers. Please reload.'));
} }
public putStart(name: string): Observable<any> { public putStart(eventConsumer: Resource): Observable<EventConsumerDto> {
const url = this.apiUrl.buildUrl(`api/event-consumers/${name}/start`); const link = eventConsumer._links['start'];
return this.http.put(url, {}).pipe( const url = this.apiUrl.buildUrl(link.href);
return this.http.request(link.method, url).pipe(
map(body => {
return parseEventConsumer(body);
}),
pretifyError('Failed to start event consumer. Please reload.')); pretifyError('Failed to start event consumer. Please reload.'));
} }
public putStop(name: string): Observable<any> { public putStop(eventConsumer: Resource): Observable<EventConsumerDto> {
const url = this.apiUrl.buildUrl(`api/event-consumers/${name}/stop`); const link = eventConsumer._links['stop'];
const url = this.apiUrl.buildUrl(link.href);
return this.http.put(url, {}).pipe( return this.http.request(link.method, url).pipe(
map(body => {
return parseEventConsumer(body);
}),
pretifyError('Failed to stop event consumer. Please reload.')); pretifyError('Failed to stop event consumer. Please reload.'));
} }
public putReset(name: string): Observable<any> { public putReset(eventConsumer: Resource): Observable<EventConsumerDto> {
const url = this.apiUrl.buildUrl(`api/event-consumers/${name}/reset`); const link = eventConsumer._links['reset'];
const url = this.apiUrl.buildUrl(link.href);
return this.http.put(url, {}).pipe( return this.http.request(link.method, url).pipe(
map(body => {
return parseEventConsumer(body);
}),
pretifyError('Failed to reset event consumer. Please reload.')); pretifyError('Failed to reset event consumer. Please reload.'));
} }
} }
function parseEventConsumer(response: any): EventConsumerDto {
return withLinks(
new EventConsumerDto(
response.name,
response.isStopped,
response.isResetting,
response.error,
response.position),
response);
}

3
src/Squidex/app/features/administration/services/users.service.spec.ts

@ -227,7 +227,8 @@ describe('UsersService', () => {
}); });
export function createUser(id: number, suffix = '') { export function createUser(id: number, suffix = '') {
const result = new UserDto(`${id}`, const result = new UserDto(
`${id}`,
`user${id}${suffix}@domain.com`, `user${id}${suffix}@domain.com`,
`user${id}${suffix}`, `user${id}${suffix}`,
[ [

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

@ -11,14 +11,14 @@ import { IMock, It, Mock, Times } from 'typemoq';
import { DialogService } from '@app/framework'; import { DialogService } from '@app/framework';
import { EventConsumerDto, EventConsumersService } from '@app/features/administration/internal'; import { EventConsumersDto, EventConsumersService } from '@app/features/administration/internal';
import { EventConsumersState } from './event-consumers.state'; import { EventConsumersState } from './event-consumers.state';
import { createEventConsumer } from './../services/event-consumers.service.spec';
describe('EventConsumersState', () => { describe('EventConsumersState', () => {
const oldConsumers = [ const eventConsumer1 = createEventConsumer(1);
new EventConsumerDto('name1', false, false, 'error', '1'), const eventConsumer2 = createEventConsumer(2);
new EventConsumerDto('name2', true, true, 'error', '2')
];
let dialogs: IMock<DialogService>; let dialogs: IMock<DialogService>;
let eventConsumersService: IMock<EventConsumersService>; let eventConsumersService: IMock<EventConsumersService>;
@ -38,11 +38,11 @@ describe('EventConsumersState', () => {
describe('Loading', () => { describe('Loading', () => {
it('should load event consumers', () => { it('should load event consumers', () => {
eventConsumersService.setup(x => x.getEventConsumers()) eventConsumersService.setup(x => x.getEventConsumers())
.returns(() => of(oldConsumers)).verifiable(); .returns(() => of(new EventConsumersDto([eventConsumer1, eventConsumer2]))).verifiable();
eventConsumersState.load().subscribe(); eventConsumersState.load().subscribe();
expect(eventConsumersState.snapshot.eventConsumers.values).toEqual(oldConsumers); expect(eventConsumersState.snapshot.eventConsumers.values).toEqual([eventConsumer1, eventConsumer2]);
expect(eventConsumersState.snapshot.isLoaded).toBeTruthy(); expect(eventConsumersState.snapshot.isLoaded).toBeTruthy();
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.never()); dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.never());
@ -50,7 +50,7 @@ describe('EventConsumersState', () => {
it('should show notification on load when reload is true', () => { it('should show notification on load when reload is true', () => {
eventConsumersService.setup(x => x.getEventConsumers()) eventConsumersService.setup(x => x.getEventConsumers())
.returns(() => of(oldConsumers)).verifiable(); .returns(() => of(new EventConsumersDto([eventConsumer1, eventConsumer2]))).verifiable();
eventConsumersState.load(true).subscribe(); eventConsumersState.load(true).subscribe();
@ -74,42 +74,48 @@ describe('EventConsumersState', () => {
describe('Updates', () => { describe('Updates', () => {
beforeEach(() => { beforeEach(() => {
eventConsumersService.setup(x => x.getEventConsumers()) eventConsumersService.setup(x => x.getEventConsumers())
.returns(() => of(oldConsumers)).verifiable(); .returns(() => of(new EventConsumersDto([eventConsumer1, eventConsumer2]))).verifiable();
eventConsumersState.load().subscribe(); eventConsumersState.load().subscribe();
}); });
it('should unmark as stopped when started', () => { it('should update evnet consumer when started', () => {
eventConsumersService.setup(x => x.putStart(oldConsumers[1].name)) const updated = createEventConsumer(2, '_new');
.returns(() => of({})).verifiable();
eventConsumersService.setup(x => x.putStart(eventConsumer2))
.returns(() => of(updated)).verifiable();
eventConsumersState.start(oldConsumers[1]).subscribe(); eventConsumersState.start(eventConsumer2).subscribe();
const es_1 = eventConsumersState.snapshot.eventConsumers.at(1); const newConsumer2 = eventConsumersState.snapshot.eventConsumers.at(1);
expect(es_1.isStopped).toBeFalsy(); expect(newConsumer2).toEqual(updated);
}); });
it('should mark as stopped when stopped', () => { it('should update event consumer when stopped', () => {
eventConsumersService.setup(x => x.putStop(oldConsumers[0].name)) const updated = createEventConsumer(2, '_new');
.returns(() => of({})).verifiable();
eventConsumersState.stop(oldConsumers[0]).subscribe(); eventConsumersService.setup(x => x.putStop(eventConsumer2))
.returns(() => of(updated)).verifiable();
const es_1 = eventConsumersState.snapshot.eventConsumers.at(0); eventConsumersState.stop(eventConsumer2).subscribe();
expect(es_1.isStopped).toBeTruthy(); const newConsumer2 = eventConsumersState.snapshot.eventConsumers.at(1);
expect(newConsumer2).toEqual(updated);
}); });
it('should mark as resetting when reset', () => { it('should update event consumer when reset', () => {
eventConsumersService.setup(x => x.putReset(oldConsumers[0].name)) const updated = createEventConsumer(2, '_new');
.returns(() => of({})).verifiable();
eventConsumersService.setup(x => x.putReset(eventConsumer2))
.returns(() => of(updated)).verifiable();
eventConsumersState.reset(oldConsumers[0]).subscribe(); eventConsumersState.reset(eventConsumer2).subscribe();
const es_1 = eventConsumersState.snapshot.eventConsumers.at(0); const newConsumer2 = eventConsumersState.snapshot.eventConsumers.at(1);
expect(es_1.isResetting).toBeTruthy(); expect(newConsumer2).toEqual(updated);
}); });
}); });
}); });

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

@ -51,12 +51,12 @@ export class EventConsumersState extends State<Snapshot> {
} }
return this.eventConsumersService.getEventConsumers().pipe( return this.eventConsumersService.getEventConsumers().pipe(
tap(payload => { tap(({ items }) => {
if (isReload && !silent) { if (isReload && !silent) {
this.dialogs.notifyInfo('Event Consumers reloaded.'); this.dialogs.notifyInfo('Event Consumers reloaded.');
} }
const eventConsumers = ImmutableArray.of(payload); const eventConsumers = ImmutableArray.of(items);
this.next(s => { this.next(s => {
return { ...s, eventConsumers, isLoaded: true }; return { ...s, eventConsumers, isLoaded: true };
@ -66,8 +66,7 @@ export class EventConsumersState extends State<Snapshot> {
} }
public start(eventConsumer: EventConsumerDto): Observable<any> { public start(eventConsumer: EventConsumerDto): Observable<any> {
return this.eventConsumersService.putStart(eventConsumer.name).pipe( return this.eventConsumersService.putStart(eventConsumer).pipe(
map(() => setStopped(eventConsumer, false)),
tap(updated => { tap(updated => {
this.replaceEventConsumer(updated); this.replaceEventConsumer(updated);
}), }),
@ -75,8 +74,7 @@ export class EventConsumersState extends State<Snapshot> {
} }
public stop(eventConsumer: EventConsumerDto): Observable<EventConsumerDto> { public stop(eventConsumer: EventConsumerDto): Observable<EventConsumerDto> {
return this.eventConsumersService.putStop(eventConsumer.name).pipe( return this.eventConsumersService.putStop(eventConsumer).pipe(
map(() => setStopped(eventConsumer, true)),
tap(updated => { tap(updated => {
this.replaceEventConsumer(updated); this.replaceEventConsumer(updated);
}), }),
@ -84,8 +82,7 @@ export class EventConsumersState extends State<Snapshot> {
} }
public reset(eventConsumer: EventConsumerDto): Observable<EventConsumerDto> { public reset(eventConsumer: EventConsumerDto): Observable<EventConsumerDto> {
return this.eventConsumersService.putReset(eventConsumer.name).pipe( return this.eventConsumersService.putReset(eventConsumer).pipe(
map(() => reset(eventConsumer)),
tap(updated => { tap(updated => {
this.replaceEventConsumer(updated); this.replaceEventConsumer(updated);
}), }),
@ -100,9 +97,3 @@ export class EventConsumersState extends State<Snapshot> {
}); });
} }
} }
const setStopped = (eventConsumer: EventConsumerDto, isStopped: boolean) =>
eventConsumer.with({ isStopped });
const reset = (eventConsumer: EventConsumerDto) =>
eventConsumer.with({ isResetting: true });

10
src/Squidex/app/features/administration/state/users.state.spec.ts

@ -168,7 +168,7 @@ describe('UsersState', () => {
expect(usersState.snapshot.selectedUser).toBeNull(); expect(usersState.snapshot.selectedUser).toBeNull();
}); });
it('should mark as locked when locked', () => { it('should update user selected user when locked', () => {
const updated = createUser(2, '_new'); const updated = createUser(2, '_new');
usersService.setup(x => x.lockUser(user2)) usersService.setup(x => x.lockUser(user2))
@ -177,12 +177,12 @@ describe('UsersState', () => {
usersState.select(user2.id).subscribe(); usersState.select(user2.id).subscribe();
usersState.lock(user2).subscribe(); usersState.lock(user2).subscribe();
const userUser2 = usersState.snapshot.users.at(1); const newUser2 = usersState.snapshot.users.at(1);
expect(userUser2).toBe(usersState.snapshot.selectedUser!); expect(newUser2).toBe(usersState.snapshot.selectedUser!);
}); });
it('should unmark as locked when unlocked', () => { it('should update user and selected user when unlocked', () => {
const updated = createUser(2, '_new'); const updated = createUser(2, '_new');
usersService.setup(x => x.unlockUser(user2)) usersService.setup(x => x.unlockUser(user2))
@ -197,7 +197,7 @@ describe('UsersState', () => {
expect(newUser2).toBe(usersState.snapshot.selectedUser!); expect(newUser2).toBe(usersState.snapshot.selectedUser!);
}); });
it('should update user properties when updated', () => { it('should update user and selected user when updated', () => {
const request = { email: 'new@mail.com', displayName: 'New', permissions: ['Permission1'] }; const request = { email: 'new@mail.com', displayName: 'New', permissions: ['Permission1'] };
const updated = createUser(2, '_new'); const updated = createUser(2, '_new');

Loading…
Cancel
Save