diff --git a/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryLicenseModule/LicenseEngineApiData.ts b/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryLicenseModule/LicenseEngineApiData.ts index 16e9901fbc..6bd75402ea 100644 --- a/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryLicenseModule/LicenseEngineApiData.ts +++ b/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryLicenseModule/LicenseEngineApiData.ts @@ -1,3 +1,5 @@ +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; + /******************************************************************************* * Copyright (c) 2022 Contributors to the Eclipse Foundation * @@ -23,6 +25,18 @@ export interface Software { licensesAll: Array; } +export class LicenseTree { + name: string; + files?: LicenseTree[]; + ismarktodelte: boolean; +} + +export class LicenseTreeFlatNode { + expandable: boolean; + name: string; + level: number; +} + export enum Status { QUEUED = 'QUEUED', UPLOADING = 'UPLOADING', diff --git a/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryLicenseModule/licenseEngine.service.ts b/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryLicenseModule/licenseEngine.service.ts index 8441d4052a..d2ed4fc481 100644 --- a/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryLicenseModule/licenseEngine.service.ts +++ b/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryLicenseModule/licenseEngine.service.ts @@ -98,7 +98,7 @@ export class LicenseEngineService { getCompatibleLicenses(): Observable { const headers = new HttpHeaders({ 'content-type': 'application/json', 'Accept': 'application/json' }); - return this.http.get(this.licenseEngineUrl + '/software/' + this.software.id + '/recommended-licenses', { + return this.http.get(this.licenseEngineUrl + '/software/' + this.software.id + '/compatible-licenses', { headers: headers }).map( (data) => { @@ -169,4 +169,35 @@ export class LicenseEngineService { return this.software.licensesEffective; } + getLicensewithFiles(id: String): any { + const headers = new HttpHeaders({ 'Accept': 'application/json' }); + return this.http.get(this.licenseEngineUrl + '/software/' + id + '/licenses?effective=false', { + headers: headers + }); + } + + excludeFile(filename: String[]) { + + const headers = new HttpHeaders({ 'content-type': 'application/json' }); + headers.append('Access-Control-Request-Method', 'DELETE'); + return this.http.put(this.licenseEngineUrl + '/software/' + this.software.id + '/files-excluded', filename, { + headers: headers, observe: 'response' + }).map((response) => { + return response.status === 200; + }); + } + + deleteExcludedFile() { + const headers = new HttpHeaders({ 'content-type': 'application/json' }); + headers.append('Access-Control-Request-Method', 'DELETE'); + return this.http.delete(this.licenseEngineUrl + '/software/' + this.software.id + '/files-excluded', { + headers: headers, observe: 'response' + }).map((response) => { + return response.status === 204; + }); + + + + + } } diff --git a/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryLicenseModule/wineryLicense.component.css b/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryLicenseModule/wineryLicense.component.css index 63a6a11f9f..c878e5d58f 100644 --- a/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryLicenseModule/wineryLicense.component.css +++ b/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryLicenseModule/wineryLicense.component.css @@ -76,3 +76,19 @@ input { font: normal 12px/100% Verdana, Tahoma, sans-serif; width: 100%; } + +#s1 { + height: 40px; + width: 600px; + overflow-x: scroll;/* Remove this if you want the user to resize the textarea */ +} + +#s2 { + height: 40px; + width: 600px; + overflow-x: scroll;/* Remove this if you want the user to resize the textarea */ +} + +.emphasized { + font-size: small; +} diff --git a/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryLicenseModule/wineryLicense.component.html b/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryLicenseModule/wineryLicense.component.html index c556f7a49f..33ea091b8f 100644 --- a/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryLicenseModule/wineryLicense.component.html +++ b/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryLicenseModule/wineryLicense.component.html @@ -78,7 +78,7 @@ -
+
@@ -117,7 +117,93 @@ Check Compatibility
- {{foundLicenses}} + + + + + +
Files with found licenses
+
+
+ + + + + + + {{node.name}} + + + + + +

{{node.name}} +

+ +

+ + {{node.name}} + +

+ + + + +
+ +
+
+
+ + + + +
Files which may contain possible licences
+ +
+ +
+ + + {{file}} + + + + +
+ +
+ +
+ + + + +
Files which do not contain any licences
+ +
+ +
+ + + {{file}} + + + + +
+ +
+


