Skip to content
Snippets Groups Projects
Commit 13013a98 authored by Alexander Philipp Nowosad's avatar Alexander Philipp Nowosad
Browse files

Add bm process diagram with ability to attach process patterns

parent e1cae698
No related branches found
No related tags found
No related merge requests found
Pipeline #
Showing
with 349 additions and 4 deletions
......@@ -64,6 +64,8 @@ import { BmProcessComponent } from './bm-process/bm-process.component';
import { CategoriesFormComponent } from './categories-form/categories-form.component';
import { FormArrayListComponent } from './form-array-list/form-array-list.component';
import { ProcessPatternCategoriesFormComponent } from './process-pattern-categories-form/process-pattern-categories-form.component';
import { BmProcessDiagramComponent } from './bm-process-diagram/bm-process-diagram.component';
import { SelectProcessPatternFormComponent } from './select-process-pattern-form/select-process-pattern-form.component';
@NgModule({
declarations: [
......@@ -126,6 +128,8 @@ import { ProcessPatternCategoriesFormComponent } from './process-pattern-categor
CategoriesFormComponent,
FormArrayListComponent,
ProcessPatternCategoriesFormComponent,
BmProcessDiagramComponent,
SelectProcessPatternFormComponent,
],
imports: [
BrowserModule,
......
<ng-template #addProcessPatternModal let-d="dismiss">
<div class="modal-header">
<h4 class="modal-title">Add Process Pattern</h4>
<button type="button" class="close" aria-label="Close" (click)="d()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="text-muted pt-3">
<app-select-process-pattern-form
(submitProcessPatternForm)="addProcessPattern($event)"></app-select-process-pattern-form>
</div>
</div>
</ng-template>
<div #canvas class="border" style="height: 500px"></div>
import { BmProcessDiagramComponent } from './bm-process-diagram.component';
import { createComponentFactory, Spectator } from '@ngneat/spectator';
import { MockComponent } from 'ng-mocks';
import { SelectProcessPatternFormComponent } from '../select-process-pattern-form/select-process-pattern-form.component';
describe('BmProcessDiagramComponent', () => {
let spectator: Spectator<BmProcessDiagramComponent>;
const createComponent = createComponentFactory({
component: BmProcessDiagramComponent,
declarations: [
MockComponent(SelectProcessPatternFormComponent),
],
});
beforeEach(() => spectator = createComponent());
it('should create', () => {
expect(spectator.component).toBeTruthy();
});
});
import { AfterContentInit, Component, ElementRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { BmProcess } from '../model/bm-process';
import BpmnModeler from 'bpmn-js/lib/Modeler';
import { BpmnService } from '../bpmn.service';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { FormGroup } from '@angular/forms';
import { ProcessPatternService } from '../process-pattern.service';
@Component({
selector: 'app-bm-process-diagram',
templateUrl: './bm-process-diagram.component.html',
styleUrls: ['./bm-process-diagram.component.css']
})
export class BmProcessDiagramComponent implements OnInit, AfterContentInit, OnDestroy {
@Input() bmProcess: BmProcess;
private modeler: BpmnModeler;
private modalBusinessObject;
private modalReference: NgbModalRef;
@ViewChild('canvas', {static: true}) canvas: ElementRef<HTMLDivElement>;
@ViewChild('addProcessPatternModal', {static: true}) addProcessPatternModal: any;
constructor(
private bpmnService: BpmnService,
private modalService: NgbModal,
private processPatternService: ProcessPatternService,
) {
}
ngOnInit() {
this.modeler = this.bpmnService.getBmProcessModeler();
this.modeler.get('eventBus').on('bmdl.processPatterns', (event, businessObject) => this.openAddProcessPatternModal(businessObject));
if (this.bmProcess) {
this.loadBmProcess(this.bmProcess);
}
}
ngAfterContentInit() {
this.modeler.attachTo(this.canvas.nativeElement);
}
ngOnDestroy() {
this.modeler.destroy();
}
private loadBmProcess(bmProcess: BmProcess) {
if (bmProcess.processDiagram) {
this.modeler.importXML(bmProcess.processDiagram).catch(
error => console.log('LoadBmProcess: ' + error)
);
} else {
this.modeler.createDiagram();
}
}
openAddProcessPatternModal(startingBusinessObject) {
this.modalBusinessObject = startingBusinessObject;
this.modalReference = this.modalService.open(this.addProcessPatternModal, {size: 'lg'});
}
addProcessPattern(addPatternForm: FormGroup) {
this.modalReference.close();
this.processPatternService.getProcessPattern(addPatternForm.value.processPatternId).then((processPattern) => {
const processPatternModeler = this.bpmnService.getBmProcessModeler();
processPatternModeler.importXML(processPattern.pattern).then(() => {
this.bpmnService.appendBpmn(this.modeler, processPatternModeler, this.modalBusinessObject);
}).catch(
error => console.log('AddProcessPattern (inner): ' + error)
);
}).catch(
error => console.log('AddProcessPattern: ' + error)
);
}
}
......@@ -4,6 +4,6 @@
</nav>
</div>
<main *ngIf="bmProcess" role="main" class="container">
Business Model Development Process: {{bmProcess.name}}
<main *ngIf="bmProcess" role="main">
<app-bm-process-diagram [bmProcess]="bmProcess"></app-bm-process-diagram>
</main>
import { BmProcessComponent } from './bm-process.component';
import { createRoutingFactory, Spectator } from '@ngneat/spectator';
import { MockComponent } from 'ng-mocks';
import { BmProcessDiagramComponent } from '../bm-process-diagram/bm-process-diagram.component';
describe('BmProcessComponent', () => {
let spectator: Spectator<BmProcessComponent>;
const createComponent = createRoutingFactory({
component: BmProcessComponent,
declarations: [],
declarations: [
MockComponent(BmProcessDiagramComponent),
],
});
beforeEach(() => spectator = createComponent());
......
import { is } from 'bpmn-js/lib/util/ModelUtil';
export default class ProcessPatternsContextPad {
static $inject = [
'contextPad',
'eventBus',
];
constructor(contextPad, private eventBus) {
contextPad.registerProvider(this);
}
getContextPadEntries(element) {
if (is(element, 'bpmn:StartEvent') || is(element, 'bpmn:EndEvent')) {
return {
'bmdl.processPatterns': {
group: 'edit',
className: 'bpmn-icon-service',
title: 'Add Process Pattern',
action: {
click: () => this.eventBus.fire('bmdl.processPatterns', element.businessObject),
}
}
};
} else {
return {};
}
}
}
import ProcessPatternsContextPad from './ProcessPatternsContextPad';
export default {
__init__: ['processPatternsContextPad'],
processPatternsContextPad: ['type', ProcessPatternsContextPad],
};
import { Injectable } from '@angular/core';
import BpmnModeler from 'bpmn-js/lib/Modeler';
import bmProcess from './bpmn-extensions/bm-process';
import categories from './bpmn-extensions/categories';
import bmdl from '../assets/bpmn_bmdl.json';
import { is } from 'bpmn-js/lib/util/ModelUtil';
import { center } from 'diagram-js/lib/util/PositionUtil';
@Injectable({
providedIn: 'root'
......@@ -25,4 +28,108 @@ export class BpmnService {
});
}
/**
* Get a BpmnModeler with customizations to support the development of business model development processes
*
* @returns a bpmnModeler
*/
getBmProcessModeler(): BpmnModeler {
return new BpmnModeler({
additionalModules: [
bmProcess,
],
moddleExtensions: {
bmdl,
},
// TODO change to not support much editing, except exchanging tasks with methods
});
}
appendBpmn(current: BpmnModeler, append: BpmnModeler, lastElement) {
new AppendBpmnOperation(current, append).append(lastElement);
}
}
class AppendBpmnOperation {
private appendElementRegistry;
private currentElementRegistry;
private currentElementFactory;
private currentBpmnFactory;
private currentModeling;
private prefix = String(Date.now()) + '_';
constructor(current: BpmnModeler, append: BpmnModeler) {
this.appendElementRegistry = append.get('elementRegistry');
this.currentElementRegistry = current.get('elementRegistry');
this.currentElementFactory = current.get('elementFactory');
this.currentBpmnFactory = current.get('bpmnFactory');
this.currentModeling = current.get('modeling');
}
append(lastElement) {
const attachingElement = this.prepareAttachment(lastElement);
const parent = attachingElement.parent;
const base = center(attachingElement);
const oldBase = center(this.getStartingPoint());
this.appendElementRegistry.filter(
(element) => is(element, 'bpmn:Task') || is(element, 'bpmn:Gateway') || is(element, 'bpmn:EndEvent')
).forEach((element) => {
const bmBusinessObject = this.currentBpmnFactory.create(element.businessObject.$type, {
...element.businessObject,
id: this.prefix + element.businessObject.id
});
const bmElement = this.currentElementFactory.createShape({type: element.type, businessObject: bmBusinessObject});
const point = center(element);
this.currentModeling.createShape(bmElement, {x: point.x + base.x - oldBase.x, y: point.y + base.y - oldBase.y}, parent);
});
this.appendElementRegistry.filter(
(element) => is(element, 'bpmn:SequenceFlow')
).forEach((element) => {
const sequenceFlow = element.businessObject;
const source = this.currentElementRegistry.get(this.prefix + sequenceFlow.sourceRef.id);
const target = this.currentElementRegistry.get(this.prefix + sequenceFlow.targetRef.id);
if (source) {
this.currentModeling.createConnection(source, target, {type: element.type}, parent);
} else {
this.currentModeling.createConnection(attachingElement, target, {type: element.type}, parent);
}
});
}
/**
* Prepares the attachment of another BPMN model to the end of this model and returns the attaching element
*
* @param lastElement the last element of the current model
* @returns the element to which the appended model should be attached
*/
private prepareAttachment(lastElement) {
if (is(lastElement, 'bpmn:EndEvent')) {
let attachingElement;
if (lastElement.get('incoming').length > 1) {
// TODO: Create merge node as attaching element
} else if (lastElement.get('incoming').length === 1) {
attachingElement = this.currentElementRegistry.get(lastElement.get('incoming')[0].sourceRef.id);
} else {
throw new Error('Can not attach to end event');
}
this.currentModeling.removeElements([this.currentElementRegistry.get(lastElement.id)]);
return attachingElement;
} else {
return this.currentElementRegistry.get(lastElement.id);
}
}
private getStartingPoint() {
return this.appendElementRegistry.find(
(element) => is(element, 'bpmn:StartEvent'),
);
}
}
......@@ -6,7 +6,7 @@ export class BmProcess extends PouchdbModel {
name: string;
// TODO Process Pattern and Methods connection
processDiagram: string;
constructor(bmProcess: Partial<BmProcess>) {
super(BmProcess.typeName);
......
<form [formGroup]="addProcessPatternForm" (ngSubmit)="submitForm()">
<div class="form-group row">
<label for="processPatternSelector" class="col-sm-4 col-form-label">Process Pattern</label>
<div class="col-sm-8">
<select id="processPatternSelector" formControlName="processPatternId" class="form-control">
<option *ngFor="let processPattern of processPatterns"
[value]="processPattern._id">{{ processPattern.name }}</option>
</select>
</div>
</div>
<div class="form-group row">
<div class="col-sm-12">
<button type="submit" class="btn btn-sm btn-dark btn-block" [disabled]="!addProcessPatternForm.valid">
Add Process Pattern
</button>
</div>
</div>
</form>
import { SelectProcessPatternFormComponent } from './select-process-pattern-form.component';
import { createComponentFactory, Spectator } from '@ngneat/spectator';
import { ReactiveFormsModule } from '@angular/forms';
describe('SelectProcessPatternFormComponent', () => {
let spectator: Spectator<SelectProcessPatternFormComponent>;
const createComponent = createComponentFactory({
component: SelectProcessPatternFormComponent,
declarations: [],
imports: [ReactiveFormsModule],
});
beforeEach(() => spectator = createComponent());
it('should create', () => {
expect(spectator.component).toBeTruthy();
});
});
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ProcessPattern } from '../model/process-pattern';
import { ProcessPatternService } from '../process-pattern.service';
@Component({
selector: 'app-select-process-pattern-form',
templateUrl: './select-process-pattern-form.component.html',
styleUrls: ['./select-process-pattern-form.component.css']
})
export class SelectProcessPatternFormComponent implements OnInit {
@Output() submitProcessPatternForm = new EventEmitter<FormGroup>();
processPatterns: ProcessPattern[];
addProcessPatternForm: FormGroup = this.fb.group({
processPatternId: [null, Validators.required],
});
constructor(
private fb: FormBuilder,
private processPatternService: ProcessPatternService,
) {
}
ngOnInit() {
this.loadProcessPatterns();
}
submitForm() {
this.submitProcessPatternForm.emit(this.addProcessPatternForm);
}
private loadProcessPatterns() {
this.processPatternService.getProcessPatternList().then(
list => this.processPatterns = list.docs
).catch(
error => console.log('LoadProcessPatterns: ' + error)
);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment