From 368cfc7b828fe4464c3992abb76b7241fc70b1b7 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sat, 8 Jun 2019 14:20:34 +0200 Subject: [PATCH] HATEOS for event consumers. --- .../Grains/EventConsumerGrain.cs | 29 ++-- .../Grains/EventConsumerManagerGrain.cs | 12 +- .../Grains/IEventConsumerGrain.cs | 6 +- .../Grains/IEventConsumerManagerGrain.cs | 10 +- src/Squidex.Web/Resource.cs | 1 - src/Squidex.Web/UrlHelperExtensions.cs | 2 - .../Contents/ContentsController.cs | 1 - .../EventConsumersController.cs | 31 +++-- .../EventConsumers/Models/EventConsumerDto.cs | 37 +++++- .../Models/EventConsumersDto.cs | 39 ++++++ .../event-consumers-page.component.html | 6 +- .../services/event-consumers.service.spec.ts | 124 +++++++++++++----- .../services/event-consumers.service.ts | 82 ++++++++---- .../services/users.service.spec.ts | 3 +- .../state/event-consumers.state.spec.ts | 60 +++++---- .../state/event-consumers.state.ts | 21 +-- .../administration/state/users.state.spec.ts | 10 +- 17 files changed, 323 insertions(+), 151 deletions(-) create mode 100644 src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumersDto.cs diff --git a/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs b/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs index f37a9200f..bc5ada68d 100644 --- a/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs +++ b/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerGrain.cs @@ -58,7 +58,12 @@ namespace Squidex.Infrastructure.EventSourcing.Grains public Task> GetStateAsync() { - return Task.FromResult(State.ToInfo(eventConsumer.Name).AsImmutable()); + return Task.FromResult(CreateInfo()); + } + + private Immutable CreateInfo() + { + return State.ToInfo(eventConsumer.Name).AsImmutable(); } public Task OnEventAsync(Immutable subscription, Immutable storedEvent) @@ -109,39 +114,43 @@ namespace Squidex.Infrastructure.EventSourcing.Grains return TaskHelper.Done; } - public Task StartAsync() + public async Task> StartAsync() { if (!State.IsStopped) { - return TaskHelper.Done; + return CreateInfo(); } - return DoAndUpdateStateAsync(() => + await DoAndUpdateStateAsync(() => { Subscribe(State.Position); State = State.Started(); }); + + return CreateInfo(); } - public Task StopAsync() + public async Task> StopAsync() { if (State.IsStopped) { - return TaskHelper.Done; + return CreateInfo(); } - return DoAndUpdateStateAsync(() => + await DoAndUpdateStateAsync(() => { Unsubscribe(); State = State.Stopped(); }); + + return CreateInfo(); } - public Task ResetAsync() + public async Task> ResetAsync() { - return DoAndUpdateStateAsync(async () => + await DoAndUpdateStateAsync(async () => { Unsubscribe(); @@ -151,6 +160,8 @@ namespace Squidex.Infrastructure.EventSourcing.Grains State = State.Reset(); }); + + return CreateInfo(); } private Task DoAndUpdateStateAsync(Action action, [CallerMemberName] string caller = null) diff --git a/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerManagerGrain.cs b/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerManagerGrain.cs index ca9097142..4952088c0 100644 --- a/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerManagerGrain.cs +++ b/src/Squidex.Infrastructure/EventSourcing/Grains/EventConsumerManagerGrain.cs @@ -74,33 +74,31 @@ namespace Squidex.Infrastructure.EventSourcing.Grains { return Task.WhenAll( eventConsumers - .Select(c => GrainFactory.GetGrain(c.Name)) - .Select(c => c.StartAsync())); + .Select(c => StartAsync(c.Name))); } public Task StopAllAsync() { return Task.WhenAll( eventConsumers - .Select(c => GrainFactory.GetGrain(c.Name)) - .Select(c => c.StopAsync())); + .Select(c => StopAsync(c.Name))); } - public Task ResetAsync(string consumerName) + public Task> ResetAsync(string consumerName) { var eventConsumer = GrainFactory.GetGrain(consumerName); return eventConsumer.ResetAsync(); } - public Task StartAsync(string consumerName) + public Task> StartAsync(string consumerName) { var eventConsumer = GrainFactory.GetGrain(consumerName); return eventConsumer.StartAsync(); } - public Task StopAsync(string consumerName) + public Task> StopAsync(string consumerName) { var eventConsumer = GrainFactory.GetGrain(consumerName); diff --git a/src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerGrain.cs b/src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerGrain.cs index 58b7bf2fb..fb7d82811 100644 --- a/src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerGrain.cs +++ b/src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerGrain.cs @@ -16,11 +16,11 @@ namespace Squidex.Infrastructure.EventSourcing.Grains { Task> GetStateAsync(); - Task StopAsync(); + Task> StopAsync(); - Task StartAsync(); + Task> StartAsync(); - Task ResetAsync(); + Task> ResetAsync(); Task OnEventAsync(Immutable subscription, Immutable storedEvent); diff --git a/src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerManagerGrain.cs b/src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerManagerGrain.cs index c0b53d403..397db21f4 100644 --- a/src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerManagerGrain.cs +++ b/src/Squidex.Infrastructure/EventSourcing/Grains/IEventConsumerManagerGrain.cs @@ -16,15 +16,15 @@ namespace Squidex.Infrastructure.EventSourcing.Grains { Task ActivateAsync(string streamName); - Task StopAllAsync(); + Task StartAllAsync(); - Task StopAsync(string consumerName); + Task StopAllAsync(); - Task StartAllAsync(); + Task> StopAsync(string consumerName); - Task StartAsync(string consumerName); + Task> StartAsync(string consumerName); - Task ResetAsync(string consumerName); + Task> ResetAsync(string consumerName); Task>> GetConsumersAsync(); } diff --git a/src/Squidex.Web/Resource.cs b/src/Squidex.Web/Resource.cs index a0e68197c..59c4d10f9 100644 --- a/src/Squidex.Web/Resource.cs +++ b/src/Squidex.Web/Resource.cs @@ -8,7 +8,6 @@ using Newtonsoft.Json; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Net.Http; namespace Squidex.Web { diff --git a/src/Squidex.Web/UrlHelperExtensions.cs b/src/Squidex.Web/UrlHelperExtensions.cs index 486d48a76..27f00a1d9 100644 --- a/src/Squidex.Web/UrlHelperExtensions.cs +++ b/src/Squidex.Web/UrlHelperExtensions.cs @@ -7,8 +7,6 @@ using Microsoft.AspNetCore.Mvc; using System; -using System.Linq.Expressions; -using System.Reflection; namespace Squidex.Web { diff --git a/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs b/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs index bd374a986..61c74d203 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs +++ b/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.Infrastructure.Commands; using Squidex.Shared; -using Squidex.Shared.Identity; using Squidex.Web; namespace Squidex.Areas.Api.Controllers.Contents diff --git a/src/Squidex/Areas/Api/Controllers/EventConsumers/EventConsumersController.cs b/src/Squidex/Areas/Api/Controllers/EventConsumers/EventConsumersController.cs index 1369eb9c7..9e17d5f09 100644 --- a/src/Squidex/Areas/Api/Controllers/EventConsumers/EventConsumersController.cs +++ b/src/Squidex/Areas/Api/Controllers/EventConsumers/EventConsumersController.cs @@ -5,7 +5,6 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Orleans; @@ -30,44 +29,54 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers [HttpGet] [Route("event-consumers/")] + [ProducesResponseType(typeof(EventConsumersDto), 200)] [ApiPermission(Permissions.AdminEventsRead)] public async Task GetEventConsumers() { 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); } [HttpPut] [Route("event-consumers/{name}/start/")] + [ProducesResponseType(typeof(EventConsumerDto), 200)] [ApiPermission(Permissions.AdminEventsManage)] - public async Task Start(string name) + public async Task 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] [Route("event-consumers/{name}/stop/")] + [ProducesResponseType(typeof(EventConsumerDto), 200)] [ApiPermission(Permissions.AdminEventsManage)] - public async Task Stop(string name) + public async Task 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] [Route("event-consumers/{name}/reset/")] + [ProducesResponseType(typeof(EventConsumerDto), 200)] [ApiPermission(Permissions.AdminEventsManage)] - public async Task Reset(string name) + public async Task 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() diff --git a/src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumerDto.cs b/src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumerDto.cs index 3ef6535c3..df0846cbf 100644 --- a/src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumerDto.cs +++ b/src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumerDto.cs @@ -7,11 +7,16 @@ using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.Reflection; +using Squidex.Infrastructure.Security; +using Squidex.Shared; +using Squidex.Web; 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 IsResetting { get; set; } @@ -22,9 +27,35 @@ namespace Squidex.Areas.Api.Controllers.EventConsumers.Models 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) { - return SimpleMapper.Map(eventConsumerInfo, new EventConsumerDto()); + if (controller.HasPermission(EventsManagePermission)) + { + var values = new { name = result.Name }; + + if (!result.IsResetting) + { + result.AddPutLink("reset", controller.Url(x => nameof(x.ResetEventConsumer), values)); + } + + if (result.IsStopped) + { + result.AddPutLink("start", controller.Url(x => nameof(x.StartEventConsumer), values)); + } + else + { + result.AddPutLink("stop", controller.Url(x => nameof(x.StopEventConsumer), values)); + } + } + + return result; } } } diff --git a/src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumersDto.cs b/src/Squidex/Areas/Api/Controllers/EventConsumers/Models/EventConsumersDto.cs new file mode 100644 index 000000000..8f9a20766 --- /dev/null +++ b/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 + { + /// + /// The event consumers. + /// + public EventConsumerDto[] Items { get; set; } + + public static EventConsumersDto FromResults(IEnumerable 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(c => nameof(c.GetEventConsumers))); + + return result; + } + } +} diff --git a/src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html b/src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html index 6664745ac..8c31c3da2 100644 --- a/src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html +++ b/src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html @@ -42,13 +42,13 @@ {{eventConsumer.position}} - - - diff --git a/src/Squidex/app/features/administration/services/event-consumers.service.spec.ts b/src/Squidex/app/features/administration/services/event-consumers.service.spec.ts index 5eacb6d62..33bb41306 100644 --- a/src/Squidex/app/features/administration/services/event-consumers.service.spec.ts +++ b/src/Squidex/app/features/administration/services/event-consumers.service.spec.ts @@ -8,9 +8,13 @@ import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/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', () => { beforeEach(() => { @@ -32,7 +36,7 @@ describe('EventConsumersService', () => { it('should make get request to get event consumers', inject([EventConsumersService, HttpTestingController], (eventConsumersService: EventConsumersService, httpMock: HttpTestingController) => { - let eventConsumers: EventConsumerDto[]; + let eventConsumers: EventConsumersDto; eventConsumersService.getEventConsumers().subscribe(result => { eventConsumers = result; @@ -43,66 +47,120 @@ describe('EventConsumersService', () => { expect(req.request.method).toEqual('GET'); expect(req.request.headers.get('If-Match')).toBeNull(); - req.flush([ - { - name: 'event-consumer1', - position: '13', - isStopped: true, - isResetting: true, - error: 'an error 1' - }, - { - name: 'event-consumer2', - position: '29', - isStopped: true, - isResetting: true, - error: 'an error 2' - } - ]); + req.flush({ + items: [ + eventConsumerResponse(12), + eventConsumerResponse(13) + ] + }); expect(eventConsumers!).toEqual( - [ - new EventConsumerDto('event-consumer1', true, true, 'an error 1', '13'), - new EventConsumerDto('event-consumer2', true, true, 'an error 2', '29') - ]); + new EventConsumersDto([ + createEventConsumer(12), + createEventConsumer(13) + ])); })); it('should make put request to start event consumer', 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' } + } + }; + + let eventConsumer: EventConsumerDto; - const req = httpMock.expectOne('http://service/p/api/event-consumers/event-consumer1/start'); + 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.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', 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; - const req = httpMock.expectOne('http://service/p/api/event-consumers/event-consumer1/stop'); + eventConsumersService.putStop(resource).subscribe(response => { + eventConsumer = response; + }); + + const req = httpMock.expectOne('http://service/p/api/event-consumers/event-consumer123/stop'); expect(req.request.method).toEqual('PUT'); 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', 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.headers.get('If-Match')).toBeNull(); - req.flush({}); + req.flush(eventConsumerResponse(12)); + + expect(eventConsumer!).toEqual(createEventConsumer(12)); })); -}); \ No newline at end of file + + 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; +} \ No newline at end of file diff --git a/src/Squidex/app/features/administration/services/event-consumers.service.ts b/src/Squidex/app/features/administration/services/event-consumers.service.ts index 05b2cd0fd..5c49b1980 100644 --- a/src/Squidex/app/features/administration/services/event-consumers.service.ts +++ b/src/Squidex/app/features/administration/services/event-consumers.service.ts @@ -12,11 +12,24 @@ import { map } from 'rxjs/operators'; import { ApiUrlConfig, - Model, - pretifyError + pretifyError, + Resource, + ResourceLinks, + withLinks } from '@app/shared'; -export class EventConsumerDto extends Model { +export class EventConsumersDto { + public readonly _links: ResourceLinks = {}; + + constructor( + public readonly items: EventConsumerDto[] + ) { + } +} + +export class EventConsumerDto { + public readonly _links: ResourceLinks = {}; + constructor( public readonly name: string, public readonly isStopped?: boolean, @@ -24,7 +37,6 @@ export class EventConsumerDto extends Model { public readonly error?: string, public readonly position?: string ) { - super(); } } @@ -36,42 +48,62 @@ export class EventConsumersService { ) { } - public getEventConsumers(): Observable { + public getEventConsumers(): Observable { const url = this.apiUrl.buildUrl('/api/event-consumers'); - return this.http.get(url).pipe( + return this.http.get<{ items: any[] } & Resource>(url).pipe( map(body => { - const eventConsumers = body.map(item => - new EventConsumerDto( - item.name, - item.isStopped, - item.isResetting, - item.error, - item.position)); - - return eventConsumers; + const eventConsumers = body.items.map(item => parseEventConsumer(item)); + + return withLinks(new EventConsumersDto(eventConsumers), body); }), pretifyError('Failed to load event consumers. Please reload.')); } - public putStart(name: string): Observable { - const url = this.apiUrl.buildUrl(`api/event-consumers/${name}/start`); + public putStart(eventConsumer: Resource): Observable { + 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.')); } - public putStop(name: string): Observable { - const url = this.apiUrl.buildUrl(`api/event-consumers/${name}/stop`); + public putStop(eventConsumer: Resource): Observable { + 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.')); } - public putReset(name: string): Observable { - const url = this.apiUrl.buildUrl(`api/event-consumers/${name}/reset`); + public putReset(eventConsumer: Resource): Observable { + 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.')); } -} \ No newline at end of file +} + +function parseEventConsumer(response: any): EventConsumerDto { + return withLinks( + new EventConsumerDto( + response.name, + response.isStopped, + response.isResetting, + response.error, + response.position), + response); +} diff --git a/src/Squidex/app/features/administration/services/users.service.spec.ts b/src/Squidex/app/features/administration/services/users.service.spec.ts index 03b9b68fb..825f962f6 100644 --- a/src/Squidex/app/features/administration/services/users.service.spec.ts +++ b/src/Squidex/app/features/administration/services/users.service.spec.ts @@ -227,7 +227,8 @@ describe('UsersService', () => { }); export function createUser(id: number, suffix = '') { - const result = new UserDto(`${id}`, + const result = new UserDto( + `${id}`, `user${id}${suffix}@domain.com`, `user${id}${suffix}`, [ diff --git a/src/Squidex/app/features/administration/state/event-consumers.state.spec.ts b/src/Squidex/app/features/administration/state/event-consumers.state.spec.ts index 4a69ffbeb..ba384f5d1 100644 --- a/src/Squidex/app/features/administration/state/event-consumers.state.spec.ts +++ b/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 { EventConsumerDto, EventConsumersService } from '@app/features/administration/internal'; +import { EventConsumersDto, EventConsumersService } from '@app/features/administration/internal'; import { EventConsumersState } from './event-consumers.state'; +import { createEventConsumer } from './../services/event-consumers.service.spec'; + describe('EventConsumersState', () => { - const oldConsumers = [ - new EventConsumerDto('name1', false, false, 'error', '1'), - new EventConsumerDto('name2', true, true, 'error', '2') - ]; + const eventConsumer1 = createEventConsumer(1); + const eventConsumer2 = createEventConsumer(2); let dialogs: IMock; let eventConsumersService: IMock; @@ -38,11 +38,11 @@ describe('EventConsumersState', () => { describe('Loading', () => { it('should load event consumers', () => { eventConsumersService.setup(x => x.getEventConsumers()) - .returns(() => of(oldConsumers)).verifiable(); + .returns(() => of(new EventConsumersDto([eventConsumer1, eventConsumer2]))).verifiable(); eventConsumersState.load().subscribe(); - expect(eventConsumersState.snapshot.eventConsumers.values).toEqual(oldConsumers); + expect(eventConsumersState.snapshot.eventConsumers.values).toEqual([eventConsumer1, eventConsumer2]); expect(eventConsumersState.snapshot.isLoaded).toBeTruthy(); 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', () => { eventConsumersService.setup(x => x.getEventConsumers()) - .returns(() => of(oldConsumers)).verifiable(); + .returns(() => of(new EventConsumersDto([eventConsumer1, eventConsumer2]))).verifiable(); eventConsumersState.load(true).subscribe(); @@ -74,42 +74,48 @@ describe('EventConsumersState', () => { describe('Updates', () => { beforeEach(() => { eventConsumersService.setup(x => x.getEventConsumers()) - .returns(() => of(oldConsumers)).verifiable(); + .returns(() => of(new EventConsumersDto([eventConsumer1, eventConsumer2]))).verifiable(); eventConsumersState.load().subscribe(); }); - it('should unmark as stopped when started', () => { - eventConsumersService.setup(x => x.putStart(oldConsumers[1].name)) - .returns(() => of({})).verifiable(); + it('should update evnet consumer when started', () => { + const updated = createEventConsumer(2, '_new'); + + 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', () => { - eventConsumersService.setup(x => x.putStop(oldConsumers[0].name)) - .returns(() => of({})).verifiable(); + it('should update event consumer when stopped', () => { + const updated = createEventConsumer(2, '_new'); - 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', () => { - eventConsumersService.setup(x => x.putReset(oldConsumers[0].name)) - .returns(() => of({})).verifiable(); + it('should update event consumer when reset', () => { + const updated = createEventConsumer(2, '_new'); + + 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); }); }); }); \ No newline at end of file diff --git a/src/Squidex/app/features/administration/state/event-consumers.state.ts b/src/Squidex/app/features/administration/state/event-consumers.state.ts index f5c5a0d1d..278578beb 100644 --- a/src/Squidex/app/features/administration/state/event-consumers.state.ts +++ b/src/Squidex/app/features/administration/state/event-consumers.state.ts @@ -51,12 +51,12 @@ export class EventConsumersState extends State { } return this.eventConsumersService.getEventConsumers().pipe( - tap(payload => { + tap(({ items }) => { if (isReload && !silent) { this.dialogs.notifyInfo('Event Consumers reloaded.'); } - const eventConsumers = ImmutableArray.of(payload); + const eventConsumers = ImmutableArray.of(items); this.next(s => { return { ...s, eventConsumers, isLoaded: true }; @@ -66,8 +66,7 @@ export class EventConsumersState extends State { } public start(eventConsumer: EventConsumerDto): Observable { - return this.eventConsumersService.putStart(eventConsumer.name).pipe( - map(() => setStopped(eventConsumer, false)), + return this.eventConsumersService.putStart(eventConsumer).pipe( tap(updated => { this.replaceEventConsumer(updated); }), @@ -75,8 +74,7 @@ export class EventConsumersState extends State { } public stop(eventConsumer: EventConsumerDto): Observable { - return this.eventConsumersService.putStop(eventConsumer.name).pipe( - map(() => setStopped(eventConsumer, true)), + return this.eventConsumersService.putStop(eventConsumer).pipe( tap(updated => { this.replaceEventConsumer(updated); }), @@ -84,8 +82,7 @@ export class EventConsumersState extends State { } public reset(eventConsumer: EventConsumerDto): Observable { - return this.eventConsumersService.putReset(eventConsumer.name).pipe( - map(() => reset(eventConsumer)), + return this.eventConsumersService.putReset(eventConsumer).pipe( tap(updated => { this.replaceEventConsumer(updated); }), @@ -99,10 +96,4 @@ export class EventConsumersState extends State { return { ...s, eventConsumers }; }); } -} - -const setStopped = (eventConsumer: EventConsumerDto, isStopped: boolean) => - eventConsumer.with({ isStopped }); - -const reset = (eventConsumer: EventConsumerDto) => - eventConsumer.with({ isResetting: true }); \ No newline at end of file +} \ No newline at end of file diff --git a/src/Squidex/app/features/administration/state/users.state.spec.ts b/src/Squidex/app/features/administration/state/users.state.spec.ts index b9e4971db..3000bf6f6 100644 --- a/src/Squidex/app/features/administration/state/users.state.spec.ts +++ b/src/Squidex/app/features/administration/state/users.state.spec.ts @@ -168,7 +168,7 @@ describe('UsersState', () => { 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'); usersService.setup(x => x.lockUser(user2)) @@ -177,12 +177,12 @@ describe('UsersState', () => { usersState.select(user2.id).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'); usersService.setup(x => x.unlockUser(user2)) @@ -197,7 +197,7 @@ describe('UsersState', () => { 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 updated = createUser(2, '_new');