Skip to content

Commit

Permalink
Support new collection projects. Display collection logic used in pro…
Browse files Browse the repository at this point in the history
…ject list and mark collection projects visually.

Signed-off-by: Ralf King <[email protected]>
  • Loading branch information
rkg-mm committed Sep 14, 2024
1 parent 1d91e6f commit bb23172
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 3 deletions.
6 changes: 6 additions & 0 deletions src/assets/scss/_custom.scss
Original file line number Diff line number Diff line change
Expand Up @@ -508,3 +508,9 @@ td a.detail-icon {
display: inline;
white-space: nowrap;
}


.icon-cellend {
float: right;
padding-top: 0.3rem;
}
8 changes: 8 additions & 0 deletions src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,8 @@
"close": "Close",
"code_not_present": "Code not present",
"code_not_reachable": "Code not reachable",
"collectionLogic": "Project Collection Logic",
"collection_indicator_tooltip": "Collection project with values calculated based on children.",
"comment": "Comment",
"comments": "Comments",
"component": "Component",
Expand Down Expand Up @@ -701,6 +703,12 @@
"profile_update": "Update Profile",
"profile_updated": "Profile updated",
"project_cloning_in_progress": "The project is being created with the cloning options specified",
"project_add_collection_tag": "Aggregate data of children with this tag",
"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.",
"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",
"project_created": "Project created",
"project_deleted": "Project deleted",
"project_details": "Project Details",
Expand Down
4 changes: 4 additions & 0 deletions src/views/portfolio/projects/Project.vue
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,10 @@ export default {
})
.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,
Expand Down
44 changes: 42 additions & 2 deletions src/views/portfolio/projects/ProjectCreateProjectModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,24 @@
:label="$t('message.classifier')"
:tooltip="$t('message.component_classifier_desc')"
/>
<b-input-group-form-select
id="v-collection-logic-input"
required="true"
v-model="project.collectionLogic" :options="availableCollectionLogics"
:label="$t('message.collectionLogic')" :tooltip="$t('message.project_collection_logic_desc')"
:readonly="this.isNotPermitted(PERMISSIONS.PORTFOLIO_MANAGEMENT)"
v-on:change="syncCollectionTagsVisibility"
/>
<vue-tags-input
id="input-collectionTags"
v-model="collectionTagTyping"
:tags="collectionTags"
:add-on-key="addOnKeys"
:placeholder="$t('message.project_add_collection_tag')" @tags-changed="newCollectionTags => this.collectionTags = newCollectionTags"
class="mw-100 bg-transparent text-lowercase" :max-tags="1" v-show="showCollectionTags"
:readonly="this.isNotPermitted(PERMISSIONS.PORTFOLIO_MANAGEMENT)"
/>

