From ac7c28835f865d4c6d3dfbbcadd0c3e9bf6c8927 Mon Sep 17 00:00:00 2001 From: Ralf King Date: Sat, 2 Dec 2023 01:12:00 +0100 Subject: [PATCH] Support new collection projects. Display collection logic used in project list and mark collection projects visually. Signed-off-by: Ralf King --- src/assets/scss/_custom.scss | 7 +++- src/i18n/locales/en.json | 8 ++++ src/views/portfolio/projects/Project.vue | 4 ++ .../projects/ProjectCreateProjectModal.vue | 39 +++++++++++++++++-- .../projects/ProjectDetailsModal.vue | 28 +++++++++++++ src/views/portfolio/projects/ProjectList.vue | 19 ++++++++- 6 files changed, 99 insertions(+), 6 deletions(-) diff --git a/src/assets/scss/_custom.scss b/src/assets/scss/_custom.scss index 4d2ceb832..abbdf580c 100644 --- a/src/assets/scss/_custom.scss +++ b/src/assets/scss/_custom.scss @@ -469,4 +469,9 @@ td a.detail-icon { .keep-together { display: inline; white-space: nowrap; -} \ No newline at end of file +} + +.icon-cellend { + float: right; + padding-top: 0.3rem; +} diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 0c208edde..7e0f0e323 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -90,6 +90,7 @@ "description": "Description", "tags": "Tags", "add_tag": "Add Tag", + "project_add_collection_tag": "Aggregate data of children with this tag", "update": "Update", "cancel": "Cancel", "add_version": "Add Version", @@ -285,6 +286,8 @@ "cpe": "CPE", "cpe_full": "Common Platform Enumeration (CPE)", "classifier": "Classifier", + "collectionLogic": "Project Collection Logic", + "collection_indicator_tooltip": "Collection project with values calculated based on children.", "search_parent": "Type to search parent", "inactive_active_children" : "The project cannot be set to inactive if it has active children", "filename": "Filename", @@ -310,6 +313,7 @@ "component_cpe_desc": "The CPE v2.2 or v2.3 URI as provided by MITRE or NIST. All assets (applications, operating systems, and hardware) should have a CPE specified", "component_swid_tagid_desc": "The ISO/IEC 19770-2:2015 (SWID) tag ID provided by the software vendor", "component_classifier_desc": "Specifies the type of component: Assets (applications, operating systems, and hardware) and non-assets (libraries, frameworks, and files)", + "project_collection_logic_desc": "Specifies if this project is a collection project and which metrics calculation logic to apply for a collection project. Collection projects do not have own components but display data of their children using one of the available logics.", "component_spdx_license_desc": "Specifies the SPDX license ID of the component", "component_license_expression_desc": "Specifies license information for the component in the form of an SPDX expression", "component_license_url_desc": "Specifies the URL to the license of the component", @@ -415,6 +419,10 @@ "component_device": "Device", "component_firmware": "Firmware", "component_file": "File", + "project_collection_logic_none": "None", + "project_collection_logic_aggregate_direct_children": "Aggregate direct children", + "project_collection_logic_aggregate_direct_children_with_tag": "Aggregate direct children with tag", + "project_collection_logic_highest_semver_child": "Show highest SemVer child", "dates": "Dates", "owasp_rr": "OWASP Risk Rating", "owasp_rr_likelihood_score": "OWASP RR Likelihood score", diff --git a/src/views/portfolio/projects/Project.vue b/src/views/portfolio/projects/Project.vue index 1a08425cb..5909b0019 100644 --- a/src/views/portfolio/projects/Project.vue +++ b/src/views/portfolio/projects/Project.vue @@ -247,6 +247,10 @@ } }).then((response) => { this.project = response.data; + // metrics are not always returned by API, fix error sometimes raised in following lines + if(!Object.hasOwn(this.project, 'metrics')) { + this.project.metrics = {} + } this.currentCritical = common.valueWithDefault(this.project.metrics.critical, 0); this.currentHigh = common.valueWithDefault(this.project.metrics.high, 0); this.currentMedium = common.valueWithDefault(this.project.metrics.medium, 0); diff --git a/src/views/portfolio/projects/ProjectCreateProjectModal.vue b/src/views/portfolio/projects/ProjectCreateProjectModal.vue index 9a073e7c6..a9acafd88 100644 --- a/src/views/portfolio/projects/ProjectCreateProjectModal.vue +++ b/src/views/portfolio/projects/ProjectCreateProjectModal.vue @@ -14,6 +14,15 @@ + +
0 ) ? {name: this.collectionTags[0].text} : null, purl: this.project.purl, cpe: this.project.cpe, swidTagId: this.project.swidTagId, @@ -212,11 +239,15 @@ }) }, resetValues: function () { - this.project = {}; + this.project = { + collectionLogic: 'NONE' // set default to regular project + }; this.tag = ""; this.tags = []; - this.selectedParent = null - this.availableParents = [] + this.selectedParent = null; + this.availableParents = []; + this.collectionTags = []; + this.showCollectionTags = false; }, createProjectLabel: function (project) { if (project.version){ diff --git a/src/views/portfolio/projects/ProjectDetailsModal.vue b/src/views/portfolio/projects/ProjectDetailsModal.vue index 0ac486b1c..37ef09339 100644 --- a/src/views/portfolio/projects/ProjectDetailsModal.vue +++ b/src/views/portfolio/projects/ProjectDetailsModal.vue @@ -17,6 +17,16 @@ v-model="project.classifier" :options="availableClassifiers" :label="$t('message.classifier')" :tooltip="$t('message.component_classifier_desc')" :readonly="this.isNotPermitted(PERMISSIONS.PORTFOLIO_MANAGEMENT)" /> + + +
({ text: tag.name })); + this.collectionTags = this.project.collectionTag ? [{ text: this.project.collectionTag.name }] : []; + this.syncCollectionTagsVisibility(this.project.collectionLogic); }, syncReadOnlyNameField: function(value) { this.readOnlyProjectName = value; @@ -213,6 +234,9 @@ syncReadOnlyVersionField: function(value) { this.readOnlyProjectVersion = value; }, + syncCollectionTagsVisibility: function(value) { + this.showCollectionTags = value === 'AGGREGATE_DIRECT_CHILDREN_WITH_TAG'; + }, updateProject: function() { let url = `${this.$api.BASE_URL}/${this.$api.URL_PROJECT}`; let tagsNode = []; @@ -230,6 +254,10 @@ version: this.project.version, description: this.project.description, classifier: this.project.classifier, + collectionLogic: this.project.collectionLogic, + collectionTag: ( this.project.collectionLogic === 'AGGREGATE_DIRECT_CHILDREN_WITH_TAG' && + this.collectionTags && + this.collectionTags.length > 0 ) ? {name: this.collectionTags[0].text} : null, parent: parent, cpe: this.project.cpe, purl: this.project.purl, diff --git a/src/views/portfolio/projects/ProjectList.vue b/src/views/portfolio/projects/ProjectList.vue index da1eccd2d..d4f071dbd 100644 --- a/src/views/portfolio/projects/ProjectList.vue +++ b/src/views/portfolio/projects/ProjectList.vue @@ -177,7 +177,24 @@ import ProjectCreateProjectModal from "./ProjectCreateProjectModal"; sortable: true, formatter(value, row, index) { let url = xssFilters.uriInUnQuotedAttr("../projects/" + row.uuid); - return `${xssFilters.inHTMLData(value)}`; + let collectionIcon = ''; + if(row.collectionLogic !== 'NONE') { + let title = 'Metrics of collection project are calculated ' + switch (row.collectionLogic) { + case 'AGGREGATE_DIRECT_CHILDREN': + title += 'by aggregating numbers of all direct children.'; + break; + case 'AGGREGATE_DIRECT_CHILDREN_WITH_TAG': + const tag = !row.collectionTag ? '' : xssFilters.inDoubleQuotedAttr(row.collectionTag.name); + title += `by aggregating numbers of direct children with tag '${tag}'.`; + break; + case 'HIGHEST_SEMVER_CHILD': + title += 'by using the child with highest SemVer version.' + break; + } + collectionIcon = ` `; + } + return `${xssFilters.inHTMLData(value)}${collectionIcon}`; } }, {