0f6e38c38ddb7f1a43ed30288211f84619406e1b
[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 {saveAs} from 'file-saver';
51 import {PackageCreationExtractionService} from '../package-creation/package-creation-extraction.service';
52 import {CBAPackage} from '../package-creation/mapping-models/CBAPacakge.model';
53 import {TopologyTemplate} from './model/designer.topologyTemplate.model';
54 import {ToastrService} from 'ngx-toastr';
55 import {DesignerDashboardState} from './model/designer.dashboard.state';
56 import {NgxUiLoaderService} from 'ngx-ui-loader';
57
58 @Component({
59     selector: 'app-designer',
60     templateUrl: './designer.component.html',
61     styleUrls: ['./designer.component.css'],
62     encapsulation: ViewEncapsulation.None
63 })
64 export class DesignerComponent implements OnInit, OnDestroy {
65
66     controllerSideBar: boolean;
67     actionAttributesSideBar: boolean;
68     functionAttributeSidebar: boolean;
69     viewedPackage: BluePrintDetailModel = new BluePrintDetailModel();
70     customActionName: string;
71     showAction: boolean;
72     cl = 'editBar';
73
74     boardGraph: joint.dia.Graph;
75     boardPaper: joint.dia.Paper;
76
77     paletteGraph: joint.dia.Graph;
78     palettePaper: joint.dia.Paper;
79     ngUnsubscribe = new Subject();
80     opt = {tx: 100, ty: 100};
81     filesData: any = [];
82     folder: FolderNodeElement = new FolderNodeElement();
83     zipFile: JSZip = new JSZip();
84     cbaPackage: CBAPackage;
85     actions: string[] = [];
86     dataTarget: string;
87     steps: string[];
88     designerState: DesignerDashboardState;
89     currentActionName: string;
90     packageId: any;
91
92     constructor(
93         private designerStore: DesignerStore,
94         private functionStore: FunctionsStore,
95         private packageCreationStore: PackageCreationStore,
96         private packageCreationUtils: PackageCreationUtils,
97         private graphUtil: GraphUtil,
98         private graphGenerator: GraphGenerator,
99         private route: ActivatedRoute,
100         private router: Router,
101         private designerService: DesignerService,
102         private packageCreationService: PackageCreationService,
103         private packageCreationExtractionService: PackageCreationExtractionService,
104         private activatedRoute: ActivatedRoute,
105         private ngxService: NgxUiLoaderService,
106         private toastService: ToastrService) {
107         this.controllerSideBar = true;
108         this.actionAttributesSideBar = false;
109         this.showAction = false;
110         this.functionAttributeSidebar = false;
111
112     }
113
114     _toggleSidebar1() {
115         this.controllerSideBar = !this.controllerSideBar;
116         if (this.controllerSideBar === false) {
117             this.cl = 'editBar2';
118         }
119         if (this.controllerSideBar === true) {
120             this.cl = 'editBar';
121         }
122     }
123
124     _toggleSidebar2() {
125         this.actionAttributesSideBar = !this.actionAttributesSideBar;
126     }
127
128     publishBluePrint() {
129         this.create();
130         this.zipFile.generateAsync({type: 'blob'})
131             .then(blob => {
132                 const formData = new FormData();
133                 formData.append('file', blob);
134                 this.designerService.publishBlueprint(formData).subscribe(res => {
135                     this.toastService.success('Package Deployed Successfuly');
136                     console.log('Package Deployed...');
137                 }, error => {
138                     this.toastService.error(error.message, 'Package error');
139                     console.log(error);
140                 }, () => {
141                     //  this.deployBluePrint = false;
142                 });
143             });
144     }
145
146
147     /**
148      * - There is a board (main paper) that will the action and function selected from the palette
149      * itmes in this board will be used to create tosca workflow and node templates
150      * - There is also palette , whis contains all the possible functions and actions
151      * that can be dragged into the board
152      * - There is also a fly paper , which is temporarliy paper created on the fly
153      * when items is dragged from the palette- and it's deleted when the item is dropped over
154      * the board.
155      * for more info about the drag and drop algorithem used please visit the following link:
156      * https://stackoverflow.com/a/36932973/1340034
157      */
158
159     ngOnInit() {
160         // this.ngxService.start();
161         this.customActionName = this.route.snapshot.paramMap.get('actionName');
162         if (this.customActionName !== '') {
163             this.showAction = true;
164         }
165         this.initializeBoard();
166         this.initializePalette();
167         this.stencilPaperEventListeners();
168         const id = this.route.snapshot.paramMap.get('id');
169         this.designerService.getPagedPackages(id).subscribe(
170             (bluePrintDetailModels) => {
171                 if (bluePrintDetailModels) {
172                     this.viewedPackage = bluePrintDetailModels[0];
173                     this.packageCreationService.downloadPackage(this.viewedPackage.artifactName + '/'
174                         + this.viewedPackage.artifactVersion)
175                         .subscribe(response => {
176                             const blob = new Blob([response], {type: 'application/octet-stream'});
177                             this.packageCreationExtractionService.extractBlobToStore(blob);
178                         });
179                 }
180             });
181         this.packageCreationStore.state$.subscribe(cba => {
182             this.cbaPackage = cba;
183             console.log(cba.templateTopology.content);
184             this.designerStore.saveSourceContent(cba.templateTopology.content);
185
186         });
187
188         /**
189          * the code to retrieve from server is commented
190          */
191         this.functionStore.state$
192             .pipe(
193                 distinctUntilChanged((a: any, b: any) => JSON.stringify(a) === JSON.stringify(b)),
194                 takeUntil(this.ngUnsubscribe))
195             .subscribe(state => {
196
197                 if (state.serverFunctions) {
198                     console.log('inside subscriotn on functions store -->', state.serverFunctions);
199                     console.log(state);
200                     // this.viewedFunctions = state.functions;
201                     const list = state.serverFunctions;
202
203                     const cells = this.graphUtil.buildPaletteGraphFromList(list);
204                     this.paletteGraph.resetCells(cells);
205
206                     let idx = 0;
207                     cells.forEach(cell => {
208                         cell.translate(5, (cell.attributes.size.height + 5) * idx++);
209                     });
210                 }
211             });
212
213         this.designerStore.state$
214             .pipe(
215                 distinctUntilChanged((a: any, b: any) => JSON.stringify(a) === JSON.stringify(b)),
216                 takeUntil(this.ngUnsubscribe))
217             .subscribe(state => {
218                 this.designerState = state;
219                 if (state.sourceContent) {
220                     console.log('inside desinger.component---> ', state);
221                     // generate graph from store objects if exist
222                     const topologtTemplate: TopologyTemplate = JSON.parse(state.sourceContent);
223                     console.log(topologtTemplate);
224                     delete state.sourceContent;
225                     this.graphGenerator.clear(this.boardGraph);
226                     this.graphGenerator.populate(topologtTemplate, this.boardGraph);
227
228                     console.log('all cells', this.boardGraph.getCells());
229                     /**
230                      * auto arrange elements in graph
231                      * https://resources.jointjs.com/docs/jointjs/v3.1/joint.html#layout.DirectedGraph
232                      */
233                     joint.layout.DirectedGraph.layout(this.boardGraph.getCells(), {
234                         dagre,
235                         graphlib,
236                         setLinkVertices: false,
237                         marginX: 10,
238                         marginY: 10,
239                         clusterPadding: {top: 100, left: 30, right: 10, bottom: 100},
240                         rankDir: 'TB'
241                     });
242                     this.actions = [];
243                     console.log('here');
244                     for (const workflowsKey in topologtTemplate.workflows) {
245                         if (workflowsKey && !this.actions.includes(workflowsKey)) {
246                             this.actions.push(workflowsKey);
247                             /* tslint:disable:no-string-literal */
248                             if (!this.designerState.template.workflows[workflowsKey]['inputs']) {
249                                 this.designerState.template.workflows[workflowsKey]['inputs'] = {};
250                             }
251                             if (!this.designerState.template.workflows[workflowsKey]['outputs']) {
252                                 this.designerState.template.workflows[workflowsKey]['outputs'] = {};
253                             }
254                         }
255                     }
256                 }
257             });
258
259         // action triggering
260         this.functionStore.retrieveFuntions();
261         this.activatedRoute.paramMap.subscribe(res => {
262             this.packageId = res.get('id');
263         });
264
265     }
266
267     initializePalette() {
268         if (!this.paletteGraph) {
269             this.paletteGraph = new joint.dia.Graph();
270             this.palettePaper = new joint.dia.Paper({
271                 el: $('#palette-paper'),
272                 model: this.paletteGraph,
273                 width: 318,
274                 // height: '100%',
275                 height: $('#palette-paper').height(),
276                 // background: {
277                 //   color: 'rgba(0, 255, 0, 0.3)'
278                 // },
279                 interactive: false
280                 // elements in paletter need to be fixed, please refer to flying paper concept
281             });
282         }
283     }
284
285     initializeBoard() {
286         if (!this.boardGraph) {
287             console.log('initializeBoard...');
288             this.boardGraph = new joint.dia.Graph();
289             this.boardPaper = new joint.dia.Paper({
290                 el: $('#board-paper'),
291                 model: this.boardGraph,
292                 height: '100%',
293                 width: '100%',
294                 // origin: { x: 80, y: 70 },
295                 gridSize: 10, // background line sizes
296                 drawGrid: true,
297                 // background: {
298                 // color: 'rgba(0, 255, 0, 0.3)'
299                 // },
300                 cellViewNamespace: joint.shapes
301             });
302
303             this.boardPaper.on('all', element => {
304                 // console.log(element);
305             });
306
307             this.boardPaper.on('link:pointerdown', link => {
308                 console.log(link);
309             });
310
311             this.boardPaper.on('element:pointerdown', element => {
312                 // this.modelSelected.emit(element.model.get('model'));
313             });
314
315             this.boardPaper.on('blank:pointerclick', () => {
316                 // this.selectedModel = undefined;
317             });
318
319             this.boardGraph.on('change:position', (cell) => {
320
321                 const parentId = cell.get('parent');
322                 if (!parentId) {
323                     // this is action
324                     return;
325                 }
326
327                 const parent = this.boardGraph.getCell(parentId);
328
329                 const parentBbox = parent.getBBox();
330                 const cellBbox = cell.getBBox();
331                 if (parentBbox.containsPoint(cellBbox.origin()) &&
332                     parentBbox.containsPoint(cellBbox.topRight()) &&
333                     parentBbox.containsPoint(cellBbox.corner()) &&
334                     parentBbox.containsPoint(cellBbox.bottomLeft())) {
335
336                     // All the four corners of the child are inside
337                     // the parent area.
338                     return;
339                 }
340
341                 // Revert the child position.
342                 cell.set('position', cell.previous('position'));
343             });
344         }
345         console.log('done initializing Board...');
346     }
347
348     insertCustomActionIntoBoard() {
349         console.log('saving action to store action workflow....');
350         let actionName = this.graphUtil.generateNewActionName();
351         while (this.actions.includes(actionName)) {
352             actionName = this.graphUtil.generateNewActionName();
353         }
354         this.graphUtil.createCustomActionWithName(actionName, this.boardGraph);
355         this.designerStore.addDeclarativeWorkFlow(actionName);
356         this.actions.push(actionName);
357     }
358
359     stencilPaperEventListeners() {
360         this.palettePaper.on('cell:pointerdown', (draggedCell, pointerDownEvent, x, y) => {
361
362             $('body').append(`
363         <div id="flyPaper"
364             style="position:fixed;z-index:100;opacity:.7;pointer-event:none;background-color: transparent !important;"></div>`
365             );
366             const flyGraph = new joint.dia.Graph();
367             const flyPaper = new joint.dia.Paper({
368                 el: $('#flyPaper'),
369                 model: flyGraph,
370                 interactive: true
371             });
372             const flyShape = draggedCell.model.clone();
373             const pos = draggedCell.model.position();
374             const offset = {
375                 x: x - pos.x,
376                 y: y - pos.y
377             };
378
379             flyShape.position(0, 0);
380             flyGraph.addCell(flyShape);
381             $('#flyPaper').offset({
382                 left: pointerDownEvent.pageX - offset.x,
383                 top: pointerDownEvent.pageY - offset.y
384             });
385             $('body').on('mousemove.fly', mouseMoveEvent => {
386                 $('#flyPaper').offset({
387                     left: mouseMoveEvent.pageX - offset.x,
388                     top: mouseMoveEvent.pageY - offset.y
389                 });
390             });
391
392             $('body').on('mouseup.fly', mouseupEvent => {
393                 const mouseupX = mouseupEvent.pageX;
394                 const mouseupY = mouseupEvent.pageY;
395                 const target = this.boardPaper.$el.offset();
396                 // Dropped over paper ?
397                 if (mouseupX > target.left &&
398                     mouseupX < target.left + this.boardPaper.$el.width() &&
399                     mouseupY > target.top && y < target.top + this.boardPaper.$el.height()) {
400                     const functionType = this.graphUtil.getFunctionTypeFromPaletteFunction(flyShape);
401                     // step name is CDS realted terminology, please refer to tosca types
402                     const stepName = functionType;
403                     const functionElementForBoard = this.graphUtil.dropFunctionOverActionWithPosition(
404                         stepName, functionType,
405                         mouseupX, mouseupY,
406                         target, offset,
407                         this.boardGraph);
408
409                     const parentCell = this.graphUtil.getParent(functionElementForBoard, this.boardPaper);
410
411                     if (parentCell &&
412                         parentCell.model.attributes.type === ActionElementTypeName &&
413                         this.graphUtil.canEmpedMoreChildern(parentCell.model, this.boardGraph)) {
414
415                         if (this.graphUtil.isEmptyParent(parentCell.model)) {
416                             // first function in action
417                             const actionName = parentCell.model.attributes.attrs['#label'].text;
418                             this.designerStore.addStepToDeclarativeWorkFlow(actionName, stepName, functionType);
419                             if (functionType === 'dg-generic') {
420                                 this.designerStore.addDgGenericNodeTemplate(stepName);
421                             } else {
422                                 this.designerStore.addNodeTemplate(stepName, functionType);
423                             }
424                         } else {
425                             // second action means there was a dg-generic node before
426                             this.designerStore.addNodeTemplate(stepName, functionType);
427                             // this will fail if multiple dg-generic were added
428                             // TODO prevent multi functions of the same type inside the same action
429                             const dgGenericNode = this.graphUtil.getDgGenericChild(parentCell.model, this.boardGraph)[0];
430                             const dgGenericNodeName = this.graphUtil.getFunctionNameFromBoardFunction(dgGenericNode);
431                             this.designerStore.addDgGenericDependency(dgGenericNodeName, stepName);
432                         }
433
434
435                         // Prevent recursive embedding.
436                         if (parentCell &&
437                             parentCell.model.get('parent') !== functionElementForBoard.id) {
438                             parentCell.model.embed(functionElementForBoard);
439                         }
440                     } else {
441                         console.log('function dropped outside action or not allowed, rolling back...');
442                         alert('function dropped outside action or not allowed, rolling back...');
443                         functionElementForBoard.remove();
444                     }
445                 }
446                 $('body').off('mousemove.fly').off('mouseup.fly');
447                 // flyShape.remove();
448                 $('#flyPaper').remove();
449             });
450         });
451         console.log('done stencilPaperEventListeners()...');
452     }
453
454     ngOnDestroy() {
455         this.ngUnsubscribe.next();
456         this.ngUnsubscribe.complete();
457     }
458
459     saveBluePrint() {
460         this.ngxService.start();
461         FilesContent.clear();
462         let packageCreationModes: PackageCreationModes;
463         this.cbaPackage = PackageCreationModes.mapModeType(this.cbaPackage);
464         this.cbaPackage.metaData = PackageCreationModes.setEntryPoint(this.cbaPackage.metaData);
465         packageCreationModes = PackageCreationBuilder.getCreationMode(this.cbaPackage);
466         this.designerStore.state$.subscribe(state => {
467             this.cbaPackage.templateTopology.content = this.packageCreationUtils.transformToJson(state.template);
468         });
469         packageCreationModes.execute(this.cbaPackage, this.packageCreationUtils);
470         this.filesData.push(this.folder.TREE_DATA);
471         this.saveBluePrintToDataBase();
472
473     }
474
475     enrichBluePrint() {
476         this.ngxService.start();
477         this.packageCreationStore.addTopologyTemplate(this.cbaPackage.templateTopology);
478         this.formTreeData();
479         this.enrichPackage();
480         this.designerStore.clear();
481         this.packageCreationStore.clear();
482     }
483
484     private formTreeData() {
485         FilesContent.clear();
486         let packageCreationModes: PackageCreationModes;
487         this.cbaPackage = PackageCreationModes.mapModeType(this.cbaPackage);
488         this.cbaPackage.metaData = PackageCreationModes.setEntryPoint(this.cbaPackage.metaData);
489         packageCreationModes = PackageCreationBuilder.getCreationMode(this.cbaPackage);
490         packageCreationModes.execute(this.cbaPackage, this.packageCreationUtils);
491         this.filesData.push(this.folder.TREE_DATA);
492     }
493
494     private enrichPackage() {
495         this.create();
496         this.zipFile.generateAsync({type: 'blob'})
497             .then(blob => {
498                 this.packageCreationService.enrichAndDeployPackage(blob).subscribe(response => {
499                     // this.packageCreationService.enrichPackage(blob).subscribe(response => {
500                     console.log('success');
501                     const blobInfo = new Blob([response], {type: 'application/octet-stream'});
502                     this.packageCreationStore.clear();
503                     this.packageCreationExtractionService.extractBlobToStore(blobInfo);
504                     this.toastService.success('Enriched & Deployed successfully ');
505                 }, err => {
506                     console.log(err);
507                     this.toastService.error(err.message, 'Enrich Failed');
508                     this.ngxService.stop();
509                 }, () => {
510                     this.ngxService.stop();
511                 });
512             }, error => {
513                 this.toastService.error(error.mesasge, 'Error occurs during enrichment process');
514                 console.error('Error -' + error.message);
515             }, () => {
516                 this.ngxService.stop();
517             });
518     }
519
520     create() {
521         this.zipFile = new JSZip();
522         FilesContent.getMapOfFilesNamesAndContent().forEach((value, key) => {
523             this.zipFile.folder(key.split('/')[0]);
524             this.zipFile.file(key, value);
525         });
526
527     }
528
529     saveBluePrintToDataBase() {
530         this.create();
531         this.zipFile.generateAsync({type: 'blob'})
532             .then(blob => {
533                     this.packageCreationService.savePackage(blob).subscribe(
534                         bluePrintDetailModels => {
535                             this.toastService.success('Package is successfully updated ');
536                             const id = bluePrintDetailModels.toString().split('id')[1].split(':')[1].split('"')[1];
537                             this.router.navigate(['/packages/designer/' + id]);
538                             console.log('success');
539                         }, error => {
540                             this.toastService.error('Error occured when editing ' + error.message);
541                             console.log('Error -' + error.message);
542                         }, () => {
543                             this.ngxService.stop();
544                         });
545                 }, err => {
546                 },
547                 () => {
548                     this.ngxService.stop();
549                 });
550     }
551
552     openActionAttributes(customActionName: string) {
553         console.log('opening here action attributes');
554         this.currentActionName = customActionName;
555         this.actionAttributesSideBar = true;
556         this.functionAttributeSidebar = false;
557         this.designerStore.setCurrentAction(customActionName);
558         /* tslint:disable:no-string-literal */
559         this.steps = Object.keys(this.designerState.template.workflows[customActionName]['steps']);
560     }
561
562     openFunctionAttributes(customFunctionName: string) {
563         // console.log(customFunctionName);
564         this.actionAttributesSideBar = false;
565         this.functionAttributeSidebar = true;
566         // console.log(this.designerState.template.workflows[this.currentActionName]
567         // ['steps'][customFunctionName]['target']);
568         this.designerStore.setCurrentFunction(this.designerState.template.workflows[this.currentActionName]
569             ['steps'][customFunctionName]['target']);
570     }
571
572     getTarget(stepname) {
573         return this.designerState.template.workflows[this.currentActionName]
574             ['steps'][stepname]['target'];
575     }
576
577     downloadPackage() {
578
579         this.ngxService.start();
580         this.packageCreationService.downloadPackage(this.viewedPackage.artifactName + '/'
581             + this.viewedPackage.artifactVersion).subscribe(response => {
582             const blob = new Blob([response], {type: 'application/octet-stream'});
583             saveAs(blob, this.viewedPackage.artifactName + '-' + this.viewedPackage.artifactVersion + '-CBA.zip');
584
585         }, err => {
586             this.toastService.error('Package ' + this.viewedPackage.artifactName + 'has error when downloading' +
587                 err.message);
588             this.ngxService.stop();
589         }, () => {
590             this.toastService.success('Package ' + this.viewedPackage.artifactName + 'has been downloaded successfully');
591             this.ngxService.stop();
592         });
593     }
594 }