adding value attribute for customized output for action
[ccsdk/cds.git] / cds-ui / designer-client / src / app / modules / feature-modules / packages / designer / designer.component.ts
1 /*
2 ============LICENSE_START==========================================
3 ===================================================================
4 Copyright (C) 2019 Orange. All rights reserved.
5 ===================================================================
6 Modification Copyright (c) 2020 IBM
7 ===================================================================
8 Modification Copyright (c) 2020 Orange
9 ===================================================================
10
11 Unless otherwise specified, all software contained herein is licensed
12 under the Apache License, Version 2.0 (the License);
13 you may not use this software except in compliance with the License.
14 You may obtain a copy of the License at
15
16     http://www.apache.org/licenses/LICENSE-2.0
17
18 Unless required by applicable law or agreed to in writing, software
19 distributed under the License is distributed on an "AS IS" BASIS,
20 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 See the License for the specific language governing permissions and
22 limitations under the License.
23 ============LICENSE_END============================================
24 */
25
26 import dagre from 'dagre';
27 import graphlib from 'graphlib';
28 import {Component, OnDestroy, OnInit, ViewEncapsulation} from '@angular/core';
29 import * as joint from 'jointjs';
30 import './jointjs/elements/palette.function.element';
31 import './jointjs/elements/action.element';
32 import './jointjs/elements/board.function.element';
33 import {DesignerStore} from './designer.store';
34 import {ActionElementTypeName} from 'src/app/common/constants/app-constants';
35 import {GraphUtil} from './graph.util';
36 import {GraphGenerator} from './graph.generator.util';
37 import {FunctionsStore} from './functions.store';
38 import {Subject} from 'rxjs';
39 import {distinctUntilChanged, takeUntil} from 'rxjs/operators';
40 import {BluePrintDetailModel} from '../model/BluePrint.detail.model';
41 import {ActivatedRoute, Router} from '@angular/router';
42 import {DesignerService} from './designer.service';
43 import {FilesContent, FolderNodeElement} from '../package-creation/mapping-models/metadata/MetaDataTab.model';
44 import {PackageCreationModes} from '../package-creation/creationModes/PackageCreationModes';
45 import {PackageCreationBuilder} from '../package-creation/creationModes/PackageCreationBuilder';
46 import {PackageCreationStore} from '../package-creation/package-creation.store';
47 import {PackageCreationService} from '../package-creation/package-creation.service';
48 import {PackageCreationUtils} from '../package-creation/package-creation.utils';
49 import * as JSZip from 'jszip';
50 import {PackageCreationExtractionService} from '../package-creation/package-creation-extraction.service';
51 import {CBAPackage} from '../package-creation/mapping-models/CBAPacakge.model';
52 import {TopologyTemplate} from './model/designer.topologyTemplate.model';
53 import {ToastrService} from 'ngx-toastr';
54 import {DesignerDashboardState} from './model/designer.dashboard.state';
55
56 @Component({
57     selector: 'app-designer',
58     templateUrl: './designer.component.html',
59     styleUrls: ['./designer.component.css'],
60     encapsulation: ViewEncapsulation.None
61 })
62 export class DesignerComponent implements OnInit, OnDestroy {
63
64     controllerSideBar: boolean;
65     actionAttributesSideBar: boolean;
66     functionAttributeSidebar: boolean;
67     viewedPackage: BluePrintDetailModel = new BluePrintDetailModel();
68     customActionName: string;
69     showAction: boolean;
70     cl = 'editBar';
71
72     boardGraph: joint.dia.Graph;
73     boardPaper: joint.dia.Paper;
74
75     paletteGraph: joint.dia.Graph;
76     palettePaper: joint.dia.Paper;
77     ngUnsubscribe = new Subject();
78     opt = {tx: 100, ty: 100};
79     filesData: any = [];
80     folder: FolderNodeElement = new FolderNodeElement();
81     zipFile: JSZip = new JSZip();
82     cbaPackage: CBAPackage;
83     actions: string[] = [];
84     dataTarget: string;
85     steps: string[];
86     designerState: DesignerDashboardState;
87     currentActionName: string;
88
89     constructor(
90         private designerStore: DesignerStore,
91         private functionStore: FunctionsStore,
92         private packageCreationStore: PackageCreationStore,
93         private packageCreationUtils: PackageCreationUtils,
94         private graphUtil: GraphUtil,
95         private graphGenerator: GraphGenerator,
96         private route: ActivatedRoute,
97         private router: Router,
98         private designerService: DesignerService,
99         private packageCreationService: PackageCreationService,
100         private packageCreationExtractionService: PackageCreationExtractionService,
101         private toastService: ToastrService) {
102         this.controllerSideBar = true;
103         this.actionAttributesSideBar = false;
104         this.showAction = false;
105         this.functionAttributeSidebar = false;
106
107     }
108
109     _toggleSidebar1() {
110         this.controllerSideBar = !this.controllerSideBar;
111         if (this.controllerSideBar === false) {
112             this.cl = 'editBar2';
113         }
114         if (this.controllerSideBar === true) {
115             this.cl = 'editBar';
116         }
117     }
118
119     _toggleSidebar2() {
120         this.actionAttributesSideBar = !this.actionAttributesSideBar;
121     }
122
123     publishBluePrint() {
124         this.create();
125         this.zipFile.generateAsync({type: 'blob'})
126             .then(blob => {
127                 const formData = new FormData();
128                 formData.append('file', blob);
129                 this.designerService.publishBlueprint(formData).subscribe(res => {
130                     console.log('Package Deployed...');
131                 }, error => {
132                     console.log(error);
133                 }, () => {
134                     //  this.deployBluePrint = false;
135                 });
136             });
137     }
138
139     // private _toggleSidebar3() {
140     //   this.functionAttributeSidebar = !this.functionAttributeSidebar;
141     // }
142
143
144     /**
145      * - There is a board (main paper) that will the action and function selected from the palette
146      * itmes in this board will be used to create tosca workflow and node templates
147      * - There is also palette , whis contains all the possible functions and actions
148      * that can be dragged into the board
149      * - There is also a fly paper , which is temporarliy paper created on the fly
150      * when items is dragged from the palette- and it's deleted when the item is dropped over
151      * the board.
152      * for more info about the drag and drop algorithem used please visit the following link:
153      * https://stackoverflow.com/a/36932973/1340034
154      */
155
156     ngOnInit() {
157         this.customActionName = this.route.snapshot.paramMap.get('actionName');
158         if (this.customActionName !== '') {
159             this.showAction = true;
160         }
161         this.initializeBoard();
162         this.initializePalette();
163         this.stencilPaperEventListeners();
164         const id = this.route.snapshot.paramMap.get('id');
165         this.designerService.getPagedPackages(id).subscribe(
166             (bluePrintDetailModels) => {
167                 if (bluePrintDetailModels) {
168                     this.viewedPackage = bluePrintDetailModels[0];
169                     this.packageCreationService.downloadPackage(this.viewedPackage.artifactName + '/'
170                         + this.viewedPackage.artifactVersion)
171                         .subscribe(response => {
172                             const blob = new Blob([response], {type: 'application/octet-stream'});
173                             this.packageCreationExtractionService.extractBlobToStore(blob);
174                         });
175                 }
176             });
177         this.packageCreationStore.state$.subscribe(cba => {
178             this.cbaPackage = cba;
179             console.log(cba.templateTopology.content);
180             this.designerStore.saveSourceContent(cba.templateTopology.content);
181
182         });
183
184         /**
185          * the code to retrieve from server is commented
186          */
187         this.functionStore.state$
188             .pipe(
189                 distinctUntilChanged((a: any, b: any) => JSON.stringify(a) === JSON.stringify(b)),
190                 takeUntil(this.ngUnsubscribe))
191             .subscribe(state => {
192
193                 if (state.serverFunctions) {
194                     console.log('inside subscriotn on functions store -->', state.serverFunctions);
195                     console.log(state);
196                     // this.viewedFunctions = state.functions;
197                     const list = state.serverFunctions;
198
199                     const cells = this.graphUtil.buildPaletteGraphFromList(list);
200                     this.paletteGraph.resetCells(cells);
201
202                     let idx = 0;
203                     cells.forEach(cell => {
204                         cell.translate(5, (cell.attributes.size.height + 5) * idx++);
205                     });
206                 }
207             });
208
209         this.designerStore.state$
210             .pipe(
211                 distinctUntilChanged((a: any, b: any) => JSON.stringify(a) === JSON.stringify(b)),
212                 takeUntil(this.ngUnsubscribe))
213             .subscribe(state => {
214                 this.designerState = state;
215                 if (state.sourceContent) {
216                     console.log('inside desinger.component---> ', state);
217                     // generate graph from store objects if exist
218                     const topologtTemplate: TopologyTemplate = JSON.parse(state.sourceContent);
219                     console.log(topologtTemplate);
220                     delete state.sourceContent;
221                     this.graphGenerator.clear(this.boardGraph);
222                     this.graphGenerator.populate(topologtTemplate, this.boardGraph);
223
224                     console.log('all cells', this.boardGraph.getCells());
225                     /**
226                      * auto arrange elements in graph
227                      * https://resources.jointjs.com/docs/jointjs/v3.1/joint.html#layout.DirectedGraph
228                      */
229                     joint.layout.DirectedGraph.layout(this.boardGraph.getCells(), {
230                         dagre,
231                         graphlib,
232                         setLinkVertices: false,
233                         marginX: 10,
234                         marginY: 10,
235                         clusterPadding: {top: 100, left: 30, right: 10, bottom: 100},
236                         rankDir: 'TB'
237                     });
238                     this.actions = [];
239                     console.log('here');
240                     for (const workflowsKey in topologtTemplate.workflows) {
241                         if (workflowsKey && !this.actions.includes(workflowsKey)) {
242                             this.actions.push(workflowsKey);
243                         }
244                     }
245                 }
246             });
247
248         // action triggering
249         this.functionStore.retrieveFuntions();
250
251     }
252
253     initializePalette() {
254         if (!this.paletteGraph) {
255             this.paletteGraph = new joint.dia.Graph();
256             this.palettePaper = new joint.dia.Paper({
257                 el: $('#palette-paper'),
258                 model: this.paletteGraph,
259                 width: 318,
260                 // height: '100%',
261                 height: $('#palette-paper').height(),
262                 // background: {
263                 //   color: 'rgba(0, 255, 0, 0.3)'
264                 // },
265                 interactive: false
266                 // elements in paletter need to be fixed, please refer to flying paper concept
267             });
268         }
269     }
270
271     initializeBoard() {
272         if (!this.boardGraph) {
273             console.log('initializeBoard...');
274             this.boardGraph = new joint.dia.Graph();
275             this.boardPaper = new joint.dia.Paper({
276                 el: $('#board-paper'),
277                 model: this.boardGraph,
278                 height: 720,
279                 width: 1100,
280                 gridSize: 10,
281                 drawGrid: true,
282                 // background: {
283                 // color: 'rgba(0, 255, 0, 0.3)'
284                 // },
285                 cellViewNamespace: joint.shapes
286             });
287
288             this.boardPaper.on('all', element => {
289                 // console.log(element);
290             });
291
292             this.boardPaper.on('link:pointerdown', link => {
293                 console.log(link);
294             });
295
296             this.boardPaper.on('element:pointerdown', element => {
297                 // this.modelSelected.emit(element.model.get('model'));
298             });
299
300             this.boardPaper.on('blank:pointerclick', () => {
301                 // this.selectedModel = undefined;
302             });
303
304             this.boardGraph.on('change:position', (cell) => {
305
306                 const parentId = cell.get('parent');
307                 if (!parentId) {
308                     // this is action
309                     return;
310                 }
311
312                 const parent = this.boardGraph.getCell(parentId);
313
314                 const parentBbox = parent.getBBox();
315                 const cellBbox = cell.getBBox();
316                 if (parentBbox.containsPoint(cellBbox.origin()) &&
317                     parentBbox.containsPoint(cellBbox.topRight()) &&
318                     parentBbox.containsPoint(cellBbox.corner()) &&
319                     parentBbox.containsPoint(cellBbox.bottomLeft())) {
320
321                     // All the four corners of the child are inside
322                     // the parent area.
323                     return;
324                 }
325
326                 // Revert the child position.
327                 cell.set('position', cell.previous('position'));
328             });
329         }
330         console.log('done initializing Board...');
331     }
332
333     insertCustomActionIntoBoard() {
334         console.log('saving action to store action workflow....');
335         let actionName = this.graphUtil.generateNewActionName();
336         while (this.actions.includes(actionName)) {
337             actionName = this.graphUtil.generateNewActionName();
338         }
339         this.graphUtil.createCustomActionWithName(actionName, this.boardGraph);
340         this.designerStore.addDeclarativeWorkFlow(actionName);
341         this.actions.push(actionName);
342     }
343
344     stencilPaperEventListeners() {
345         this.palettePaper.on('cell:pointerdown', (draggedCell, pointerDownEvent, x, y) => {
346
347             $('body').append(`
348         <div id="flyPaper"
349             style="position:fixed;z-index:100;opacity:.7;pointer-event:none;background-color: transparent !important;"></div>`
350             );
351             const flyGraph = new joint.dia.Graph();
352             const flyPaper = new joint.dia.Paper({
353                 el: $('#flyPaper'),
354                 model: flyGraph,
355                 interactive: true
356             });
357             const flyShape = draggedCell.model.clone();
358             const pos = draggedCell.model.position();
359             const offset = {
360                 x: x - pos.x,
361                 y: y - pos.y
362             };
363
364             flyShape.position(0, 0);
365             flyGraph.addCell(flyShape);
366             $('#flyPaper').offset({
367                 left: pointerDownEvent.pageX - offset.x,
368                 top: pointerDownEvent.pageY - offset.y
369             });
370             $('body').on('mousemove.fly', mouseMoveEvent => {
371                 $('#flyPaper').offset({
372                     left: mouseMoveEvent.pageX - offset.x,
373                     top: mouseMoveEvent.pageY - offset.y
374                 });
375             });
376
377             $('body').on('mouseup.fly', mouseupEvent => {
378                 const mouseupX = mouseupEvent.pageX;
379                 const mouseupY = mouseupEvent.pageY;
380                 const target = this.boardPaper.$el.offset();
381                 // Dropped over paper ?
382                 if (mouseupX > target.left &&
383                     mouseupX < target.left + this.boardPaper.$el.width() &&
384                     mouseupY > target.top && y < target.top + this.boardPaper.$el.height()) {
385                     const functionType = this.graphUtil.getFunctionTypeFromPaletteFunction(flyShape);
386                     // step name is CDS realted terminology, please refer to tosca types
387                     const stepName = functionType;
388                     const functionElementForBoard = this.graphUtil.dropFunctionOverActionWithPosition(
389                         stepName, functionType,
390                         mouseupX, mouseupY,
391                         target, offset,
392                         this.boardGraph);
393
394                     const parentCell = this.graphUtil.getParent(functionElementForBoard, this.boardPaper);
395
396                     if (parentCell &&
397                         parentCell.model.attributes.type === ActionElementTypeName &&
398                         this.graphUtil.canEmpedMoreChildern(parentCell.model, this.boardGraph)) {
399
400                         if (this.graphUtil.isEmptyParent(parentCell.model)) {
401                             // first function in action
402                             const actionName = parentCell.model.attributes.attrs['#label'].text;
403                             this.designerStore.addStepToDeclarativeWorkFlow(actionName, stepName, functionType);
404                             if (functionType === 'dg-generic') {
405                                 this.designerStore.addDgGenericNodeTemplate(stepName);
406                             } else {
407                                 this.designerStore.addNodeTemplate(stepName, functionType);
408                             }
409                         } else {
410                             // second action means there was a dg-generic node before
411                             this.designerStore.addNodeTemplate(stepName, functionType);
412                             // this will fail if multiple dg-generic were added
413                             // TODO prevent multi functions of the same type inside the same action
414                             const dgGenericNode = this.graphUtil.getDgGenericChild(parentCell.model, this.boardGraph)[0];
415                             const dgGenericNodeName = this.graphUtil.getFunctionNameFromBoardFunction(dgGenericNode);
416                             this.designerStore.addDgGenericDependency(dgGenericNodeName, stepName);
417                         }
418
419
420                         // Prevent recursive embedding.
421                         if (parentCell &&
422                             parentCell.model.get('parent') !== functionElementForBoard.id) {
423                             parentCell.model.embed(functionElementForBoard);
424                         }
425                     } else {
426                         console.log('function dropped outside action or not allowed, rolling back...');
427                         alert('function dropped outside action or not allowed, rolling back...');
428                         functionElementForBoard.remove();
429                     }
430                 }
431                 $('body').off('mousemove.fly').off('mouseup.fly');
432                 // flyShape.remove();
433                 $('#flyPaper').remove();
434             });
435         });
436         console.log('done stencilPaperEventListeners()...');
437     }
438
439     ngOnDestroy() {
440         this.ngUnsubscribe.next();
441         this.ngUnsubscribe.complete();
442     }
443
444     saveBluePrint() {
445
446         FilesContent.clear();
447         let packageCreationModes: PackageCreationModes;
448         this.cbaPackage = PackageCreationModes.mapModeType(this.cbaPackage);
449         this.cbaPackage.metaData = PackageCreationModes.setEntryPoint(this.cbaPackage.metaData);
450         packageCreationModes = PackageCreationBuilder.getCreationMode(this.cbaPackage);
451         this.designerStore.state$.subscribe(state => {
452             this.cbaPackage.templateTopology.content = this.packageCreationUtils.transformToJson(state.template);
453         });
454         packageCreationModes.execute(this.cbaPackage, this.packageCreationUtils);
455         this.filesData.push(this.folder.TREE_DATA);
456         this.saveBluePrintToDataBase();
457
458     }
459
460     create() {
461         this.zipFile = new JSZip();
462         FilesContent.getMapOfFilesNamesAndContent().forEach((value, key) => {
463             this.zipFile.folder(key.split('/')[0]);
464             this.zipFile.file(key, value);
465         });
466
467     }
468
469     saveBluePrintToDataBase() {
470         this.create();
471         this.zipFile.generateAsync({type: 'blob'})
472             .then(blob => {
473                 this.packageCreationService.savePackage(blob).subscribe(
474                     bluePrintDetailModels => {
475                         this.toastService.info('success updating the package');
476                         const id = bluePrintDetailModels.toString().split('id')[1].split(':')[1].split('"')[1];
477                         this.router.navigate(['/packages/designer/' + id]);
478                         console.log('success');
479                     }, error => {
480                         this.toastService.error('error happened when editing ' + error.message);
481                         console.log('Error -' + error.message);
482                     });
483             });
484     }
485
486     openActionAttributes(customActionName: string) {
487         console.log('opening here action attributes');
488         this.currentActionName = customActionName;
489         this.actionAttributesSideBar = true;
490         this.functionAttributeSidebar = false;
491         this.designerStore.setCurrentAction(customActionName);
492         /* tslint:disable:no-string-literal */
493         this.steps = Object.keys(this.designerState.template.workflows[customActionName]['steps']);
494     }
495
496     openFunctionAttributes(customFunctionName: string) {
497         this.actionAttributesSideBar = false;
498         this.functionAttributeSidebar = true;
499         this.designerStore.setCurrentFunction(this.designerState.template.workflows[this.currentActionName]
500             ['steps'][customFunctionName]['target']);
501     }
502 }