2 * # ============LICENSE_START=======================================================
3 * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved.
4 * # ================================================================================
5 * # Licensed under the Apache License, Version 2.0 (the "License");
6 * # you may not use this file except in compliance with the License.
7 * # You may obtain a copy of the License at
9 * # http://www.apache.org/licenses/LICENSE-2.0
11 * # Unless required by applicable law or agreed to in writing, software
12 * # distributed under the License is distributed on an "AS IS" BASIS,
13 * # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * # See the License for the specific language governing permissions and
15 * # limitations under the License.
16 * # ============LICENSE_END=========================================================
19 import { Component, OnInit, ViewChild, ElementRef, Input, EventEmitter, Output, ChangeDetectorRef } from '@angular/core';
20 import { Table } from 'primeng/table';
21 import { MessageService } from 'primeng/api';
22 import { trigger, state, style, transition, animate } from '@angular/animations';
23 import * as saveAs from 'file-saver';
24 import * as JSZip from 'jszip';
25 import { AuthService } from '../services/auth.service';
26 import { DatePipe } from '@angular/common';
27 import { DeploymentArtifactService } from '../services/deployment-artifact.service';
28 import { Ng4LoadingSpinnerService } from 'ng4-loading-spinner';
29 import { Toast } from 'primeng/toast'
30 import { ActivatedRoute } from '@angular/router';
31 import { DownloadService } from '../services/download.service';
34 selector: 'app-blueprints',
35 templateUrl: './blueprints.component.html',
36 styleUrls: ['./blueprints.component.css'],
38 trigger('rowExpansionTrigger', [
40 transform: 'translateX(-10%)',
43 state('active', style({
44 transform: 'translateX(0)',
47 transition('* <=> *', animate('400ms cubic-bezier(0.86, 0, 0.07, 1)'))
50 providers: [DatePipe, MessageService]
52 export class BlueprintsComponent implements OnInit {
53 @ViewChild(Table, { static: false }) dt: Table;
54 @ViewChild(Toast, { static: false }) toast: Toast;
56 /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. **/
57 bpElements: BlueprintElement[] = [];
59 { field: 'instanceName', header: 'Instance Name' },
60 { field: 'instanceRelease', header: 'Instance Release', width: '7%' },
61 { field: 'tag', header: 'Tag' },
62 { field: 'type', header: 'Type', width: '7%' },
63 { field: 'version', header: 'Version', width: '6%' },
64 { field: 'status', header: 'Status', width: '125px' }];
65 states: {field: string, label: string}[] = [];
68 downloadItems: { label: string; command: () => void; }[];
70 showBpContentDialog: boolean = false;
71 selectedBPs: BlueprintElement[] = [];
72 // Hides the BP list until the rows are retrieved and filtered
74 // These 2 fields are passed from MS Instance to filter the BP list
79 filteredRelease: string;
82 filteredVersion: string;
83 filteredStatus: string;
85 constructor(private change: ChangeDetectorRef, private messageService: MessageService, private authService: AuthService,
86 private datePipe: DatePipe, private bpApis: DeploymentArtifactService, private spinnerService: Ng4LoadingSpinnerService,
87 private route: ActivatedRoute, private downloadService: DownloadService) { }
91 this.username = this.authService.getUser().username;
96 this.change.markForCheck();
98 this.route.queryParams.subscribe((params) => {
99 this.filteredTag = params['tag'];
100 this.filteredRelease = params['release']});
103 //gets statuses for status updates
106 this.bpApis.getStatuses().subscribe((response) => {this.setMenuStates(response)})
109 //fills actions menu with states
110 setMenuStates(states){
111 for(let item of states){
119 canDelete: boolean = false;
120 canDownload: boolean = false;
121 canUpdate: boolean = false;
122 deleteTooltip: string;
124 if(this.selectedBPs.length > 0){
125 this.canDownload = true;
126 this.canUpdate = true;
128 for(let item of this.selectedBPs){
129 if (item.status !== 'IN_DEV' && item.status !== 'NOT_NEEDED' && item.status !== 'DEV_COMPLETE'){
130 this.canDelete = false;
131 this.deleteTooltip = 'Only blueprints that are in a status of "In Dev", "Not Needed" or "Dev Complete" can be deleted'
134 this.canDelete = true;
139 this.canDownload = false;
140 this.canUpdate = false;
141 this.canDelete = false;
142 this.deleteTooltip = 'No Blueprints Selected'
146 updateStateTo: string = ''; //selected state to update blueprint to
147 //checks if there are different releases/statuses selected
148 updateSelectedStatusesCheck(state){
149 this.updateStateTo = state.field
150 let multipleStates: boolean = false
151 let multipleReleases: boolean = false
152 let firstStatus = this.selectedBPs[0]['status']
153 let firstRelease = this.selectedBPs[0]['instanceRelease']
155 for(let bp of this.selectedBPs){
156 if(bp.instanceRelease !== firstRelease){
157 multipleReleases = true
159 if (bp.status !== firstStatus) {
160 multipleStates = true
164 if(multipleReleases && multipleStates){
165 this.messageService.add({ key: 'confirmToast', sticky: true, severity: 'warn', summary: 'Are you sure?', detail: 'You are about to update blueprints for different releases and statuses. Confirm to proceed.' });
166 } else if (multipleReleases && !multipleStates) {
167 this.messageService.add({ key: 'confirmToast', sticky: true, severity: 'warn', summary: 'Are you sure?', detail: 'You are about to update blueprints for different releases. Confirm to proceed.' });
168 } else if (!multipleReleases && multipleStates) {
169 this.messageService.add({ key: 'confirmToast', sticky: true, severity: 'warn', summary: 'Are you sure?', detail: 'You are about to update blueprints for different statuses. Confirm to proceed.' });
170 } else if (!multipleReleases && !multipleStates){
171 this.updateSelectedStatuses()
175 this.messageService.clear('confirmToast')
176 this.updateSelectedStatuses()
179 this.messageService.clear('confirmToast')
182 /* * * * Update status for multiple blueprints * * * */
183 successfulStatusUpdates: number = 0 //keeps track of how many status updates were successful
184 selectionLength: number = 0 //length of array of blueprints with different statuses than update choice
185 statusUpdateCount: number = 0 //keeps track of how many api calls have been made throughout a loop
186 statusUpdateErrors: string[] = [] //keeps list of errors
187 updateSelectedStatuses(){
188 this.successfulStatusUpdates = 0
189 this.statusUpdateErrors = []
190 this.statusUpdateCount = 0
192 let bpsToUpdate = this.selectedBPs.filter(bp => bp.status !== this.updateStateTo) //array of blueprints with different statuses than update choice
193 this.selectionLength = bpsToUpdate.length;
195 if (this.selectionLength === 0) { this.selectedBPs = [] } else {
196 this.spinnerService.show();
197 this.updateState(this.updateStateTo, bpsToUpdate, true)
201 /* * * * Update Statuses * * * */
202 //state is the state to update to
203 //data is the bp data from selection
204 //multiple is whether updates were called for single blueprint or multiple selected blueprints
205 updateState(state, data, multiple){
206 //single status update
208 this.bpApis.patchBlueprintStatus(state.field, data['id']).subscribe(
209 (response: string) => {
210 data.status = state.field
211 this.messageService.add({ key: 'statusUpdate', severity: 'success', summary: 'Status Updated' });
213 this.statusUpdatesResponseHandler(errResponse, false)
218 //multiple status updates
221 for (let bp of data) {
222 this.bpApis.patchBlueprintStatus(this.updateStateTo, bp.id).subscribe(
223 (response: string) => {
224 bp.status = this.updateStateTo
225 this.statusUpdatesResponseHandler(null, true)
227 this.statusUpdatesResponseHandler(errResponse, true)
234 function timeout(ms) {
235 return new Promise(resolve => setTimeout(resolve, ms));
240 /* * * * Handles errors and messages for status updates * * * */
241 statusUpdatesResponseHandler(response, multiple){
243 if(response !== null){
244 if (response.error.message.includes('Only 1 blueprint can be in the DEV_COMPLETE state.')) {
245 let message = response.error.message.replace('Only 1 blueprint can be in the DEV_COMPLETE state. ', '\n\nOnly 1 blueprint can be in the DEV_COMPLETE state.\n')
246 this.messageService.add({ key: 'statusUpdate', severity: 'error', summary: 'Status Not Updated', detail: message, sticky: true });
248 this.messageService.add({ key: 'statusUpdate', severity: 'error', summary: 'Error Message', detail: response.error.message, sticky: true });
254 this.statusUpdateCount++
255 if (response === null) {
256 this.successfulStatusUpdates++
258 if (response.error.message.includes('Only 1 blueprint can be in the DEV_COMPLETE state.')) {
259 let error = response.error.message.split('Only 1 blueprint can be in the DEV_COMPLETE state.')[0]
260 this.statusUpdateErrors.push(error)
262 this.messageService.add({ key: 'statusUpdate', severity: 'error', summary: 'Error Message', detail: response.error.message, sticky: true });
266 if (this.statusUpdateCount === this.selectionLength) {
267 if (this.successfulStatusUpdates > 0) {
268 this.messageService.add({ key: 'statusUpdate', severity: 'success', summary: `(${this.successfulStatusUpdates} of ${this.selectionLength}) Statuses Updated`, life: 5000 });
270 if (this.statusUpdateErrors.length > 0) {
271 let message: string = ''
272 for (let elem of this.statusUpdateErrors) {
273 message += '- ' + elem + '\n'
275 message += '\nOnly 1 blueprint can be in the DEV_COMPLETE state.\nChange the current DEV_COMPLETE blueprint to NOT_NEEDED or IN_DEV before changing another to DEV_COMPLETE.'
276 this.messageService.add({ key: 'statusUpdate', severity: 'error', summary: 'Statuses Not Updated', detail: message, sticky: true });
278 this.spinnerService.hide()
279 this.selectedBPs = []
285 deleteSingle: boolean = false;
287 rowIndexToDeleteFiltered;
288 warnDeleteBlueprint(data){
290 this.deleteSingle = true;
291 this.rowIndexToDeleteFiltered = this.filteredRows.map(function (x) { return x.id; }).indexOf(data['id']);
292 this.rowIndexToDelete = this.bpElements.map(function (x) { return x.id; }).indexOf(data['id']);
293 this.bpToDelete = data;
294 this.messageService.add({ key: 'confirmDeleteToast', sticky: true, severity: 'warn', summary: 'Are you sure?', detail: `- ${data.instanceName} (v${data.version}) for ${data.instanceRelease}` });
296 this.deleteSingle = false;
297 this.selectionLength = this.selectedBPs.length;
298 let warnMessage: string = ''
299 for(let item of this.selectedBPs){
300 warnMessage += `- ${item.instanceName} (v${item.version}) for ${item.instanceRelease}\n`
302 this.messageService.add({ key: 'confirmDeleteToast', sticky: true, severity: 'warn', summary: 'Are you sure?', detail: warnMessage });
308 this.messageService.clear('confirmDeleteToast')
310 if (this.filteredName !== '' || this.filteredRelease !== '' || this.filteredTag !== '' || this.filteredType !== '' || this.filteredVersion !== '' || this.filteredStatus !== ''){
311 this.resetFilter = true;
312 } else {this.resetFilter = false}
314 if(this.deleteSingle){
315 this.bpApis.deleteBlueprint(this.bpToDelete['id']).subscribe(response => {
316 this.checkBpWasSelected(this.bpToDelete['id'])
317 this.bpElements.splice(this.rowIndexToDelete, 1)
318 if (this.resetFilter) {
321 this.messageService.add({ key: 'bpDeleteResponse', severity: 'success', summary: 'Success Message', detail: 'Deployment Artifact Deleted' });
323 this.messageService.add({ key: 'bpDeleteResponse', severity: 'error', summary: 'Error Message', detail: error.error.message });
326 for(let item of this.selectedBPs){
327 this.bpApis.deleteBlueprint(item.id).subscribe(response => {
328 this.deleteResponseHandler(true, item.id)
330 this.messageService.add({ key: 'bpDeleteResponse', severity: 'error', summary: 'Error Message', detail: error.error.message });
336 this.messageService.clear('confirmDeleteToast')
339 checkBpWasSelected(id){
340 if(this.selectedBPs.length > 0){
341 for(let item of this.selectedBPs){
343 let indexToDelete = this.selectedBPs.map(function (x) { return x.id; }).indexOf(item['id']);
344 this.selectedBPs.splice(indexToDelete, 1)
350 bpsToDelete: string[] = [];
352 deleteResponseHandler(success, bpToDeleteId){
355 this.bpsToDelete.push(bpToDeleteId)
357 if(this.deleteBpCount === this.selectionLength){
358 for(let item of this.bpsToDelete){
360 let indexToDelete = this.bpElements.map(function (x) { return x.id; }).indexOf(item);
361 this.bpElements.splice(indexToDelete, 1)
364 if(this.resetFilter){
368 this.selectedBPs = [];
369 this.bpsToDelete = [];
370 this.deleteBpCount = 0;
371 this.messageService.add({ key: 'bpDeleteResponse', severity: 'success', summary: 'Success Message', detail: 'Deployment Artifacts Deleted' });
376 let filters: {field: string, value: string}[] = [];
377 filters.push({field: 'instanceName', value: this.filteredName})
378 filters.push({ field: 'instanceRelease', value: this.filteredRelease })
379 filters.push({ field: 'tag', value: this.filteredTag })
380 filters.push({ field: 'type', value: this.filteredType })
381 filters.push({ field: 'version', value: this.filteredVersion })
382 filters.push({ field: 'status', value: this.filteredStatus })
384 for(let item of filters){
385 this.dt.filter(item.value, item.field, 'contains')
389 /* * * * Gets all blueprints * * * */
391 this.spinnerService.show();
392 this.bpElements = [];
393 this.columns = this.cols.map(col => ({ title: col.header, dataKey: col.field }));
395 this.visible = "hidden";
397 this.bpApis.getAllBlueprints()
398 .subscribe((data: any[]) => {
404 /* * * * Checks when table is filtered and stores filtered data in new object to be downloaded when download button is clicked * * * */
405 onTableFiltered(values) {
407 this.filteredRows = values;
409 this.filteredRows = this.bpElements
413 /* * * * Download table as excel file * * * */
414 exportTable(exportTo) {
415 let downloadElements: any[] = []
417 for (let row of this.filteredRows) {
420 if (exportTo === "excel") {
421 if (row.metadata.labels !== undefined && row.metadata.labels !== null ) {
422 labels = row.metadata.labels.join(",")
425 labels = row.metadata.labels
428 if (row.metadata.notes !== null && row.metadata.notes !== undefined && row.metadata.notes !== '') {
429 notes = encodeURI(row.metadata.notes).replace(/%20/g, " ").replace(/%0A/g, "\\n")
432 downloadElements.push({
433 Instance_Name: row.instanceName,
434 Instance_Release: row.instanceRelease,
437 Version: row.version,
439 Created_By: row.metadata.createdBy,
440 Created_On: row.metadata.createdOn,
441 Updated_By: row.metadata.updatedBy,
442 Updated_On: row.metadata.updatedOn,
443 Failure_Reason: row.metadata.failureReason,
451 if (exportTo === "csv") {
469 this.downloadService.exportTableData(exportTo, downloadElements, csvHeaders)
472 /* * * * Fills object with blueprint data to be used to fill table * * * */
474 let fileName: string;
478 for (let elem of data) {
479 fileName = elem.fileName;
480 if(fileName.includes('docker')){
482 if(fileName.includes('-docker')){
483 tag = fileName.split('-docker')[0]
484 } else if (fileName.includes('_docker')){
485 tag = fileName.split('_docker')[0]
487 } else if (fileName.includes('k8s')){
489 if (fileName.includes('-k8s')) {
490 tag = fileName.split('-k8s')[0]
491 } else if (fileName.includes('_k8s')) {
492 tag = fileName.split('_k8s')[0]
496 //create temporary bp element to push to array of blueprints
497 var tempBpElement: BlueprintElement = {
498 instanceId: elem.msInstanceInfo.id,
499 instanceName: elem.msInstanceInfo.name,
500 instanceRelease: elem.msInstanceInfo.release,
502 version: elem.version,
503 content: elem.content,
509 failureReason: elem.metadata.failureReason,
510 notes: elem.metadata.notes,
511 labels: elem.metadata.labels,
512 createdBy: elem.metadata.createdBy,
513 createdOn: this.datePipe.transform(elem.metadata.createdOn, 'MM-dd-yyyy HH:mm'),
514 updatedBy: elem.metadata.updatedBy,
515 updatedOn: this.datePipe.transform(elem.metadata.updatedOn, 'MM-dd-yyyy HH:mm')
518 id: elem.specificationInfo.id
522 this.bpElements.push(tempBpElement)
524 this.bpElements.reverse();
525 this.filteredRows = this.bpElements;
529 this.visible = "visible";
530 this.spinnerService.hide();
533 /* * * * Define content to show in bp view dialog pop up * * * */
534 BpContentToView: string;
536 this.BpFileNameForDownload = `${data['tag']}_${data['type']}_${data['instanceRelease']}_${data['version']}`
537 this.BpContentToView = data['content']
538 this.showBpContentDialog = true
541 /* * * * Download single blueprint * * * */
542 BpFileNameForDownload: string;
544 let file = new Blob([this.BpContentToView], { type: 'text;charset=utf-8' });
545 let name: string = this.BpFileNameForDownload + '.yaml'
549 /* * * * Download selected blueprints * * * */
550 downloadSelectedBps() {
551 let canDownloadBps: boolean = true;
553 //checks if blueprints for multiple releases are selected
554 let selectedBpRelease: string = this.selectedBPs[0]['instanceRelease'];
555 for (let bp in this.selectedBPs) {
556 if (this.selectedBPs[bp]['instanceRelease'] !== selectedBpRelease) {
557 canDownloadBps = false
562 //downloads blueprints to zip file if all selected blueprints are for one release
563 if (canDownloadBps) {
564 var zip = new JSZip();
565 for (var i in this.selectedBPs) {
566 zip.file(`${this.selectedBPs[i]['tag']}_${this.selectedBPs[i]['type']}_${this.selectedBPs[i]['instanceRelease']}_${this.selectedBPs[i]['version']}.yaml`, this.selectedBPs[i]['content'])
568 zip.generateAsync({ type: "blob" }).then(function (content) {
569 saveAs(content, 'Blueprints.zip');
572 this.messageService.add({ key: 'multipleBpReleasesSelected', severity: 'error', summary: 'Error Message', detail: "Cannot download blueprints for different releases" });
575 this.selectedBPs = []
579 export interface BlueprintElement{
582 instanceRelease: string
591 failureReason: string