summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorLukas Eipert <leipert@gitlab.com>2018-11-06 11:20:49 +0100
committerLukas Eipert <leipert@gitlab.com>2018-11-06 11:39:11 +0100
commit26ab92d3f38eea724728d71bf033895e1e783d1d (patch)
tree7cedaf4a007e17dec699e03cb28269a62b506319 /app
parent84f562e7040f1da818157853de84874d57135ca9 (diff)
downloadgitlab-ce-26ab92d3f38eea724728d71bf033895e1e783d1d.tar.gz
Instead of rendering all report items in 4 big lists, we make use of vue-virtual-scroll-list and render only few dozens at once. This improves the performance in several metrics: - Initial load time - Memory Pressure - CPU Load - DOM node count In an example with around 11k reported security vulnerabilities: - Initial load time: 27s -> 4.1s - Memory Pressure: ~750 MB -> ~270 MB - CPU Load (time spent on executing JS/Rendering): 22s -> 2.5s - DOM node count: 430k -> 7k up to 30k while scrolling
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/reports/components/issues_list.vue85
-rw-r--r--app/assets/javascripts/reports/components/report_item.vue (renamed from app/assets/javascripts/reports/components/report_issues.vue)44
-rw-r--r--app/assets/javascripts/vue_shared/components/smart_virtual_list.vue42
3 files changed, 106 insertions, 65 deletions
diff --git a/app/assets/javascripts/reports/components/issues_list.vue b/app/assets/javascripts/reports/components/issues_list.vue
index 3b425ee2fed..f4243522ef8 100644
--- a/app/assets/javascripts/reports/components/issues_list.vue
+++ b/app/assets/javascripts/reports/components/issues_list.vue
@@ -1,18 +1,31 @@
<script>
-import IssuesBlock from '~/reports/components/report_issues.vue';
-import { STATUS_SUCCESS, STATUS_FAILED, STATUS_NEUTRAL } from '~/reports/constants';
+import ReportItem from '~/reports/components/report_item.vue';
+import { STATUS_FAILED, STATUS_NEUTRAL, STATUS_SUCCESS } from '~/reports/constants';
+import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue';
+
+const wrapIssueWithState = (status, isNew = false) => issue => ({
+ status: issue.status || status,
+ isNew,
+ issue,
+});
/**
* Renders block of issues
*/
-
export default {
components: {
- IssuesBlock,
+ SmartVirtualList,
+ ReportItem,
},
- success: STATUS_SUCCESS,
- failed: STATUS_FAILED,
- neutral: STATUS_NEUTRAL,
+ // Typical height of a report item in px
+ typicalReportItemHeight: 32,
+ /*
+ The maximum amount of shown issues. This is calculated by
+ ( max-height of report-block-list / typicalReportItemHeight ) + some safety margin
+ We will use VirtualList if we have more items than this number.
+ For entries lower than this number, the virtual scroll list calculates the total height of the element wrongly.
+ */
+ maxShownReportItems: 20,
props: {
newIssues: {
type: Array,
@@ -40,42 +53,34 @@ export default {
default: '',
},
},
+ computed: {
+ issuesWithState() {
+ return [
+ ...this.newIssues.map(wrapIssueWithState(STATUS_FAILED, true)),
+ ...this.unresolvedIssues.map(wrapIssueWithState(STATUS_FAILED)),
+ ...this.neutralIssues.map(wrapIssueWithState(STATUS_NEUTRAL)),
+ ...this.resolvedIssues.map(wrapIssueWithState(STATUS_SUCCESS)),
+ ];
+ },
+ },
};
</script>
<template>
- <div class="report-block-container">
-
- <issues-block
- v-if="newIssues.length"
- :component="component"
- :issues="newIssues"
- class="js-mr-code-new-issues"
- status="failed"
- is-new
- />
-
- <issues-block
- v-if="unresolvedIssues.length"
- :component="component"
- :issues="unresolvedIssues"
- :status="$options.failed"
- class="js-mr-code-new-issues"
- />
-
- <issues-block
- v-if="neutralIssues.length"
- :component="component"
- :issues="neutralIssues"
- :status="$options.neutral"
- class="js-mr-code-non-issues"
- />
-
- <issues-block
- v-if="resolvedIssues.length"
+ <smart-virtual-list
+ :length="issuesWithState.length"
+ :remain="$options.maxShownReportItems"
+ :size="$options.typicalReportItemHeight"
+ class="report-block-container"
+ wtag="ul"
+ wclass="report-block-list"
+ >
+ <report-item
+ v-for="(wrapped, index) in issuesWithState"
+ :key="index"
+ :issue="wrapped.issue"
+ :status="wrapped.status"
:component="component"
- :issues="resolvedIssues"
- :status="$options.success"
- class="js-mr-code-resolved-issues"
+ :is-new="wrapped.isNew"
/>
- </div>
+ </smart-virtual-list>
</template>
diff --git a/app/assets/javascripts/reports/components/report_issues.vue b/app/assets/javascripts/reports/components/report_item.vue
index a2a03945ae3..01e6d357a21 100644
--- a/app/assets/javascripts/reports/components/report_issues.vue
+++ b/app/assets/javascripts/reports/components/report_item.vue
@@ -3,14 +3,14 @@ import IssueStatusIcon from '~/reports/components/issue_status_icon.vue';
import { components, componentNames } from '~/reports/components/issue_body';
export default {
- name: 'ReportIssues',
+ name: 'ReportItem',
components: {
IssueStatusIcon,
...components,
},
props: {
- issues: {
- type: Array,
+ issue: {
+ type: Object,
required: true,
},
component: {
@@ -33,27 +33,21 @@ export default {
};
</script>
<template>
- <div>
- <ul class="report-block-list">
- <li
- v-for="(issue, index) in issues"
- :key="index"
- :class="{ 'is-dismissed': issue.isDismissed }"
- class="report-block-list-issue"
- >
- <issue-status-icon
- :status="issue.status || status"
- class="append-right-5"
- />
+ <li
+ :class="{ 'is-dismissed': issue.isDismissed }"
+ class="report-block-list-issue"
+ >
+ <issue-status-icon
+ :status="status"
+ class="append-right-5"
+ />
- <component
- :is="component"
- v-if="component"
- :issue="issue"
- :status="issue.status || status"
- :is-new="isNew"
- />
- </li>
- </ul>
- </div>
+ <component
+ :is="component"
+ v-if="component"
+ :issue="issue"
+ :status="status"
+ :is-new="isNew"
+ />
+ </li>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/smart_virtual_list.vue b/app/assets/javascripts/vue_shared/components/smart_virtual_list.vue
new file mode 100644
index 00000000000..63034a45f77
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/smart_virtual_list.vue
@@ -0,0 +1,42 @@
+<script>
+import VirtualList from 'vue-virtual-scroll-list';
+
+export default {
+ name: 'SmartVirtualList',
+ components: { VirtualList },
+ props: {
+ size: { type: Number, required: true },
+ length: { type: Number, required: true },
+ remain: { type: Number, required: true },
+ rtag: { type: String, default: 'div' },
+ wtag: { type: String, default: 'div' },
+ wclass: { type: String, default: null },
+ },
+};
+</script>
+<template>
+ <virtual-list
+ v-if="length > remain"
+ v-bind="$attrs"
+ :size="remain"
+ :remain="remain"
+ :rtag="rtag"
+ :wtag="wtag"
+ :wclass="wclass"
+ class="js-virtual-list"
+ >
+ <slot></slot>
+ </virtual-list>
+ <component
+ :is="rtag"
+ v-else
+ class="js-plain-element"
+ >
+ <component
+ :is="wtag"
+ :class="wclass"
+ >
+ <slot></slot>
+ </component>
+ </component>
+</template>