Skip to content

Commit bad9641

Browse files
authored
Implement treegrid to display parent-child-relationship in project list (#328)
* Implement treegrid for parent-child-relationship Removed the hierarchical view with subtables in favor of the default table with an added treegrid to display the hierarchy. Also includes lazy-loading sub-levels. Signed-off-by: RBickert <[email protected]> * Implement dummy children for better lazy-loading Dummy children are now used instead of loading a level ahead to reduce API calls and optimize the loading times of a projects next level. Pagination not working correctly because dummy children need to be removed and removing from a bootstrap-table decrements the total row count. Signed-off-by: RBickert <[email protected]> * Remove dummy children for lazy-loading Removes dummy children for lazy-loading to avoid the need of removing data from the table, which caused problems with the pagination. Now renders the expander button for a project, if it has children which meet the current filter criteria (e.g. inactive, only applications) Signed-off-by: RBickert <[email protected]> * Retrieve available parents only when needed Only fetches the available parents for the `ProjectDetailsModal` and the `ProjectCreateProjectModal` when the modals are opened, instead of at the startup of the page, to reduce unnecessary loading of data. Signed-off-by: RBickert <[email protected]> * Fix checking visibility of wrong column Signed-off-by: RBickert <[email protected]> * Implement flat view and search Project list is a hierarchical view per default, can now be switched to flat view. Automatically switches to flat view while searching. Disables switching views while searching. Signed-off-by: RBickert <[email protected]> * Toggle `FlatView` switch to `on` when searching Toggles the state of the `showFlatView` switch to `on` when searching and saves the state before searching to switch back to it after searching. Signed-off-by: RBickert <[email protected]> * Switch to `uuid` and `parentUuid` for hierarchy Instead of using a projects `id` and `pid` to map the relationship between children and parents, a projects `uuid` and `parentUuid` are now hashed with `MurmurHash2` and then used to create the treegrid. (A treegrid's `idField` and `parentIdField` must be numbers and not strings to correctly work) Signed-off-by: RBickert <[email protected]> * Remove unused parameter of `onClickRow` Signed-off-by: RBickert <[email protected]> * Change parent input form Change parent input form to `vue-multiselect` and the API endpoint to `/api/v1/project?searchText=<fieldInput>&excludeInactive=false` Signed-off-by: RBickert <[email protected]> * Fix API endpoint including inactive projects Signed-off-by: RBickert <[email protected]> Signed-off-by: RBickert <[email protected]>
1 parent 35dd6e4 commit bad9641

File tree

10 files changed

+407
-29
lines changed

10 files changed

+407
-29
lines changed

package-lock.json

Lines changed: 29 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@
3333
"css-vars-ponyfill": "2.4.7",
3434
"dompurify": "2.4.0",
3535
"font-awesome": "4.7.0",
36+
"imurmurhash": "^0.1.4",
3637
"jquery": "3.6.0",
38+
"jquery-treegrid": "0.3.0",
3739
"lodash-es": "4.17.21",
3840
"oidc-client": "1.11.5",
3941
"perfect-scrollbar": "1.5.5",
@@ -48,6 +50,7 @@
4850
"vue-debounce": "2.6.0",
4951
"vue-easy-pie-chart": "git+https://[email protected]/DependencyTrack/vue-easy-pie-chart.git#master",
5052
"vue-i18n": "8.27.0",
53+
"vue-multiselect": "^2.1.6",
5154
"vue-page-title": "1.2.5",
5255
"vue-perfect-scrollbar": "0.2.1",
5356
"vue-router": "3.5.3",

src/assets/scss/_custom.scss

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,3 +338,127 @@ td a.detail-icon {
338338
.b-calendar-grid-body .text-dark {
339339
color: #ffffff !important;
340340
}
341+
.treegrid-indent {
342+
width: 10px;
343+
height: 16px;
344+
display: inline-block;
345+
position: relative;
346+
vertical-align: text-bottom;
347+
}
348+
.treegrid-expander {
349+
width: 10px;
350+
height: 16px;
351+
display: inline-block;
352+
position: relative;
353+
vertical-align: text-bottom;
354+
margin-right: 0.5rem;
355+
}
356+
.treegrid-expander-expanded {
357+
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEwIDE2IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbDpzcGFjZT0icHJlc2VydmUiIHhtbG5zOnNlcmlmPSJodHRwOi8vd3d3LnNlcmlmLmNvbS8iIHN0eWxlPSJmaWxsLXJ1bGU6ZXZlbm9kZDtjbGlwLXJ1bGU6ZXZlbm9kZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MjsiPgogICAgPGcgdHJhbnNmb3JtPSJtYXRyaXgoLTEsLTAuMDAwNDE4NDcxLDAuMDAwNDE4NDcxLC0xLDEyLjk3NDgsMTguNDM4MikiPgogICAgICAgIDxnIGlkPSJfLTAwMDAwMGZmIiBzZXJpZjppZD0iIzAwMDAwMGZmIj4KICAgICAgICAgICAgPHBhdGggZD0iTTguMTE5LDYuNDUyTDEyLjk4MSwxNC40MTZMMy4yNTgsMTQuNDE2TDguMTE5LDYuNDUyWiIgc3R5bGU9ImZpbGw6cmdiKDMzLDE2OCwyMTYpOyIvPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+);
358+
cursor: pointer;
359+
}
360+
.treegrid-expander-collapsed {
361+
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+Cjxzdmcgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEwIDE2IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbDpzcGFjZT0icHJlc2VydmUiIHhtbG5zOnNlcmlmPSJodHRwOi8vd3d3LnNlcmlmLmNvbS8iIHN0eWxlPSJmaWxsLXJ1bGU6ZXZlbm9kZDtjbGlwLXJ1bGU6ZXZlbm9kZDtzdHJva2UtbGluZWpvaW46cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MjsiPgogICAgPGcgdHJhbnNmb3JtPSJtYXRyaXgoLTAuMDAwMTk2MDg4LDEsLTEsLTAuMDAwMTk2MDg4LDE0LjQzNTksLTAuMTE2NDI5KSI+CiAgICAgICAgPGcgaWQ9Il8tMDAwMDAwZmYiIHNlcmlmOmlkPSIjMDAwMDAwZmYiPgogICAgICAgICAgICA8cGF0aCBkPSJNOC4xMTksNi40NTJMMTIuOTgxLDE0LjQxNkwzLjI1OCwxNC40MTZMOC4xMTksNi40NTJaIiBzdHlsZT0iZmlsbDpyZ2IoMzMsMTY4LDIxNik7Ii8+CiAgICAgICAgPC9nPgogICAgPC9nPgo8L3N2Zz4=);
362+
cursor: pointer;
363+
}
364+
365+
// Adjusts vue-multiselect style to dependency track
366+
367+
.multiselect__spinner {
368+
background: $grey-650;
369+
z-index: 52;
370+
}
371+
372+
.multiselect__spinner:before,
373+
.multiselect__spinner:after {
374+
border-color: $primary transparent transparent;
375+
}
376+
377+
.multiselect--active {
378+
color: #bfcfdf;
379+
}
380+
381+
.multiselect__input,
382+
.multiselect__single {
383+
background: $grey-650;
384+
color: #bfcfdf;
385+
font-size: 0.875rem;
386+
font-weight: 400;
387+
padding-left: 0;
388+
margin-top: 0.15rem;
389+
}
390+
391+
.multiselect__input::placeholder {
392+
color: #bfcfdf;
393+
}
394+
395+
.multiselect__input:hover,
396+
.multiselect__single:hover {
397+
border-color: $grey-650;
398+
}
399+
400+
.multiselect__input:focus,
401+
.multiselect__single:focus {
402+
border-color: $grey-650;
403+
}
404+
405+
.multiselect__select,
406+
.multiselect__select:before {
407+
border-color: #2f353a transparent;
408+
z-index: 51;
409+
}
410+
411+
.multiselect__placeholder {
412+
color: $grey-400;
413+
font-size: 0.875rem;
414+
font-weight: 400;
415+
}
416+
417+
.multiselect__content-wrapper {
418+
border: 1px solid $border-color;
419+
background: $grey-650;
420+
font-size: 0.875rem;
421+
font-weight: 400;
422+
}
423+
424+
.multiselect__content {
425+
font-size: 0.875rem;
426+
font-weight: 400;
427+
}
428+
429+
.multiselect__option {
430+
cursor: default;
431+
font-size: 0.875rem;
432+
font-weight: 400;
433+
}
434+
435+
.multiselect__option--highlight {
436+
background: #52525e;
437+
color: #bfcfdf;
438+
font-size: 0.875rem;
439+
font-weight: 400;
440+
}
441+
442+
.multiselect__option--selected {
443+
background: $grey-650;
444+
color: #bfcfdf;
445+
font-size: 0.875rem;
446+
font-weight: 700;
447+
}
448+
449+
.multiselect__tags {
450+
border: 1px solid $border-color;
451+
background: $grey-650;
452+
color: #bfcfdf;
453+
height: $input-height;
454+
padding: 0.375rem 0.75rem;
455+
font-size: 0.875rem;
456+
font-weight: 400;
457+
}
458+
459+
.multiselect__option--selected.multiselect__option--highlight {
460+
background: #52525e;
461+
color: #bfcfdf;
462+
font-weight: 700;
463+
}
464+

src/assets/scss/style.scss

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@
1313

1414
// ie fixes
1515
@import "ie-fix";
16+
17+
@import "~vue-multiselect/dist/vue-multiselect.min.css";

src/i18n/locales/en.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@
109109
"property_deleted": "Property deleted",
110110
"create_project": "Create Project",
111111
"show_inactive_projects": "Show inactive projects",
112+
"show_flat_view": "Show flat project view",
113+
"switch_view": "Cannot switch view while searching",
112114
"create_project_property": "Create Project Property",
113115
"group_name": "Group Name",
114116
"property_name": "Property Name",
@@ -267,6 +269,8 @@
267269
"cpe": "CPE",
268270
"cpe_full": "Common Platform Enumeration (CPE)",
269271
"classifier": "Classifier",
272+
"search_parent": "Type to search parent",
273+
"inactive_active_children" : "The project cannot be set to inactive if it has active children",
270274
"filename": "Filename",
271275
"copyright": "Copyright",
272276
"md5": "MD5",

src/plugins/table.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import 'bootstrap-table/dist/bootstrap-table.min.css'
33
import './jquery.js'
44
import Vue from 'vue'
55
import 'bootstrap'
6+
import 'jquery-treegrid/js/jquery.treegrid.min.js'
67
import 'bootstrap-table/dist/bootstrap-table.js'
8+
import 'bootstrap-table/dist/extensions/treegrid/bootstrap-table-treegrid.min.js'
79
import BootstrapTable from 'bootstrap-table/dist/bootstrap-table-vue.esm.js'
810

911
Vue.component('BootstrapTable', BootstrapTable);

src/views/portfolio/projects/Project.vue

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@
9292
</b-row>
9393
</b-card-body>
9494
<div id="project-info-footer" slot="footer">
95-
<b-link class="font-weight-bold font-xs btn-block text-muted" v-b-modal.projectDetailsModal>{{ $t('message.view_details') }} <i class="fa fa-angle-right float-right font-lg"></i></b-link>
95+
<b-link class="font-weight-bold font-xs btn-block text-muted" @click="initializeProjectDetailsModal">{{ $t('message.view_details') }} <i class="fa fa-angle-right float-right font-lg"></i></b-link>
9696
</div>
9797
</b-card>
9898
<b-tabs class="body-bg-color" style="border-left: 0; border-right:0; border-top:0 ">
@@ -125,7 +125,7 @@
125125
<project-policy-violations :key="this.uuid" :uuid="this.uuid" v-on:total="totalViolations = $event" />
126126
</b-tab>
127127
</b-tabs>
128-
<project-details-modal :project="cloneDeep(project)" v-on:projectUpdated="syncProjectFields"/>
128+
<project-details-modal :project="cloneDeep(project)" :uuid="this.uuid" v-on:projectUpdated="syncProjectFields"/>
129129
<project-properties-modal :uuid="this.uuid" />
130130
<project-create-property-modal :uuid="this.uuid" />
131131
<project-add-version-modal :uuid="this.uuid" />
@@ -240,12 +240,13 @@
240240
this.currentUnassigned = common.valueWithDefault(response.data.unassigned, 0);
241241
this.currentRiskScore = common.valueWithDefault(response.data.inheritedRiskScore, 0);
242242
});
243+
},
244+
initializeProjectDetailsModal: function () {
245+
this.$root.$emit('initializeProjectDetailsModal')
243246
}
244247
},
245248
beforeMount() {
246249
this.uuid = this.$route.params.uuid;
247-
},
248-
mounted() {
249250
this.initialize();
250251
},
251252
watch:{

0 commit comments

Comments
 (0)