Fixed defect CCSDK-1299
[ccsdk/cds.git] / cds-ui / client / src / app / feature-modules / blueprint / modify-template / editor / editor.component.ts
1 /*
2 ============LICENSE_START==========================================
3 ===================================================================
4 Copyright (C) 2018-19 IBM Intellectual Property. All rights reserved.
5 ===================================================================
6
7 Unless otherwise specified, all software contained herein is licensed
8 under the Apache License, Version 2.0 (the License);
9 you may not use this software except in compliance with the License.
10 You may obtain a copy of the License at
11
12     http://www.apache.org/licenses/LICENSE-2.0
13
14 Unless required by applicable law or agreed to in writing, software
15 distributed under the License is distributed on an "AS IS" BASIS,
16 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 See the License for the specific language governing permissions and
18 limitations under the License.
19 ============LICENSE_END============================================
20 */
21
22 import { Component, OnInit, ViewChild } from '@angular/core';
23 import { FlatTreeControl } from '@angular/cdk/tree';
24 import { MatTreeFlatDataSource, MatTreeFlattener } from '@angular/material/tree';
25 import { IBlueprint } from 'src/app/common/core/store/models/blueprint.model';
26 import "ace-builds/webpack-resolver";
27 import 'brace';
28 import 'brace/ext/language_tools';
29 import 'ace-builds/src-min-noconflict/snippets/html';
30 import * as JSZip from 'jszip';
31 import { saveAs } from 'file-saver';
32
33 import { IAppState } from '../../../../common/core/store/state/app.state';
34 import { Store } from '@ngrx/store';
35 import { Observable } from 'rxjs';
36 import { IBlueprintState } from 'src/app/common/core/store/models/blueprintState.model';
37 import { LoadBlueprintSuccess, SetBlueprintState } from '../../../../common/core/store/actions/blueprint.action'
38 import { ApiService } from 'src/app/common/core/services/api.service';
39 import { IMetaData } from 'src/app/common/core/store/models/metadata.model';
40 import { EditorService } from './editor.service';
41
42 interface Node {
43   name: string;
44   children?: Node[];
45   data?: any
46 }
47
48 const TREE_DATA: Node[] = [
49   {
50     name: 'Definitions',
51     children: [
52       { name: 'activation-blueprint.json' },
53       { name: 'artifacts_types.json' },
54       { name: 'data_types.json' },
55     ]
56   }
57 ];
58
59 /** Flat node with expandable and level information */
60 interface ExampleFlatNode {
61   expandable: boolean;
62   name: string;
63   level: number;
64 }
65
66
67 @Component({
68   selector: 'app-editor',
69   templateUrl: './editor.component.html',
70   styleUrls: ['./editor.component.scss']
71 })
72 export class EditorComponent implements OnInit {
73
74   @ViewChild('editor') editor;
75   blueprintdata: IBlueprint;
76   blueprint: IBlueprint;
77   bpState: Observable<IBlueprintState>;
78   text: string;
79   filesTree: any = [];
80   filesData: any = [];
81   selectedFile: string;
82   zipFolder: any;
83   blueprintName: string;
84   fileExtension: string;
85   mode: string;
86   private zipFile: JSZip = new JSZip();
87   activeNode: any;
88   selectedFolder: string;
89   activationBlueprint: string;
90   isNameTextboxEnablled: boolean = false;
91   fileAction: string;
92   filetoDelete: string;
93   currentFilePath: string = '';
94   selectedFileObj = { name: '', type: '' };
95   viewTemplateMode: boolean = false;
96   paramData: any = {
97     'capability-data': [],
98     'resourceAccumulatorResolvedData': []
99   };
100   validfile: boolean = false;
101   @ViewChild('fileInput') fileInput;
102   result: string = '';
103   private paths = [];
104   private tree;
105   private fileObject: any;
106   private tocsaMetadaData: any;
107   metadata: IMetaData;
108
109   private transformer = (node: Node, level: number) => {
110     return {
111       expandable: !!node.children && node.children.length > 0,
112       name: node.name,
113       level: level,
114     };
115   }
116
117   treeControl = new FlatTreeControl<ExampleFlatNode>(
118     node => node.level, node => node.expandable);
119
120   treeFlattener = new MatTreeFlattener(
121     this.transformer, node => node.level, node => node.expandable, node => node.children);
122
123   dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
124   artifactName: any;
125   artifactVersion: any;
126
127   constructor(private store: Store<IAppState>, private apiservice: EditorService) {
128     this.dataSource.data = TREE_DATA;
129     this.bpState = this.store.select('blueprint');
130     // this.dataSource.data = TREE_DATA;
131   }
132
133   hasChild = (_: number, node: ExampleFlatNode) => node.expandable;
134
135   ngOnInit() {
136     this.editorContent();
137     this.dataSource.data = this.filesTree;
138   }
139
140   editorContent() {
141     this.editor.setTheme("eclipse");
142     this.editor.getEditor().setOptions({
143       // enableBasicAutocompletion: true,
144       fontSize: "100%",
145       printMargin: false,
146     });
147     this.editor.getEditor().commands.addCommand({
148       name: "showOtherCompletions",
149       bindKey: "Ctrl-.",
150       exec: function (editor) {
151
152       }
153     })
154     this.bpState.subscribe(
155       blueprintdata => {
156         var blueprintState: IBlueprintState = { blueprint: blueprintdata.blueprint, isLoadSuccess: blueprintdata.isLoadSuccess, isSaveSuccess: blueprintdata.isSaveSuccess, isUpdateSuccess: blueprintdata.isUpdateSuccess };
157         this.blueprintdata = blueprintState.blueprint;
158         this.filesTree = blueprintdata.files;
159         this.filesData = blueprintdata.filesData;
160         this.dataSource.data = this.filesTree;
161         this.blueprintName = blueprintdata.name;
162         let blueprint = [];
163         for (let key in this.blueprintdata) {
164           if (this.blueprintdata.hasOwnProperty(key)) {
165             blueprint.push(this.blueprintdata[key]);
166           }
167         }
168         this.metadata = blueprintState.blueprint.metadata;
169         let metadatavalues = [];
170         for (let key in this.metadata) {
171           if (this.metadata.hasOwnProperty(key)) {
172             metadatavalues.push(this.metadata[key]);
173           }
174         }
175         this.artifactName = metadatavalues[3];
176         this.artifactVersion = metadatavalues[4];
177         this.editor.getEditor().getSession().setTabSize(2);
178         this.editor.getEditor().getSession().setUseWrapMode(true);
179         this.editor.getEditor().getSession().setValue("");
180         this.setEditorMode();
181       })
182   }
183
184   updateBlueprint() {
185     // console.log(this.blueprint);
186     // this.filesData.forEach(fileNode => {
187     //   if (this.selectedFile && fileNode.name.includes(this.blueprintName.trim()) && fileNode.name.includes(this.selectedFile.trim())) {
188     //     fileNode.data = this.text;
189     //   } else if (this.selectedFile && fileNode.name.includes(this.currentFilePath)) {
190     //     // this.selectedFile && fileNode.name.includes(this.selectedFile.trim())) {
191     //     fileNode.data = this.text;
192     //   }
193     // });
194
195     // if (this.selectedFile && this.selectedFile == this.blueprintName.trim()) {
196     //   this.blueprint = JSON.parse(this.text);
197     // } else {
198     //   this.blueprint = this.blueprintdata;
199     // }
200
201     let blueprintState = {
202       blueprint: this.blueprint,
203       name: this.blueprintName,
204       files: this.filesTree,
205       filesData: this.filesData
206     }
207     this.store.dispatch(new SetBlueprintState(blueprintState));
208     // console.log(this.text);
209   }
210
211   selectFileToView(file) {
212     if (file.name.includes('.vtl')) { this.viewTemplateMode = true; } else { this.viewTemplateMode = false; }
213     this.currentFilePath = '';
214     this.expandParents(file);
215     this.selectedFileObj.name = file.name;
216     this.selectedFileObj.type = 'file';
217     this.selectedFile = file.name;
218     this.filetoDelete = file.name;
219     this.currentFilePath = this.currentFilePath + this.selectedFile;
220     this.filesData.forEach((fileNode) => {
221       if (fileNode.name.includes(file.name)) {
222         this.text = fileNode.data;
223       }
224     })
225     this.fileExtension = this.selectedFile.substr(this.selectedFile.lastIndexOf('.') + 1);
226     // console.log(this.fileExtension);
227     this.setEditorMode();
228   }
229
230   getEnriched() {
231     this.create();
232     this.zipFile.generateAsync({ type: "blob" })
233       .then(blob => {
234         const formData = new FormData();
235         formData.append("file", blob);
236         this.apiservice.enrich("/enrich-blueprint/", formData)
237           .subscribe(
238             (response) => {
239               this.zipFile.files = {};
240               this.zipFile.loadAsync(response)
241                 .then((zip) => {
242                   if (zip) {
243                     this.buildFileViewData(zip);
244                     console.log("processed");
245                   }
246                 });
247               window.alert('Blueprint enriched successfully');
248             });
249       });
250   }
251
252
253
254   saveToBackend() {
255     this.create();
256     this.zipFile.generateAsync({ type: "blob" })
257       .then(blob => {
258         const formData = new FormData();
259         formData.append("file", blob);
260         this.apiservice.post("/create-blueprint/", formData)
261           .subscribe(
262             data => {
263               // console.log(data);
264               window.alert('Success:' + JSON.stringify(data));
265             });
266
267       });
268   }
269
270   deploy() {
271     // to do
272     this.create();
273     this.zipFile.generateAsync({ type: "blob" })
274       .then(blob => {
275         const formData = new FormData();
276         formData.append("file", blob);
277         this.apiservice.deployPost("/deploy-blueprint/", formData)
278           .subscribe(data => {
279             // console.log(data);
280             window.alert('Saved Successfully:' + JSON.stringify(data));
281           });
282
283       });
284   }
285
286   publish() {
287     this.create();
288     this.zipFile.generateAsync({ type: "blob" })
289       .then(blob => {
290         const formData = new FormData();
291         formData.append("file", blob);
292         this.apiservice.post("/publish/", formData)
293           .subscribe(data => {
294             // console.log(data);
295             window.alert('Published:' + JSON.stringify(data));
296           });
297
298       });
299
300   }
301
302   create() {
303     this.filesData.forEach((path) => {
304       let index = path.name.indexOf("/");
305       let name = path.name.slice(index + 1, path.name.length);
306       this.zipFile.file(name, path.data);
307     });
308   }
309
310   download() {
311     console.log(this.artifactName);
312     status = this.apiservice.downloadCBA("/download-blueprint/" + this.artifactName + "/" + this.artifactVersion);
313     window.alert(status);
314     // .subscribe(response => {
315     //   console.log(response);
316     //   var blob = new Blob([response], { type: 'application/zip' });
317     //   const fileName = 'CBA';
318     //   saveAs(blob, fileName);
319     // },
320     //   error => {
321     //     console.log(error);
322     //   }
323     // );
324
325     // this.create();
326     // var zipFilename = "baseconfiguration.zip";
327     // this.zipFile.generateAsync({ type: "blob" })
328     //   .then(blob => {
329     //     saveAs(blob, zipFilename);
330     //   });
331   }
332   setEditorMode() {
333     switch (this.fileExtension) {
334       case "xml":
335         this.mode = 'xml';
336         break;
337       case "py":
338         this.mode = 'python';
339         break;
340       case "kts":
341         this.mode = 'kotlin';
342         break;
343       case "txt":
344         this.mode = 'text';
345         break;
346       case "meta":
347         this.mode = 'text';
348         break;
349       case "vtl":
350         this.mode = 'velocity';
351         break;
352       default:
353         this.mode = 'json';
354     }
355   }
356
357   selectFolder(node) {
358     this.currentFilePath = '';
359     this.expandParents(node);
360     this.selectedFolder = node.name;
361     this.filetoDelete = node.name;
362     this.selectedFileObj.name = node.name;
363     this.selectedFileObj.type = 'folder';
364     this.currentFilePath = this.currentFilePath + this.selectedFolder + '/';
365   }
366
367   createFolderOrFile(name) {
368     if (name && name.srcElement.value !== null && name.srcElement.value !== '') {
369       let newFilesData: [any] = this.filesData;
370       let newFileNode = {
371         name: '',
372         data: ''
373       }
374       let newFileNode1 = {
375         name: '',
376         data: ''
377       }
378       if (this.fileAction == 'createFolder') {
379         newFileNode.name = this.currentFilePath + name.srcElement.value + '/'
380         newFileNode.data = '';
381         newFileNode1.name = this.currentFilePath + name.srcElement.value + '/README.md'
382         newFileNode1.data = name.srcElement.value + ' Folder';
383         this.filesData.push(newFileNode);
384         this.filesData.push(newFileNode1);
385       } else {
386         newFileNode.name = this.currentFilePath + name.srcElement.value;
387         newFileNode.data = '';
388         this.filesData.push(newFileNode);
389       }
390       this.arrangeTreeData(this.filesData);
391     }
392   }
393
394   findIndexForNewNode() {
395     let indexForNewNode;
396     for (let i = 0; i < this.filesData.length; i++) {
397       if (this.filesData[i].name.includes(this.selectedFolder)) {
398         indexForNewNode = i;
399       }
400     }
401     return indexForNewNode;
402   }
403
404   async buildFileViewData(zip) {
405     this.validfile = false;
406     this.paths = [];
407     for (var file in zip.files) {
408       this.fileObject = {
409         name: zip.files[file].name,
410         data: ''
411       };
412       const value = <any>await zip.files[file].async('string');
413       this.fileObject.data = value;
414       this.paths.push(this.fileObject);
415     }
416
417     if (this.paths) {
418       this.paths.forEach(path => {
419         if (path.name.includes("TOSCA.meta")) {
420           this.validfile = true
421         }
422       });
423     } else {
424       alert('Please update proper file');
425     }
426
427     if (this.validfile) {
428       this.fetchTOSACAMetadata();
429       this.filesData = this.paths;
430       this.tree = this.arrangeTreeData(this.paths);
431     } else {
432       alert('Please update proper file with TOSCA metadata');
433     }
434   }
435
436   fetchTOSACAMetadata() {
437     let toscaData = {};
438     this.paths.forEach(file => {
439       if (file.name.includes('TOSCA.meta')) {
440         let keys = file.data.split("\n");
441         keys.forEach((key) => {
442           let propertyData = key.split(':');
443           toscaData[propertyData[0]] = propertyData[1];
444         });
445       }
446     });
447     this.blueprintName = (((toscaData['Entry-Definitions']).split('/'))[1]).toString();;
448     console.log(toscaData);
449   }
450
451   arrangeTreeData(paths) {
452     const tree = [];
453
454     paths.forEach((path) => {
455
456       const pathParts = path.name.split('/');
457       let currentLevel = tree;
458
459       pathParts.forEach((part) => {
460         const existingPath = currentLevel.filter(level => level.name === part);
461
462         if (existingPath.length > 0) {
463           currentLevel = existingPath[0].children;
464         } else {
465           const newPart = {
466             name: part,
467             children: [],
468             data: path.data,
469             path: path.name
470           };
471           if (part.trim() == this.blueprintName.trim()) {
472             this.activationBlueprint = path.data;
473             newPart.data = JSON.parse(this.activationBlueprint.toString());
474             console.log('newpart', newPart);
475           }
476           if (newPart.name !== '') {
477             currentLevel.push(newPart);
478             currentLevel = newPart.children;
479           }
480         }
481       });
482     });
483     this.dataSource.data = tree;
484     this.filesTree = tree;
485     this.isNameTextboxEnablled = false;
486     this.updateBlueprint();
487   }
488
489   enableNameInputEl(action) {
490     this.fileAction = action;
491     if (action == 'createFolder' || action == 'createFile') {
492       this.isNameTextboxEnablled = true;
493     }
494   }
495
496   deleteFolderOrFile(action) {
497     for (let i = 0; i < this.filesData.length; i++) {
498       if (this.filesData[i].name.includes(this.filetoDelete.trim()) && this.filesData[i].name.includes(this.currentFilePath)) {
499         this.filesData.splice(i, 1);
500         i = i - 1;
501       }
502     }
503     this.arrangeTreeData(this.filesData);
504   }
505
506   expandParents(node) {
507     const parent = this.getParent(node);
508     this.treeControl.expand(parent);
509
510     if (parent && parent.level > 0) {
511       this.expandParents(parent);
512     }
513
514     console.log(this.currentFilePath);
515   }
516
517   getParent(node) {
518     const { treeControl } = this;
519     const currentLevel = treeControl.getLevel(node);
520
521     if (currentLevel < 1) {
522       // this.currentFilePath = this.currentFilePath + this.selectedFolder;
523       return null;
524     }
525
526     const startIndex = treeControl.dataNodes.indexOf(node) - 1;
527
528     for (let i = startIndex; i >= 0; i--) {
529       const currentNode = treeControl.dataNodes[i];
530
531       if (treeControl.getLevel(currentNode) < currentLevel) {
532         this.currentFilePath = currentNode.name + '/' + this.currentFilePath;
533         return currentNode;
534       }
535     }
536   }
537   loadConfigParams() {
538     console.log(this.currentFilePath);
539     console.log(this.selectedFile);
540     console.log(this.selectedFileObj);
541     console.log(this.selectedFolder);
542     console.log(this.text);
543
544     let parsedData = JSON.parse(this.text);
545     this.paramData.resourceAccumulatorResolvedData = parsedData['resource-accumulator-resolved-data'];
546     let i = 0;
547
548     this.paramData.resourceAccumulatorResolvedData.forEach(element => {
549       element.id = i;
550       let tempElement = element['param-value'];
551       let indexLength = tempElement.length;
552       tempElement = tempElement.slice(2, indexLength);
553       let index = tempElement.indexOf('}');
554       tempElement = this.removeItemByIndex(tempElement, index);
555       element['param-value'] = tempElement;
556       i++;
557     });
558
559   }
560
561   removeItemByIndex(paramValue, index) {
562     if (index == 0) {
563       return paramValue.slice(1)
564     } else if (index > 0) {
565       let indexLength = paramValue.length;
566       return paramValue.slice(0, index) + paramValue.slice(index + 1, indexLength);
567     } else {
568       return paramValue;
569     }
570   }
571
572   saveEditedChanges() {
573     this.filesData.forEach(fileNode => {
574       if (this.selectedFile && fileNode.name.includes(this.blueprintName.trim()) && fileNode.name.includes(this.selectedFile.trim())) {
575         fileNode.data = this.text;
576       } else if (this.selectedFile && fileNode.name.includes(this.currentFilePath)) {
577         // this.selectedFile && fileNode.name.includes(this.selectedFile.trim())) {
578         fileNode.data = this.text;
579       }
580     });
581
582     if (this.selectedFile && this.selectedFile == this.blueprintName.trim()) {
583       this.blueprint = JSON.parse(this.text);
584     } else {
585       this.blueprint = this.blueprintdata;
586     }
587
588     this.updateBlueprint();
589   }
590 }