<div style="margin-bottom: 1rem">
<label>Parent</label>
<multiselect
Expand Down Expand Up @@ -238,15 +256,26 @@ export default {
{ value: 'FIRMWARE', text: this.$i18n.t('message.component_firmware') },
{ value: 'FILE', text: this.$i18n.t('message.component_file') },
],
availableCollectionLogics: [
{ value: 'NONE', text: this.$i18n.t('message.project_collection_logic_none') },
{ value: 'AGGREGATE_DIRECT_CHILDREN', text: this.$i18n.t('message.project_collection_logic_aggregate_direct_children') },
{ value: 'AGGREGATE_DIRECT_CHILDREN_WITH_TAG', text: this.$i18n.t('message.project_collection_logic_aggregate_direct_children_with_tag') },
{ value: 'HIGHEST_SEMVER_CHILD', text: this.$i18n.t('message.project_collection_logic_highest_semver_child') }
],
selectableLicenses: [],
selectedLicense: '',
selectedParent: null,
availableParents: [],
project: {},
project: {
collectionLogic: 'NONE' // set default to regular project
},
tag: '', // The contents of a tag as its being typed into the vue-tag-input
tags: [], // An array of tags bound to the vue-tag-input
tagsAutoCompleteItems: [],
tagsAutoCompleteDebounce: null,
collectionTagTyping: '', // The contents of a collection tag as its being typed into the vue-tag-input
collectionTags: [], // An array of tags bound to the vue-tag-input for collection tag
showCollectionTags: false,
addOnKeys: [9, 13, 32, ':', ';', ','], // Separators used when typing tags into the vue-tag-input
labelIcon: {
dataOn: '\u2713',
Expand Down Expand Up @@ -287,6 +316,9 @@ export default {
syncReadOnlyVersionField: function (value) {
this.readOnlyProjectVersion = value;
},
syncCollectionTagsVisibility: function(value) {
this.showCollectionTags = value === 'AGGREGATE_DIRECT_CHILDREN_WITH_TAG';
},
createProject: function () {
let url = `${this.$api.BASE_URL}/${this.$api.URL_PROJECT}`;
let tagsNode = [];
Expand All @@ -304,6 +336,10 @@ export default {
//license: this.selectedLicense,
parent: parent,
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,
purl: this.project.purl,
cpe: this.project.cpe,
swidTagId: this.project.swidTagId,
Expand Down Expand Up @@ -353,11 +389,15 @@ export default {
});
},
resetValues: function () {
this.project = {};
this.project = {
collectionLogic: 'NONE' // set default to regular project
};
this.tag = '';
this.tags = [];
this.selectedParent = null;
this.availableParents = [];
this.collectionTags = [];
this.showCollectionTags = false;
},
createProjectLabel: function (project) {
if (project.version) {
Expand Down
40 changes: 40 additions & 0 deletions src/views/portfolio/projects/ProjectDetailsModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,28 @@
:tooltip="$t('message.component_classifier_desc')"
:readonly="this.isNotPermitted(PERMISSIONS.PORTFOLIO_MANAGEMENT)"
/>
<b-input-group-form-select
id="v-collection-logic-input"
required="true"
v-model="project.collectionLogic"
:options="availableCollectionLogics"
:label="$t('message.collectionLogic')"
:tooltip="$t('message.project_collection_logic_desc')"
:readonly="this.isNotPermitted(PERMISSIONS.PORTFOLIO_MANAGEMENT)"
v-on:change="syncCollectionTagsVisibility"
/>
<vue-tags-input
id="input-collectionTags"
v-model="collectionTagTyping"
:tags="collectionTags"
:add-on-key="addOnKeys"
:placeholder="$t('message.project_add_collection_tag')"
@tags-changed="newCollectionTags => this.collectionTags = newCollectionTags"
class="mw-100 bg-transparent text-lowercase"
:max-tags="1"
v-show="showCollectionTags"
:readonly="this.isNotPermitted(PERMISSIONS.PORTFOLIO_MANAGEMENT)"
/>
<div style="margin-bottom: 1rem">
<label>Parent</label>
<multiselect
Expand Down Expand Up @@ -496,13 +518,22 @@ export default {
{ value: 'FIRMWARE', text: this.$i18n.t('message.component_firmware') },
{ value: 'FILE', text: this.$i18n.t('message.component_file') },
],
availableCollectionLogics: [
{ value: 'NONE', text: this.$i18n.t('message.project_collection_logic_none') },
{ value: 'AGGREGATE_DIRECT_CHILDREN', text: this.$i18n.t('message.project_collection_logic_aggregate_direct_children') },
{ value: 'AGGREGATE_DIRECT_CHILDREN_WITH_TAG', text: this.$i18n.t('message.project_collection_logic_aggregate_direct_children_with_tag') },
{ value: 'HIGHEST_SEMVER_CHILD', text: this.$i18n.t('message.project_collection_logic_highest_semver_child') }
],
parent: null,
selectedParent: null,
availableParents: [],
tag: '', // The contents of a tag as its being typed into the vue-tag-input
tags: [], // An array of tags bound to the vue-tag-input
tagsAutoCompleteItems: [],
tagsAutoCompleteDebounce: null,
collectionTagTyping: '', // The contents of a collection tag as its being typed into the vue-tag-input
collectionTags: [], // An array of tags bound to the vue-tag-input for collection tag
showCollectionTags: false,
addOnKeys: [9, 13, 32, ':', ';', ','], // Separators used when typing tags into the vue-tag-input
labelIcon: {
dataOn: '\u2713',
Expand Down Expand Up @@ -657,13 +688,18 @@ export default {
methods: {
initializeTags: function () {
this.tags = (this.project.tags || []).map((tag) => ({ text: tag.name }));
this.collectionTags = this.project.collectionTag ? [{ text: this.project.collectionTag.name }] : [];
this.syncCollectionTagsVisibility(this.project.collectionLogic);
},
syncReadOnlyNameField: function (value) {
this.readOnlyProjectName = value;
},
syncReadOnlyVersionField: function (value) {
this.readOnlyProjectVersion = value;
},
syncCollectionTagsVisibility: function(value) {
this.showCollectionTags = value === 'AGGREGATE_DIRECT_CHILDREN_WITH_TAG';
},
syncActiveLabel: function (value) {
this.projectActiveLabel = value
? this.$t('message.active')
Expand All @@ -688,6 +724,10 @@ export default {
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,
Expand Down
19 changes: 18 additions & 1 deletion src/views/portfolio/projects/ProjectList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,24 @@ export default {
sortable: true,
formatter(value, row, index) {
let url = xssFilters.uriInUnQuotedAttr('../projects/' + row.uuid);
return `<a href="${url}">${xssFilters.inHTMLData(value)}</a>`;
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 = ` <i class="fa fa-calculator fa-fw icon-cellend" title="${title}"></i>`;
}
return `<a href="${url}">${xssFilters.inHTMLData(value)}</a>${collectionIcon}`;
},
},
{
Expand Down

0 comments on commit bb23172

Please sign in to comment.