diff --git a/src/Squidex/app/shared/services/users.service.ts b/src/Squidex/app/shared/services/users.service.ts index 6f4d4c1da..7c245db05 100644 --- a/src/Squidex/app/shared/services/users.service.ts +++ b/src/Squidex/app/shared/services/users.service.ts @@ -44,38 +44,38 @@ export class UsersService { const url = this.apiUrl.buildUrl(`api/users?query=${query || ''}`); return this.http.get(url).pipe( - map(body => { - const users = body.map(item => - new UserDto( - item.id, - item.displayName)); + map(body => { + const users = body.map(item => + new UserDto( + item.id, + item.displayName)); - return users; - }), - pretifyError('Failed to load users. Please reload.')); + return users; + }), + pretifyError('Failed to load users. Please reload.')); } public getUser(id: string): Observable { const url = this.apiUrl.buildUrl(`api/users/${id}`); return this.http.get(url).pipe( - map(body => { - const user = new UserDto( - body.id, - body.displayName); + map(body => { + const user = new UserDto( + body.id, + body.displayName); - return user; - }), - pretifyError('Failed to load user. Please reload.')); + return user; + }), + pretifyError('Failed to load user. Please reload.')); } public getResources(): Observable { const url = this.apiUrl.buildUrl(`api`); return this.http.get<{ _links: {} }>(url).pipe( - map(({ _links }) => { - return new ResourcesDto(_links); - }), - pretifyError('Failed to load user. Please reload.')); + map(({ _links }) => { + return new ResourcesDto(_links); + }), + pretifyError('Failed to load user. Please reload.')); } } \ No newline at end of file diff --git a/src/Squidex/app/shared/services/workflows.service.spec.ts b/src/Squidex/app/shared/services/workflows.service.spec.ts index f6584bd8a..634052471 100644 --- a/src/Squidex/app/shared/services/workflows.service.spec.ts +++ b/src/Squidex/app/shared/services/workflows.service.spec.ts @@ -5,7 +5,138 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { WorkflowDto, WorkflowPayload } from '@app/shared/internal'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { inject, TestBed } from '@angular/core/testing'; + +import { + AnalyticsService, + ApiUrlConfig, + Version, + Versioned, + WorkflowDto, + WorkflowPayload, + WorkflowsService +} from '@app/shared/internal'; + +describe('WorkflowsService', () => { + + const version = new Version('1'); + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + HttpClientTestingModule + ], + providers: [ + WorkflowsService, + { provide: ApiUrlConfig, useValue: new ApiUrlConfig('http://service/p/') }, + { provide: AnalyticsService, useValue: new AnalyticsService() } + ] + }); + }); + + afterEach(inject([HttpTestingController], (httpMock: HttpTestingController) => { + httpMock.verify(); + })); + + it('should make a get request to get app workflows', + inject([WorkflowsService, HttpTestingController], (workflowsService: WorkflowsService, httpMock: HttpTestingController) => { + + let workflow: Versioned; + + workflowsService.getWorkflow('my-app').subscribe(result => { + workflow = result; + }); + + const req = httpMock.expectOne('http://service/p/api/apps/my-app/workflows'); + + expect(req.request.method).toEqual('GET'); + expect(req.request.headers.get('If-Match')).toBeNull(); + + req.flush(workflowsResponse('Draft'), + { + headers: { + etag: '2' + } + }); + + expect(workflow!).toEqual({ payload: createWorkflow('Draft'), version: new Version('2') }); + })); + + it('should make a put request to assign a workflow', + inject([WorkflowsService, HttpTestingController], (workflowsService: WorkflowsService, httpMock: HttpTestingController) => { + + const dto = createWorkflow('Draft'); + + let workflow: Versioned; + + workflowsService.putWorkflow('my-app', dto, dto.workflow.serialize(), version).subscribe(result => { + workflow = result; + }); + + const req = httpMock.expectOne('http://service/p/api/apps/my-app/workflows'); + + expect(req.request.method).toEqual('PUT'); + expect(req.request.headers.get('If-Match')).toEqual(version.value); + + req.flush(workflowsResponse('Draft'), { + headers: { + etag: '2' + } + }); + + expect(workflow!).toEqual({ payload: createWorkflow('Draft'), version: new Version('2') }); + })); + + function workflowsResponse(name: string) { + return { + workflow: { + steps: { + [`${name}1`]: { + transitions: { + [`${name}2`]: { + expression: 'Expression1', role: 'Role1' + } + }, + color: `${name}1`, noUpdate: true + }, + [`${name}2`]: { + transitions: { + [`${name}1`]: { + expression: 'Expression2', role: 'Role2' + } + }, + color: `${name}2`, noUpdate: true + } + }, + initial: `${name}1`, + _links: { + update: { method: 'PUT', href: '/api/workflows' } + } + }, + _links: {}, + canCreate: true + }; + } +}); + +export function createWorkflow(name: string): WorkflowPayload { + return { + workflow: new WorkflowDto({ + update: { method: 'PUT', href: '/api/workflows' } + }, + `${name}1`, + [ + { name: `${name}1`, color: `${name}1`, noUpdate: true, isLocked: false }, + { name: `${name}2`, color: `${name}2`, noUpdate: true, isLocked: false } + ], + [ + { from: `${name}1`, to: `${name}2`, expression: 'Expression1', role: 'Role1' }, + { from: `${name}2`, to: `${name}1`, expression: 'Expression2', role: 'Role2' } + ]), + _links: {} + }; +} describe('Workflow', () => { it('should create empty workflow', () => { @@ -296,22 +427,5 @@ describe('Workflow', () => { initial: '2' }); }); -}); -export function createWorkflow(name: string): WorkflowPayload { - return { - workflow: new WorkflowDto({ - update: { method: 'PUT', href: '/api/workflows' } - }, - `${name}1`, - [ - { name: `${name}1`, color: `${name}1`, noUpdate: true, isLocked: true }, - { name: `${name}2`, color: `${name}1`, noUpdate: true, isLocked: true } - ], - [ - { from: `${name}1`, to: `${name}2`, expression: 'Expression1', role: 'Role1' }, - { from: `${name}2`, to: `${name}1`, expression: 'Expression2', role: 'Role2' } - ]), - _links: {} - }; -} \ No newline at end of file +}); \ No newline at end of file diff --git a/src/Squidex/app/shared/services/workflows.service.ts b/src/Squidex/app/shared/services/workflows.service.ts index e1b3398bb..4f8450cdf 100644 --- a/src/Squidex/app/shared/services/workflows.service.ts +++ b/src/Squidex/app/shared/services/workflows.service.ts @@ -5,19 +5,26 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ +import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { Observable, of } from 'rxjs'; +import { Observable } from 'rxjs'; +import { tap } from 'rxjs/operators'; import { + AnalyticsService, + ApiUrlConfig, compareStringsAsc, hasAnyLink, + HTTP, + mapVersioned, + pretifyError, Resource, ResourceLinks, Version, - Versioned, - versioned + Versioned } from '@app/framework'; +export type WorkflowsDto = Versioned; export type WorkflowPayload = { workflow: WorkflowDto; } & Resource; export class WorkflowDto { @@ -72,7 +79,7 @@ export class WorkflowDto { return this; } - values = { ...existing, ...values }; + values = { ...existing, ...values }; } const steps = [...this.steps.filter(s => s !== found), { name, ...values }]; @@ -209,7 +216,6 @@ export class WorkflowDto { } return result; - } } @@ -223,11 +229,64 @@ export type WorkflowTransitionView = { step: WorkflowStep } & WorkflowTransition @Injectable() export class WorkflowsService { + constructor( + private readonly http: HttpClient, + private readonly apiUrl: ApiUrlConfig, + private readonly analytics: AnalyticsService + ) { + } + public getWorkflow(appName: string): Observable> { - return of(versioned(new Version('1'), { workflow: WorkflowDto.DEFAULT, _links: {} })); + const url = this.apiUrl.buildUrl(`api/apps/${appName}/workflows`); + + return HTTP.getVersioned(this.http, url).pipe( + mapVersioned(({ body }) => { + return parseWorkflowPayload(body); + }), + pretifyError('Failed to load workflows. Please reload.')); } public putWorkflow(appName: string, resource: Resource, dto: any, version: Version): Observable> { - return of(versioned(new Version('1'), { workflow: WorkflowDto.DEFAULT, _links: {} })); + const url = this.apiUrl.buildUrl(`api/apps/${appName}/workflows`); + + return HTTP.putVersioned(this.http, url, resource, version).pipe( + mapVersioned(({ body }) => { + return parseWorkflowPayload(body); + }), + tap(() => { + this.analytics.trackEvent('Workflow', 'Configured', appName); + }), + pretifyError('Failed to configure Workflow. Please reload.')); } +} + +function parseWorkflowPayload(response: any) { + const { workflow, _links } = response; + + const result = parseWorkflow(workflow); + + return { workflow: result, _links }; +} + +function parseWorkflow(workflow: any) { + const steps: WorkflowStep[] = []; + const transitions: WorkflowTransition[] = []; + + for (let stepName in workflow.steps) { + if (workflow.steps.hasOwnProperty(stepName)) { + const step = workflow.steps[stepName]; + + steps.push({ name: stepName, color: step.color, noUpdate: step.noUpdate, isLocked: stepName === 'Published' }); + + for (let to in step.transitions) { + if (step.transitions.hasOwnProperty(to)) { + const transition = step.transitions[to]; + + transitions.push({ from: stepName, to, ...transition }); + } + } + } + } + + return new WorkflowDto(workflow._links, workflow.initial, steps, transitions); } \ No newline at end of file