summaryrefslogtreecommitdiffstats
path: root/mod2/ui/src/app/blueprints/blueprints.component.ts
diff options
context:
space:
mode:
Diffstat (limited to 'mod2/ui/src/app/blueprints/blueprints.component.ts')
-rw-r--r--mod2/ui/src/app/blueprints/blueprints.component.ts602
1 files changed, 602 insertions, 0 deletions
diff --git a/mod2/ui/src/app/blueprints/blueprints.component.ts b/mod2/ui/src/app/blueprints/blueprints.component.ts
new file mode 100644
index 0000000..b4a7e73
--- /dev/null
+++ b/mod2/ui/src/app/blueprints/blueprints.component.ts
@@ -0,0 +1,602 @@
+/*
+ * # ============LICENSE_START=======================================================
+ * # Copyright (c) 2020 AT&T Intellectual Property. All rights reserved.
+ * # ================================================================================
+ * # Licensed under the Apache License, Version 2.0 (the "License");
+ * # you may not use this file except in compliance with the License.
+ * # You may obtain a copy of the License at
+ * #
+ * # http://www.apache.org/licenses/LICENSE-2.0
+ * #
+ * # Unless required by applicable law or agreed to in writing, software
+ * # distributed under the License is distributed on an "AS IS" BASIS,
+ * # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * # See the License for the specific language governing permissions and
+ * # limitations under the License.
+ * # ============LICENSE_END=========================================================
+ */
+
+import { Component, OnInit, ViewChild, ElementRef, Input, EventEmitter, Output, ChangeDetectorRef } from '@angular/core';
+import { Table } from 'primeng/table';
+import { MessageService } from 'primeng/api';
+import { trigger, state, style, transition, animate } from '@angular/animations';
+import * as saveAs from 'file-saver';
+import * as JSZip from 'jszip';
+import { AuthService } from '../services/auth.service';
+import { DatePipe } from '@angular/common';
+import { DeploymentArtifactService } from '../services/deployment-artifact.service';
+import { Ng4LoadingSpinnerService } from 'ng4-loading-spinner';
+import { Toast } from 'primeng/toast'
+import { ActivatedRoute } from '@angular/router';
+import { DownloadService } from '../services/download.service';
+
+@Component({
+ selector: 'app-blueprints',
+ templateUrl: './blueprints.component.html',
+ styleUrls: ['./blueprints.component.css'],
+ animations: [
+ trigger('rowExpansionTrigger', [
+ state('void', style({
+ transform: 'translateX(-10%)',
+ opacity: 0
+ })),
+ state('active', style({
+ transform: 'translateX(0)',
+ opacity: 1
+ })),
+ transition('* <=> *', animate('400ms cubic-bezier(0.86, 0, 0.07, 1)'))
+ ])
+ ],
+ providers: [DatePipe, MessageService]
+})
+export class BlueprintsComponent implements OnInit {
+ @ViewChild(Table, { static: false }) dt: Table;
+ @ViewChild(Toast, { static: false }) toast: Toast;
+
+ /** Columns displayed in the table. Columns IDs can be added, removed, or reordered. **/
+ bpElements: BlueprintElement[] = [];
+ cols: any[] = [
+ { field: 'instanceName', header: 'Instance Name' },
+ { field: 'instanceRelease', header: 'Instance Release', width: '7%' },
+ { field: 'tag', header: 'Tag' },
+ { field: 'type', header: 'Type', width: '7%' },
+ { field: 'version', header: 'Version', width: '6%' },
+ { field: 'status', header: 'Status', width: '125px' }];
+ states: {field: string, label: string}[] = [];
+ columns: any[];
+ filteredRows: any;
+ downloadItems: { label: string; command: () => void; }[];
+ username: string;
+ showBpContentDialog: boolean = false;
+ selectedBPs: BlueprintElement[] = [];
+ // Hides the BP list until the rows are retrieved and filtered
+ visible = "hidden";
+ // These 2 fields are passed from MS Instance to filter the BP list
+ tag: string;
+ release: string;
+
+ filteredName: string;
+ filteredRelease: string;
+ filteredTag: string;
+ filteredType: string;
+ filteredVersion: string;
+ filteredStatus: string;
+
+ constructor(private change: ChangeDetectorRef, private messageService: MessageService, private authService: AuthService,
+ private datePipe: DatePipe, private bpApis: DeploymentArtifactService, private spinnerService: Ng4LoadingSpinnerService,
+ private route: ActivatedRoute, private downloadService: DownloadService) { }
+
+ ngOnInit() {
+
+ this.username = this.authService.getUser().username;
+
+ this.getStates();
+ this.getAllBPs();
+
+ this.change.markForCheck();
+
+ this.route.queryParams.subscribe((params) => {
+ this.filteredTag = params['tag'];
+ this.filteredRelease = params['release']});
+ }
+
+ //gets statuses for status updates
+ getStates(){
+ this.states = []
+ this.bpApis.getStatuses().subscribe((response) => {this.setMenuStates(response)})
+ }
+
+ //fills actions menu with states
+ setMenuStates(states){
+ for(let item of states){
+ this.states.push({
+ field: item,
+ label: 'To ' + item
+ })
+ }
+ }
+
+ canDelete: boolean = false;
+ canDownload: boolean = false;
+ canUpdate: boolean = false;
+ deleteTooltip: string;
+ enableButtonCheck(){
+ if(this.selectedBPs.length > 0){
+ this.canDownload = true;
+ this.canUpdate = true;
+
+ for(let item of this.selectedBPs){
+ if (item.status !== 'IN_DEV' && item.status !== 'NOT_NEEDED' && item.status !== 'DEV_COMPLETE'){
+ this.canDelete = false;
+ this.deleteTooltip = 'Only blueprints that are in a status of "In Dev", "Not Needed" or "Dev Complete" can be deleted'
+ break
+ } else {
+ this.canDelete = true;
+ }
+ }
+
+ } else {
+ this.canDownload = false;
+ this.canUpdate = false;
+ this.canDelete = false;
+ this.deleteTooltip = 'No Blueprints Selected'
+ }
+ }
+
+ updateStateTo: string = ''; //selected state to update blueprint to
+ //checks if there are different releases/statuses selected
+ updateSelectedStatusesCheck(state){
+ this.updateStateTo = state.field
+ let multipleStates: boolean = false
+ let multipleReleases: boolean = false
+ let firstStatus = this.selectedBPs[0]['status']
+ let firstRelease = this.selectedBPs[0]['instanceRelease']
+
+ for(let bp of this.selectedBPs){
+ if(bp.instanceRelease !== firstRelease){
+ multipleReleases = true
+ }
+ if (bp.status !== firstStatus) {
+ multipleStates = true
+ }
+ }
+
+ if(multipleReleases && multipleStates){
+ 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.' });
+ } else if (multipleReleases && !multipleStates) {
+ 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.' });
+ } else if (!multipleReleases && multipleStates) {
+ 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.' });
+ } else if (!multipleReleases && !multipleStates){
+ this.updateSelectedStatuses()
+ }
+ }
+ onConfirm() {
+ this.messageService.clear('confirmToast')
+ this.updateSelectedStatuses()
+ }
+ onReject() {
+ this.messageService.clear('confirmToast')
+ }
+
+ /* * * * Update status for multiple blueprints * * * */
+ successfulStatusUpdates: number = 0 //keeps track of how many status updates were successful
+ selectionLength: number = 0 //length of array of blueprints with different statuses than update choice
+ statusUpdateCount: number = 0 //keeps track of how many api calls have been made throughout a loop
+ statusUpdateErrors: string[] = [] //keeps list of errors
+ updateSelectedStatuses(){
+ this.successfulStatusUpdates = 0
+ this.statusUpdateErrors = []
+ this.statusUpdateCount = 0
+
+ let bpsToUpdate = this.selectedBPs.filter(bp => bp.status !== this.updateStateTo) //array of blueprints with different statuses than update choice
+ this.selectionLength = bpsToUpdate.length;
+
+ if (this.selectionLength === 0) { this.selectedBPs = [] } else {
+ this.spinnerService.show();
+ this.updateState(this.updateStateTo, bpsToUpdate, true)
+ }
+ }
+
+ /* * * * Update Statuses * * * */
+ //state is the state to update to
+ //data is the bp data from selection
+ //multiple is whether updates were called for single blueprint or multiple selected blueprints
+ updateState(state, data, multiple){
+ //single status update
+ if(!multiple){
+ this.bpApis.patchBlueprintStatus(state.field, data['id']).subscribe(
+ (response: string) => {
+ data.status = state.field
+ this.messageService.add({ key: 'statusUpdate', severity: 'success', summary: 'Status Updated' });
+ }, errResponse => {
+ this.statusUpdatesResponseHandler(errResponse, false)
+ }
+ )
+ }
+
+ //multiple status updates
+ if(multiple){
+ (async () => {
+ for (let bp of data) {
+ this.bpApis.patchBlueprintStatus(this.updateStateTo, bp.id).subscribe(
+ (response: string) => {
+ bp.status = this.updateStateTo
+ this.statusUpdatesResponseHandler(null, true)
+ }, errResponse => {
+ this.statusUpdatesResponseHandler(errResponse, true)
+ }
+ )
+ await timeout(1500);
+ }
+ })();
+
+ function timeout(ms) {
+ return new Promise(resolve => setTimeout(resolve, ms));
+ }
+ }
+ }
+
+ /* * * * Handles errors and messages for status updates * * * */
+ statusUpdatesResponseHandler(response, multiple){
+ if(!multiple){
+ if(response !== null){
+ if (response.error.message.includes('Only 1 blueprint can be in the DEV_COMPLETE state.')) {
+ 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')
+ this.messageService.add({ key: 'statusUpdate', severity: 'error', summary: 'Status Not Updated', detail: message, sticky: true });
+ } else {
+ this.messageService.add({ key: 'statusUpdate', severity: 'error', summary: 'Error Message', detail: response.error.message, sticky: true });
+ }
+ }
+ }
+
+ if(multiple){
+ this.statusUpdateCount++
+ if (response === null) {
+ this.successfulStatusUpdates++
+ } else {
+ if (response.error.message.includes('Only 1 blueprint can be in the DEV_COMPLETE state.')) {
+ let error = response.error.message.split('Only 1 blueprint can be in the DEV_COMPLETE state.')[0]
+ this.statusUpdateErrors.push(error)
+ } else {
+ this.messageService.add({ key: 'statusUpdate', severity: 'error', summary: 'Error Message', detail: response.error.message, sticky: true });
+ }
+ }
+
+ if (this.statusUpdateCount === this.selectionLength) {
+ if (this.successfulStatusUpdates > 0) {
+ this.messageService.add({ key: 'statusUpdate', severity: 'success', summary: `(${this.successfulStatusUpdates} of ${this.selectionLength}) Statuses Updated`, life: 5000 });
+ }
+ if (this.statusUpdateErrors.length > 0) {
+ let message: string = ''
+ for (let elem of this.statusUpdateErrors) {
+ message += '- ' + elem + '\n'
+ }
+ 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.'
+ this.messageService.add({ key: 'statusUpdate', severity: 'error', summary: 'Statuses Not Updated', detail: message, sticky: true });
+ }
+ this.spinnerService.hide()
+ this.selectedBPs = []
+ }
+ }
+ }
+
+ bpToDelete: any;
+ deleteSingle: boolean = false;
+ rowIndexToDelete;
+ rowIndexToDeleteFiltered;
+ warnDeleteBlueprint(data){
+ if(data !== null){
+ this.deleteSingle = true;
+ this.rowIndexToDeleteFiltered = this.filteredRows.map(function (x) { return x.id; }).indexOf(data['id']);
+ this.rowIndexToDelete = this.bpElements.map(function (x) { return x.id; }).indexOf(data['id']);
+ this.bpToDelete = data;
+ this.messageService.add({ key: 'confirmDeleteToast', sticky: true, severity: 'warn', summary: 'Are you sure?', detail: `- ${data.instanceName} (v${data.version}) for ${data.instanceRelease}` });
+ } else {
+ this.deleteSingle = false;
+ this.selectionLength = this.selectedBPs.length;
+ let warnMessage: string = ''
+ for(let item of this.selectedBPs){
+ warnMessage += `- ${item.instanceName} (v${item.version}) for ${item.instanceRelease}\n`
+ }
+ this.messageService.add({ key: 'confirmDeleteToast', sticky: true, severity: 'warn', summary: 'Are you sure?', detail: warnMessage });
+ }
+ }
+
+ resetFilter = false;
+ onConfirmDelete() {
+ this.messageService.clear('confirmDeleteToast')
+
+ if (this.filteredName !== '' || this.filteredRelease !== '' || this.filteredTag !== '' || this.filteredType !== '' || this.filteredVersion !== '' || this.filteredStatus !== ''){
+ this.resetFilter = true;
+ } else {this.resetFilter = false}
+
+ if(this.deleteSingle){
+ this.bpApis.deleteBlueprint(this.bpToDelete['id']).subscribe(response => {
+ this.checkBpWasSelected(this.bpToDelete['id'])
+ this.bpElements.splice(this.rowIndexToDelete, 1)
+ if (this.resetFilter) {
+ this.resetFilters()
+ }
+ this.messageService.add({ key: 'bpDeleteResponse', severity: 'success', summary: 'Success Message', detail: 'Deployment Artifact Deleted' });
+ }, error => {
+ this.messageService.add({ key: 'bpDeleteResponse', severity: 'error', summary: 'Error Message', detail: error.error.message });
+ })
+ } else {
+ for(let item of this.selectedBPs){
+ this.bpApis.deleteBlueprint(item.id).subscribe(response => {
+ this.deleteResponseHandler(true, item.id)
+ }, error => {
+ this.messageService.add({ key: 'bpDeleteResponse', severity: 'error', summary: 'Error Message', detail: error.error.message });
+ })
+ }
+ }
+ }
+ onRejectDelete() {
+ this.messageService.clear('confirmDeleteToast')
+ }
+
+ checkBpWasSelected(id){
+ if(this.selectedBPs.length > 0){
+ for(let item of this.selectedBPs){
+ if(item.id === id){
+ let indexToDelete = this.selectedBPs.map(function (x) { return x.id; }).indexOf(item['id']);
+ this.selectedBPs.splice(indexToDelete, 1)
+ }
+ }
+ }
+ }
+
+ bpsToDelete: string[] = [];
+ deleteBpCount = 0;
+ deleteResponseHandler(success, bpToDeleteId){
+ this.deleteBpCount++
+ if(success){
+ this.bpsToDelete.push(bpToDeleteId)
+ }
+ if(this.deleteBpCount === this.selectionLength){
+ for(let item of this.bpsToDelete){
+
+ let indexToDelete = this.bpElements.map(function (x) { return x.id; }).indexOf(item);
+ this.bpElements.splice(indexToDelete, 1)
+ }
+
+ if(this.resetFilter){
+ this.resetFilters()
+ }
+
+ this.selectedBPs = [];
+ this.bpsToDelete = [];
+ this.deleteBpCount = 0;
+ this.messageService.add({ key: 'bpDeleteResponse', severity: 'success', summary: 'Success Message', detail: 'Deployment Artifacts Deleted' });
+ }
+ }
+
+ resetFilters(){
+ let filters: {field: string, value: string}[] = [];
+ filters.push({field: 'instanceName', value: this.filteredName})
+ filters.push({ field: 'instanceRelease', value: this.filteredRelease })
+ filters.push({ field: 'tag', value: this.filteredTag })
+ filters.push({ field: 'type', value: this.filteredType })
+ filters.push({ field: 'version', value: this.filteredVersion })
+ filters.push({ field: 'status', value: this.filteredStatus })
+
+ for(let item of filters){
+ this.dt.filter(item.value, item.field, 'contains')
+ }
+ }
+
+ /* * * * Gets all blueprints * * * */
+ getAllBPs() {
+ this.spinnerService.show();
+ this.bpElements = [];
+ this.columns = this.cols.map(col => ({ title: col.header, dataKey: col.field }));
+
+ this.visible = "hidden";
+
+ this.bpApis.getAllBlueprints()
+ .subscribe((data: any[]) => {
+ this.fillTable(data)
+ })
+
+ }
+
+ /* * * * Checks when table is filtered and stores filtered data in new object to be downloaded when download button is clicked * * * */
+ onTableFiltered(values) {
+ if (values) {
+ this.filteredRows = values;
+ } else {
+ this.filteredRows = this.bpElements
+ }
+ }
+
+ /* * * * Download table as excel file * * * */
+ exportTable(exportTo) {
+ let downloadElements: any[] = []
+
+ for (let row of this.filteredRows) {
+ let labels;
+ let notes;
+ if (exportTo === "excel") {
+ if (row.metadata.labels !== undefined && row.metadata.labels !== null ) {
+ labels = row.metadata.labels.join(",")
+ }
+ } else {
+ labels = row.metadata.labels
+ }
+
+ if (row.metadata.notes !== null && row.metadata.notes !== undefined && row.metadata.notes !== '') {
+ notes = encodeURI(row.metadata.notes).replace(/%20/g, " ").replace(/%0A/g, "\\n")
+ }
+
+ downloadElements.push({
+ Instance_Name: row.instanceName,
+ Instance_Release: row.instanceRelease,
+ Tag: row.tag,
+ Type: row.type,
+ Version: row.version,
+ Status: row.status,
+ Created_By: row.metadata.createdBy,
+ Created_On: row.metadata.createdOn,
+ Updated_By: row.metadata.updatedBy,
+ Updated_On: row.metadata.updatedOn,
+ Failure_Reason: row.metadata.failureReason,
+ Notes: notes,
+ Labels: labels
+ })
+ }
+
+ let csvHeaders = []
+
+ if (exportTo === "csv") {
+ csvHeaders = [
+ "Instance_Name",
+ "Instance_Release",
+ "Tag",
+ "Type",
+ "Version",
+ "Status",
+ "Created_By",
+ "Created_On",
+ "Updated_By",
+ "Updated_On",
+ "Failure_Reason",
+ "Notes",
+ "Labels"];
+
+ }
+
+ this.downloadService.exportTableData(exportTo, downloadElements, csvHeaders)
+ }
+
+ /* * * * Fills object with blueprint data to be used to fill table * * * */
+ fillTable(data) {
+ let fileName: string;
+ let tag: string;
+ let type: string;
+
+ for (let elem of data) {
+ fileName = elem.fileName;
+ if(fileName.includes('docker')){
+ type = 'docker'
+ if(fileName.includes('-docker')){
+ tag = fileName.split('-docker')[0]
+ } else if (fileName.includes('_docker')){
+ tag = fileName.split('_docker')[0]
+ }
+ } else if (fileName.includes('k8s')){
+ type = 'k8s'
+ if (fileName.includes('-k8s')) {
+ tag = fileName.split('-k8s')[0]
+ } else if (fileName.includes('_k8s')) {
+ tag = fileName.split('_k8s')[0]
+ }
+ }
+
+ //create temporary bp element to push to array of blueprints
+ var tempBpElement: BlueprintElement = {
+ instanceId: elem.msInstanceInfo.id,
+ instanceName: elem.msInstanceInfo.name,
+ instanceRelease: elem.msInstanceInfo.release,
+ id: elem.id,
+ version: elem.version,
+ content: elem.content,
+ status: elem.status,
+ fileName: fileName,
+ tag: tag,
+ type: type,
+ metadata: {
+ failureReason: elem.metadata.failureReason,
+ notes: elem.metadata.notes,
+ labels: elem.metadata.labels,
+ createdBy: elem.metadata.createdBy,
+ createdOn: this.datePipe.transform(elem.metadata.createdOn, 'MM-dd-yyyy HH:mm'),
+ updatedBy: elem.metadata.updatedBy,
+ updatedOn: this.datePipe.transform(elem.metadata.updatedOn, 'MM-dd-yyyy HH:mm')
+ },
+ specification: {
+ id: elem.specificationInfo.id
+ }
+ }
+
+ this.bpElements.push(tempBpElement)
+ }
+ this.bpElements.reverse();
+ this.filteredRows = this.bpElements;
+
+ this.resetFilters();
+
+ this.visible = "visible";
+ this.spinnerService.hide();
+ }
+
+ /* * * * Define content to show in bp view dialog pop up * * * */
+ BpContentToView: string;
+ viewBpContent(data){
+ this.BpFileNameForDownload = `${data['tag']}_${data['type']}_${data['instanceRelease']}_${data['version']}`
+ this.BpContentToView = data['content']
+ this.showBpContentDialog = true
+ }
+
+ /* * * * Download single blueprint * * * */
+ BpFileNameForDownload: string;
+ download() {
+ let file = new Blob([this.BpContentToView], { type: 'text;charset=utf-8' });
+ let name: string = this.BpFileNameForDownload + '.yaml'
+ saveAs(file, name)
+ }
+
+/* * * * Download selected blueprints * * * */
+ downloadSelectedBps() {
+ let canDownloadBps: boolean = true;
+
+ //checks if blueprints for multiple releases are selected
+ let selectedBpRelease: string = this.selectedBPs[0]['instanceRelease'];
+ for (let bp in this.selectedBPs) {
+ if (this.selectedBPs[bp]['instanceRelease'] !== selectedBpRelease) {
+ canDownloadBps = false
+ break
+ }
+ }
+
+ //downloads blueprints to zip file if all selected blueprints are for one release
+ if (canDownloadBps) {
+ var zip = new JSZip();
+ for (var i in this.selectedBPs) {
+ zip.file(`${this.selectedBPs[i]['tag']}_${this.selectedBPs[i]['type']}_${this.selectedBPs[i]['instanceRelease']}_${this.selectedBPs[i]['version']}.yaml`, this.selectedBPs[i]['content'])
+ }
+ zip.generateAsync({ type: "blob" }).then(function (content) {
+ saveAs(content, 'Blueprints.zip');
+ });
+ } else {
+ this.messageService.add({ key: 'multipleBpReleasesSelected', severity: 'error', summary: 'Error Message', detail: "Cannot download blueprints for different releases" });
+ }
+
+ this.selectedBPs = []
+ }
+}
+
+export interface BlueprintElement{
+ instanceId: string
+ instanceName: string
+ instanceRelease: string
+ id: string
+ version: string
+ content: string
+ status: string
+ fileName: string
+ tag: string
+ type: string
+ metadata: {
+ failureReason: string
+ notes: string
+ labels: string[]
+ createdBy: string
+ createdOn: string
+ updatedBy: string
+ updatedOn: string
+ },
+ specification: {
+ id: string
+ }
+} \ No newline at end of file