+
Select License diff --git a/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryLicenseModule/wineryLicense.component.ts b/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryLicenseModule/wineryLicense.component.ts index 576468ae4f..8f0c3c2802 100644 --- a/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryLicenseModule/wineryLicense.component.ts +++ b/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryLicenseModule/wineryLicense.component.ts @@ -13,6 +13,7 @@ *******************************************************************************/ import { ViewChild, Component, OnInit, TemplateRef } from '@angular/core'; import { WineryLicenseService } from './wineryLicense.service'; +import { ChecklistDatabase } from './wineryLicenseTree.service'; import { WineryNotificationService } from '../wineryNotificationModule/wineryNotification.service'; import { InstanceService } from '../instance/instance.service'; import { ToscaTypes } from '../model/enums'; @@ -28,24 +29,33 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { MatStepper } from '@angular/material/stepper'; import { BsModalRef, BsModalService } from 'ngx-bootstrap'; import { LicenseEngineService } from './licenseEngine.service'; -import { License } from './LicenseEngineApiData'; +import { License, LicenseTree, LicenseTreeFlatNode } from './LicenseEngineApiData'; +import { MatTreeFlattener } from '@angular/material'; +import { FlatTreeControl } from '@angular/cdk/tree'; +import { MatTreeFlatDataSource } from '@angular/material/tree'; +import { SelectionModel } from '@angular/cdk/collections'; + @Component({ templateUrl: 'wineryLicense.component.html', styleUrls: ['wineryLicense.component.css'], - providers: [WineryLicenseService, LicenseEngineService] + providers: [WineryLicenseService, LicenseEngineService, ChecklistDatabase] }) + export class WineryLicenseComponent implements OnInit { + licenseID: String; loading = true; loadingLicense = false; isEditable = false; licenseAvailable = true; showForm = false; loadingbar = false; - foundLicenses = ''; + foundLicenseswithFiles = new Map>(); + treeData = {}; + treeParentData = {}; currentLicenseText = ''; initialLicenseText = ''; selectedLicenseText = ''; @@ -53,14 +63,40 @@ export class WineryLicenseComponent implements OnInit { options: string[] = []; selectedOptions: string[] = []; compatibleLicenses: License[] = []; - + unknowLicense = []; + nullLicense = []; toscaType: ToscaTypes; licenseEngine: boolean; firstFormGroup: FormGroup; secondFormGroup: FormGroup; confirmSaveModalRef: BsModalRef; confirmDownloadModalRef: BsModalRef; - + treeControl = new FlatTreeControl( + (node) => node.level, + (node) => node.expandable, + ); + + treeFlattener = new MatTreeFlattener( + (node: LicenseTree, level_: number) => { + return { + expandable: !!node.files && node.files.length > 0, + name: node.name, + level: level_, + }; + }, + (node) => node.level, + (node) => node.expandable, + (node) => node.files, + ); + dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener); + flatNodeMap = new Map(); + nestedNodeMap = new Map(); + /** A selected parent node to be inserted */ + selectedParent: LicenseTreeFlatNode | null = null; + /** The new item's name */ + newItemName = ''; + /** The selection for checklist */ + checklistSelection = new SelectionModel(true /* multiple */); @ViewChild('stepper') stepper: MatStepper; @ViewChild('confirmSaveModal') confirmSaveModal: TemplateRef; @ViewChild('confirmDownloadModal') confirmDownloadModal: TemplateRef; @@ -69,9 +105,199 @@ export class WineryLicenseComponent implements OnInit { private configurationService: WineryRepositoryConfigurationService, private wlService: WineryLicenseService, private leService: LicenseEngineService, public sharedData: InstanceService, private formBuilder: FormBuilder, - private modalService: BsModalService) { + private modalService: BsModalService, + private _database: ChecklistDatabase) { this.licenseEngine = configurationService.configuration.features.licenseEngine; this.toscaType = this.sharedData.toscaComponent.toscaType; + + this.treeFlattener = new MatTreeFlattener(this.transformer, this.getLevel, + this.isExpandable, this.getChildren); + this.treeControl = new FlatTreeControl(this.getLevel, this.isExpandable); + this.dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener); + + _database.dataChange.subscribe((data) => { + this.dataSource.data = data; + }); + + } + + + + + getLevel = (node: LicenseTreeFlatNode) => node.level; + isExpandable = (node: LicenseTreeFlatNode) => node.expandable; + getChildren = (node: LicenseTree): LicenseTree[] => node.files; + hasChild = (_: number, _nodeData: LicenseTreeFlatNode) => _nodeData.expandable; + hasNoContent = (_: number, _nodeData: LicenseTreeFlatNode) => _nodeData.name === ''; + + /** + * Transformer to convert nested node to flat node. Record the nodes in maps for later use. + */ + transformer = (node: LicenseTree, level: number) => { + const existingNode = this.nestedNodeMap.get(node); + // @ts-ignore + // @ts-ignore + const flatNode = existingNode && existingNode.item === node.item + ? existingNode + : new LicenseTreeFlatNode(); + flatNode.name = node.name; + flatNode.level = level; + flatNode.expandable = true; // edit this to true to make it always expandable + this.flatNodeMap.set(flatNode, node); + this.nestedNodeMap.set(node, flatNode); + return flatNode; + } + + /** Whether all the descendants of the node are selected. */ + descendantsAllSelected(node: LicenseTreeFlatNode): boolean { + const descendants = this.treeControl.getDescendants(node); + const descAllSelected = descendants.length > 0 && descendants.every((child) => { + return this.checklistSelection.isSelected(child); + }); + return descAllSelected; + } + + /** Whether part of the descendants are selected */ + descendantsPartiallySelected(node: LicenseTreeFlatNode): boolean { + const descendants = this.treeControl.getDescendants(node); + const result = descendants.some((child) => this.checklistSelection.isSelected(child)); + return result && !this.descendantsAllSelected(node); + } + + /** Toggle the to-do item selection. Select/deselect all the descendants node */ + todoItemSelectionToggle(node: LicenseTreeFlatNode): void { + this.checklistSelection.toggle(node); + const descendants = this.treeControl.getDescendants(node); + this.checklistSelection.isSelected(node) + ? this.checklistSelection.select(...descendants) + : this.checklistSelection.deselect(...descendants); + + // Force update for the parent + descendants.forEach((child) => this.checklistSelection.isSelected(child)); + this.checkAllParentsSelection(node); + } + /* Checks all the parents when a leaf node is selected/unselected */ + checkAllParentsSelection(node: LicenseTreeFlatNode): void { + let parent: LicenseTreeFlatNode | null = this.getParentNode(node); + while (parent) { + this.checkRootNodeSelection(parent); + parent = this.getParentNode(parent); + } + } + + /** Check root node checked state and change it accordingly */ + checkRootNodeSelection(node: LicenseTreeFlatNode): void { + const nodeSelected = this.checklistSelection.isSelected(node); + const descendants = this.treeControl.getDescendants(node); + const descAllSelected = descendants.length > 0 && descendants.every((child) => { + return this.checklistSelection.isSelected(child); + }); + if (nodeSelected && !descAllSelected) { + this.checklistSelection.deselect(node); + } else if (!nodeSelected && descAllSelected) { + this.checklistSelection.select(node); + } + } + + /* Get the parent node of a node */ + getParentNode(node: LicenseTreeFlatNode): LicenseTreeFlatNode | null { + const currentLevel = this.getLevel(node); + + if (currentLevel < 1) { + return null; + } + + const startIndex = this.treeControl.dataNodes.indexOf(node) - 1; + + for (let i = startIndex; i >= 0; i--) { + const currentNode = this.treeControl.dataNodes[i]; + + if (this.getLevel(currentNode) < currentLevel) { + return currentNode; + } + } + return null; + } + + /** Select the category so we can insert the new item. */ + addNewItem(node: LicenseTreeFlatNode) { + const parentNode = this.flatNodeMap.get(node); + this._database.insertItem(parentNode!, ''); + this.treeControl.expand(node); + } + + /** Save the node to database */ + saveNode(node: LicenseTreeFlatNode, itemValue: string) { + const nestedNode = this.flatNodeMap.get(node); + this._database.updateItem(nestedNode!, itemValue); + } + + public deleteItem(node: LicenseTreeFlatNode): void { + + // Get the parent node of the selected child node + if (this.treeParentData.hasOwnProperty(node.name)) { + this.treeParentData[node.name] = true; + + // It mean user selected parent node so we need to select all the childred node + for (const child in this.treeData[node.name]) { + if (this.treeData[node.name].hasOwnProperty(child)) { + this.treeData[node.name][child] = true; + } } + + this.treeControl.expand(node); + return; + } + const parentNode = this.getParentNode(node); + // Map from flat node to nested node. + this.treeData[parentNode.name][node.name] = true; + this.treeControl.expand(node); + + } + + undoDelteItemd(node: LicenseTreeFlatNode): void { + + // Get the parent node of the selected child node + if (this.treeParentData.hasOwnProperty(node.name)) { + this.treeParentData[node.name] = false; + + // undo parent children selection + for (const child in this.treeData[node.name]) { + if (this.treeData[node.name].hasOwnProperty(child)) { + this.treeData[node.name][child] = false; + } + } + this.treeControl.expand(node); + return; + } + const parentNode = this.getParentNode(node); + // Map from flat node to nested node. + this.treeData[parentNode.name][node.name] = false; + this.treeControl.expand(node); + + } + + public isObjectEmpty(obj) { + for (const prop in obj) { + if (obj.hasOwnProperty(prop)) { + return false; + } + } + return true; + } + + public checkNode(node: LicenseTreeFlatNode) { + + // check if dictionary is empty or not + if (this.isObjectEmpty(this.treeData)) { + return false; + } + // check is parent not child + if (this.treeParentData.hasOwnProperty(node.name)) { + return this.treeParentData[node.name]; + } + + const parentNode = this.getParentNode(node); + return this.treeData[parentNode.name][node.name]; } ngOnInit() { @@ -164,7 +390,45 @@ export class WineryLicenseComponent implements OnInit { checkLicenseData() { if (this.leService.isFinished()) { - this.foundLicenses = 'Extracted Licenses From Source Code: ' + this.leService.getFoundLicenses(); + const toscaElements = this.sharedData.path.split('/'); + const toscaElementID = toscaElements[toscaElements.length - 1]; + this.licenseID = toscaElementID; + let tempDict = {}; + this.leService.getLicensewithFiles(toscaElementID).subscribe((data) => { + // separating other license + if (data.hasOwnProperty('UNKNOWN LICENSE')) { + this.unknowLicense = data['UNKNOWN LICENSE']; + delete data['UNKNOWN LICENSE']; + } else { this.unknowLicense = []; } + if (data.hasOwnProperty('NULL LICENSE')) { + + this.nullLicense = data['NULL LICENSE']; + delete data['NULL LICENSE']; + } else { + this.nullLicense = []; + } + for (const entry in data) { + if (data.hasOwnProperty(entry)) { + const tempList = []; + for (const index in data[entry]) { + + if (data[entry].hasOwnProperty(index)) { + tempDict[data[entry][index]] = false; + tempList.push(data[entry][index]); + } } + this.treeData[entry] = tempDict; + this.treeParentData[entry] = false; + tempDict = {}; + this._database.TREE_DATA[entry] = tempList; + const mapKey = entry; + const mapValue = data[entry]; + this.foundLicenseswithFiles.set(mapKey, mapValue); + }} + + this._database.initialize(); + + this.foundLicenses = data; + }); this.loadingbar = false; this.firstFormGroup.enable(); this.stepper.selected.completed = true; @@ -174,6 +438,23 @@ export class WineryLicenseComponent implements OnInit { } } + deleteFromBackend() { + const filenames = []; + for (const key in this.treeData) { + if (this.treeData.hasOwnProperty(key)) { + for (const val in this.treeData[key]) { + if (this.treeData[key][val]) { + + filenames.push((val)); + } + } + } } + if (filenames.length === 0) { + return; + } + this.leService.excludeFile(filenames).subscribe(); + } + onSubmitCheckCompatibility() { this.loadingbar = true; this.compatibleLicenses = []; @@ -335,3 +616,4 @@ export class WineryLicenseComponent implements OnInit { this.notify.success('Successfully saved LICENSE'); } } + diff --git a/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryLicenseModule/wineryLicense.module.ts b/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryLicenseModule/wineryLicense.module.ts index aa8b344f5f..f8d0c15bb9 100644 --- a/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryLicenseModule/wineryLicense.module.ts +++ b/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryLicenseModule/wineryLicense.module.ts @@ -19,9 +19,10 @@ import { WineryPipesModule } from '../wineryPipes/wineryPipes.module'; import { CommonModule } from '@angular/common'; import { BrowserModule } from '@angular/platform-browser'; import { - MatButtonModule, MatExpansionModule, MatFormFieldModule, MatIconModule, MatInputModule, MatListModule, + MatButtonModule, MatCheckboxModule, MatExpansionModule, MatFormFieldModule, MatIconModule, MatInputModule, + MatListModule, MatProgressBarModule, - MatSelectModule, MatStepperModule + MatSelectModule, MatStepperModule, MatTreeModule } from '@angular/material'; import { WineryModalModule } from '../wineryModalModule/winery.modal.module'; import { WineryLoaderModule } from '../wineryLoader/wineryLoader.module'; @@ -45,6 +46,8 @@ import { WineryLoaderModule } from '../wineryLoader/wineryLoader.module'; MatListModule, MatExpansionModule, MatIconModule, + MatTreeModule, + MatCheckboxModule, ], exports: [ WineryLicenseComponent diff --git a/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryLicenseModule/wineryLicenseTree.service.ts b/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryLicenseModule/wineryLicenseTree.service.ts new file mode 100644 index 0000000000..fa0fd243a6 --- /dev/null +++ b/org.eclipse.winery.frontends/app/tosca-management/src/app/wineryLicenseModule/wineryLicenseTree.service.ts @@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2022 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + *******************************************************************************/ + +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; +import { LicenseTree, LicenseTreeFlatNode } from './LicenseEngineApiData'; +import { Injectable } from '@angular/core'; +import { LicenseEngineService } from './licenseEngine.service'; +import { InstanceService } from '../instance/instance.service'; + +@Injectable() +export class ChecklistDatabase { + dataChange = new BehaviorSubject([]); + TREE_DATA = []; + parentNodeMap = new Map(); + + get data(): LicenseTree[] { + return this.dataChange.value; + } + + constructor(private leService: LicenseEngineService, private sharedData: InstanceService + ) { + this.initialize(); + } + + initialize() { + // Build the tree nodes from Json object. The result is a list of `TodoItemNode` with nested + // file node as children. + const data = this.buildFileTree(this.TREE_DATA, 0); + + // Notify the change. + this.dataChange.next(data); + } + + /** + * Build the file structure tree. The `value` is the Json object, or a sub-tree of a Json object. + * The return value is the list of `TodoItemNode`. + */ + buildFileTree(obj: { [key: string]: any }, level: number): LicenseTree[] { + return Object.keys(obj).reduce((accumulator, key) => { + const value = obj[key]; + const node = new LicenseTree(); + node.name = key; + + if (value != null) { + if (typeof value === 'object') { + node.files = this.buildFileTree(value, level + 1); + } else { + node.name = value; + } + } + + return accumulator.concat(node); + }, []); + } + + insertItem(parent: LicenseTree, name: string) { + if (!parent.files) { + + parent.files = []; + } + parent.files.push({ name } as LicenseTree); + this.dataChange.next(this.data); + } + + public deleteItem(parent: LicenseTree, name: string): void { + if (parent.files) { + parent.files = parent.files.filter((c) => c.name !== name); + this.dataChange.next(this.data); + } + const toscaElements = this.sharedData.path.split('/'); + const toscaElementID = toscaElements[toscaElements.length - 1]; + const lst: String[] = [name]; + this.leService.excludeFile(lst).subscribe((success) => { + }, () => { + }); + } + + public findParent(id: number, node: any): any { + + if (node && node.id === id) { + return node; + } else { + for (const element in node.children) { + if (node.children[element].children && node.children[element].children.length > 0) { + return this.findParent(id, node.children[element]); + } + } + } + } + updateItem(node: LicenseTree, name: string) { + node.name = name; + this.dataChange.next(this.data); + } +}