Browse Source

Workflow model.

pull/372/head
Sebastian Stehle 7 years ago
parent
commit
a3635ea825
  1. 2
      src/Squidex/app-config/karma-test-shim.js
  2. 1
      src/Squidex/app/shared/internal.ts
  3. 2
      src/Squidex/app/shared/services/users.service.spec.ts
  4. 138
      src/Squidex/app/shared/services/workflow.service.ts
  5. 188
      src/Squidex/app/shared/services/workflows.service.spec.ts
  6. 99
      src/Squidex/app/shared/services/workflows.service.ts

2
src/Squidex/app-config/karma-test-shim.js

@ -19,7 +19,7 @@ testing.TestBed.initTestEnvironment(
browser.platformBrowserDynamicTesting()
);
var testContext = require.context('../app', true, /\.spec\.ts/);
var testContext = require.context('../app', true, /workflows\.service\.spec\.ts/);
/**
* Get all the files, for each file, call the context function

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

@ -32,6 +32,7 @@ export * from './services/ui.service';
export * from './services/usages.service';
export * from './services/users-provider.service';
export * from './services/users.service';
export * from './services/workflows.service';
export * from './state/apps.forms';
export * from './state/apps.state';

2
src/Squidex/app/shared/services/users.service.spec.ts

@ -10,10 +10,10 @@ import { inject, TestBed } from '@angular/core/testing';
import {
ApiUrlConfig,
ResourcesDto,
UserDto,
UsersService
} from '@app/shared/internal';
import { ResourcesDto } from './users.service';
describe('UsersService', () => {
beforeEach(() => {

138
src/Squidex/app/shared/services/workflow.service.ts

@ -1,138 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Model, ResourceLinks } from '@app/shared';
export class WorkflowDto extends Model<WorkflowDto> {
public readonly _links: ResourceLinks;
constructor(links: ResourceLinks,
public readonly name: string,
public readonly steps: WorkflowStepDto[]
) {
super();
this._links = links;
}
public addStep(name: string, color: string) {
let found = this.steps.find(x => x.name === name);
if (found) {
return this;
}
const steps = [...this.steps, new WorkflowStepDto(name, color, [])];
return this.with({ steps });
}
public changeStepColor(name: string, color: string) {
return this.updateStep(name, x => x.changeColor(color));
}
public changeStepName(name: string, newName: string) {
return this.updateStep(name, x => x.changeName(newName));
}
public changeTransition(from: string, to: string, expression?: string) {
return this.updateStep(from, x => x.changeExpression(to, expression));
}
public removeStep(name: string) {
const steps = this.steps.filter(x => x.name !== name).map(x => x.removeTransition(name));
return this.with({ steps });
}
private updateStep(name: string, updater: (step: WorkflowStepDto) => WorkflowStepDto) {
let found = this.steps.find(x => x.name === name);
if (!found) {
return this;
}
const newStep = updater(found);
const steps = this.steps.map(x => x.name === name ? newStep : x.replaceTransition(newStep));
return this.with({ steps });
}
}
export class WorkflowStepDto extends Model<WorkflowStepDto> {
constructor(
public readonly name: string,
public readonly color: string,
public readonly transitions: WorkflowTransitionDto[]
) {
super();
}
public changeName(name: string) {
return this.with({ name });
}
public changeColor(color: string) {
return this.with({ color });
}
public changeExpression(to: string, expression?: string) {
return this.updateTransition(to, x => x.changeExpression(expression));
}
public addTransition(step: WorkflowStepDto) {
let found = this.transitions.find(x => x.step.name === step.name);
if (found) {
return this;
}
const transitions = [...this.transitions, new WorkflowTransitionDto(step) ];
return this.with({ transitions });
}
public replaceTransition(step: WorkflowStepDto) {
const transitions = this.transitions.map(x => x.step.name === step.name ? new WorkflowTransitionDto(step, x.expression) : x);
return this.with({ transitions });
}
public removeTransition(to: string) {
const transitions = this.transitions.filter(x => x.step.name !== to);
return this.with({ transitions });
}
private updateTransition(to: string, updater: (step: WorkflowTransitionDto) => WorkflowTransitionDto) {
let found = this.transitions.find(x => x.step.name === to);
if (!found) {
return this;
}
const newTransition = updater(found);
const transitions = this.transitions.map(x => x === found ? newTransition : x);
return this.with({ transitions });
}
}
export class WorkflowTransitionDto extends Model<WorkflowTransitionDto> {
constructor(
public readonly step: WorkflowStepDto,
public readonly expression?: string
) {
super();
}
public changeExpression(expression?: string) {
return this.with({ expression });
}
}

188
src/Squidex/app/shared/services/workflows.service.spec.ts

@ -0,0 +1,188 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { WorkflowDto } from '@app/shared/internal';
describe('Workflow', () => {
it('should create empty workflow', () => {
const workflow = new WorkflowDto();
expect(workflow.name).toEqual('Default');
});
it('should add step to workflow', () => {
const workflow =
new WorkflowDto()
.setStep('1', { color: '#00ff00' });
expect(simplify(workflow)).toEqual({
_links: {},
steps: [
{ name: '1', color: '#00ff00' }
],
transitions: [],
name: 'Default'
});
});
it('should override settings if step already exists', () => {
const workflow =
new WorkflowDto()
.setStep('1', { color: '#00ff00' })
.setStep('1', { color: 'red' });
expect(simplify(workflow)).toEqual({
_links: {},
steps: [
{ name: '1', color: 'red' }
],
transitions: [],
name: 'Default'
});
});
it('should not remove step if locked', () => {
const workflow =
new WorkflowDto()
.setStep('1', { color: '#00ff00', isLocked: true })
.setStep('2', { color: '#ff0000' })
.setTransition('1', '2', { expression: '1 === 2' })
.removeStep('1');
expect(simplify(workflow)).toEqual({
_links: {},
steps: [
{ name: '1', color: '#00ff00', isLocked: true },
{ name: '2', color: '#ff0000' }
],
transitions: [
{ from: '1', to: '2', expression: '1 === 2' }
],
name: 'Default'
});
});
it('should remove step', () => {
const workflow =
new WorkflowDto()
.setStep('1', { color: '#00ff00' })
.setStep('2', { color: '#ff0000' })
.setStep('3', { color: '#0000ff' })
.setTransition('1', '2', { expression: '1 === 2' })
.setTransition('1', '3', { expression: '1 === 3' })
.setTransition('2', '3', { expression: '2 === 3' })
.removeStep('1');
expect(simplify(workflow)).toEqual({
_links: {},
steps: [
{ name: '2', color: '#ff0000' },
{ name: '3', color: '#0000ff' }
],
transitions: [
{ from: '2', to: '3', expression: '2 === 3' }
],
name: 'Default'
});
});
it('should rename step', () => {
const workflow =
new WorkflowDto()
.setStep('1', { color: '#00ff00' })
.setStep('2', { color: '#ff0000' })
.setStep('3', { color: '#0000ff' })
.setTransition('1', '2', { expression: '1 === 2' })
.setTransition('2', '1', { expression: '2 === 1' })
.setTransition('2', '3', { expression: '2 === 3' })
.renameStep('1', '4');
expect(simplify(workflow)).toEqual({
_links: {},
steps: [
{ name: '4', color: '#00ff00' },
{ name: '2', color: '#ff0000' },
{ name: '3', color: '#0000ff' }
],
transitions: [
{ from: '4', to: '2', expression: '1 === 2' },
{ from: '2', to: '4', expression: '2 === 1' },
{ from: '2', to: '3', expression: '2 === 3' }
],
name: 'Default'
});
});
it('should add transitions to workflow', () => {
const workflow =
new WorkflowDto()
.setStep('1', { color: '#00ff00' })
.setStep('2', { color: '#ff0000' })
.setTransition('1', '2', { expression: '1 === 2' })
.setTransition('2', '1', { expression: '2 === 1' });
expect(simplify(workflow)).toEqual({
_links: {},
steps: [
{ name: '1', color: '#00ff00' },
{ name: '2', color: '#ff0000' }
],
transitions: [
{ from: '1', to: '2', expression: '1 === 2' },
{ from: '2', to: '1', expression: '2 === 1' }
],
name: 'Default'
});
});
it('should add remove transition from workflow', () => {
const workflow =
new WorkflowDto()
.setStep('1', { color: '#00ff00' })
.setStep('2', { color: '#ff0000' })
.setTransition('1', '2', { expression: '1 === 1' })
.setTransition('2', '1', { expression: '2 === 1' })
.removeTransition('1', '2');
expect(simplify(workflow)).toEqual({
_links: {},
steps: [
{ name: '1', color: '#00ff00' },
{ name: '2', color: '#ff0000' }
],
transitions: [
{ from: '2', to: '1', expression: '2 === 1' }
],
name: 'Default'
});
});
it('should override settings if transition already exists', () => {
const workflow =
new WorkflowDto()
.setStep('1', { color: '#00ff00' })
.setStep('2', { color: '#ff0000' })
.setTransition('1', '2', { expression: '1 === 2' })
.setTransition('1', '2', { expression: '1 !== 2' });
expect(simplify(workflow)).toEqual({
_links: {},
steps: [
{ name: '1', color: '#00ff00' },
{ name: '2', color: '#ff0000' }
],
transitions: [
{ from: '1', to: '2', expression: '1 !== 2' }
],
name: 'Default'
});
});
});
function simplify(value: any) {
return JSON.parse(JSON.stringify(value));
}

99
src/Squidex/app/shared/services/workflows.service.ts

@ -0,0 +1,99 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Model, ResourceLinks } from '@app/framework';
export class WorkflowDto extends Model<WorkflowDto> {
public readonly _links: ResourceLinks;
constructor(links: ResourceLinks = {},
public readonly name: string = 'Default',
public readonly steps: WorkflowStep[] = [],
public readonly transitions: WorkflowTransition[] = []
) {
super();
this._links = links;
}
public setStep(name: string, values: Partial<WorkflowStepValues>) {
const steps = [...this.steps.filter(s => s.name !== name), { name, ...values }];
return this.with({ steps });
}
public removeStep(name: string) {
const steps = this.steps.filter(s => s.name !== name || s.isLocked);
const transitions =
steps.length !== this.steps.length ?
this.transitions.filter(t => t.from !== name && t.to !== name) :
this.transitions;
return this.with({ steps, transitions });
}
public renameStep(name: string, newName: string) {
const steps = this.steps.map(step => {
if (step.name === name) {
return { ...step, name: newName };
}
return step;
});
const transitions = this.transitions.map(transition => {
if (transition.from === name || transition.to === name) {
let newTransition = { ...transition };
if (newTransition.from === name) {
newTransition.from = newName;
}
if (newTransition.to === name) {
newTransition.to = newName;
}
return newTransition;
}
return transition;
});
return this.with({ steps, transitions });
}
public removeTransition(from: string, to: string) {
const transitions = this.transitions.filter(t => t.from !== from || t.to !== to);
return this.with({ transitions });
}
public setTransition(from: string, to: string, values: Partial<WorkflowTransitionValues>) {
const stepFrom = this.steps.find(s => s.name === from);
if (!stepFrom) {
return this;
}
const stepTo = this.steps.find(s => s.name === to);
if (!stepTo) {
return this;
}
const transitions = [...this.transitions.filter(t => t.from !== from || t.to !== to), { from, to, ...values }];
return this.with({ transitions });
}
}
export type WorkflowStepValues = { color?: string; isLocked?: boolean; };
export type WorkflowStep = { name: string } & WorkflowStepValues;
export type WorkflowTransitionValues = { expression?: string };
export type WorkflowTransition = { from: string; to: string } & WorkflowTransitionValues;
Loading…
Cancel
Save