Skip to content

Commit d19a6f6

Browse files
committed
Merge branch '7737-ci-pipeline-view-slowed-down-massivly-if-security-tabs-has-many-entries' into 'master'
Improve performance of rendering large reports See merge request gitlab-org/gitlab-ce!22835
2 parents 7a72616 + 26ab92d commit d19a6f6

File tree

7 files changed

+197
-68
lines changed

7 files changed

+197
-68
lines changed
Lines changed: 45 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,31 @@
11
<script>
2-
import IssuesBlock from '~/reports/components/report_issues.vue';
3-
import { STATUS_SUCCESS, STATUS_FAILED, STATUS_NEUTRAL } from '~/reports/constants';
2+
import ReportItem from '~/reports/components/report_item.vue';
3+
import { STATUS_FAILED, STATUS_NEUTRAL, STATUS_SUCCESS } from '~/reports/constants';
4+
import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue';
5+
6+
const wrapIssueWithState = (status, isNew = false) => issue => ({
7+
status: issue.status || status,
8+
isNew,
9+
issue,
10+
});
411
512
/**
613
* Renders block of issues
714
*/
8-
915
export default {
1016
components: {
11-
IssuesBlock,
17+
SmartVirtualList,
18+
ReportItem,
1219
},
13-
success: STATUS_SUCCESS,
14-
failed: STATUS_FAILED,
15-
neutral: STATUS_NEUTRAL,
20+
// Typical height of a report item in px
21+
typicalReportItemHeight: 32,
22+
/*
23+
The maximum amount of shown issues. This is calculated by
24+
( max-height of report-block-list / typicalReportItemHeight ) + some safety margin
25+
We will use VirtualList if we have more items than this number.
26+
For entries lower than this number, the virtual scroll list calculates the total height of the element wrongly.
27+
*/
28+
maxShownReportItems: 20,
1629
props: {
1730
newIssues: {
1831
type: Array,
@@ -40,42 +53,34 @@ export default {
4053
default: '',
4154
},
4255
},
56+
computed: {
57+
issuesWithState() {
58+
return [
59+
...this.newIssues.map(wrapIssueWithState(STATUS_FAILED, true)),
60+
...this.unresolvedIssues.map(wrapIssueWithState(STATUS_FAILED)),
61+
...this.neutralIssues.map(wrapIssueWithState(STATUS_NEUTRAL)),
62+
...this.resolvedIssues.map(wrapIssueWithState(STATUS_SUCCESS)),
63+
];
64+
},
65+
},
4366
};
4467
</script>
4568
<template>
46-
<div class="report-block-container">
47-
48-
<issues-block
49-
v-if="newIssues.length"
50-
:component="component"
51-
:issues="newIssues"
52-
class="js-mr-code-new-issues"
53-
status="failed"
54-
is-new
55-
/>
56-
57-
<issues-block
58-
v-if="unresolvedIssues.length"
59-
:component="component"
60-
:issues="unresolvedIssues"
61-
:status="$options.failed"
62-
class="js-mr-code-new-issues"
63-
/>
64-
65-
<issues-block
66-
v-if="neutralIssues.length"
67-
:component="component"
68-
:issues="neutralIssues"
69-
:status="$options.neutral"
70-
class="js-mr-code-non-issues"
71-
/>
72-
73-
<issues-block
74-
v-if="resolvedIssues.length"
69+
<smart-virtual-list
70+
:length="issuesWithState.length"
71+
:remain="$options.maxShownReportItems"
72+
:size="$options.typicalReportItemHeight"
73+
class="report-block-container"
74+
wtag="ul"
75+
wclass="report-block-list"
76+
>
77+
<report-item
78+
v-for="(wrapped, index) in issuesWithState"
79+
:key="index"
80+
:issue="wrapped.issue"
81+
:status="wrapped.status"
7582
:component="component"
76-
:issues="resolvedIssues"
77-
:status="$options.success"
78-
class="js-mr-code-resolved-issues"
83+
:is-new="wrapped.isNew"
7984
/>
80-
</div>
85+
</smart-virtual-list>
8186
</template>

app/assets/javascripts/reports/components/report_issues.vue renamed to app/assets/javascripts/reports/components/report_item.vue

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ import IssueStatusIcon from '~/reports/components/issue_status_icon.vue';
33
import { components, componentNames } from '~/reports/components/issue_body';
44
55
export default {
6-
name: 'ReportIssues',
6+
name: 'ReportItem',
77
components: {
88
IssueStatusIcon,
99
...components,
1010
},
1111
props: {
12-
issues: {
13-
type: Array,
12+
issue: {
13+
type: Object,
1414
required: true,
1515
},
1616
component: {
@@ -33,27 +33,21 @@ export default {
3333
};
3434
</script>
3535
<template>
36-
<div>
37-
<ul class="report-block-list">
38-
<li
39-
v-for="(issue, index) in issues"
40-
:key="index"
41-
:class="{ 'is-dismissed': issue.isDismissed }"
42-
class="report-block-list-issue"
43-
>
44-
<issue-status-icon
45-
:status="issue.status || status"
46-
class="append-right-5"
47-
/>
36+
<li
37+
:class="{ 'is-dismissed': issue.isDismissed }"
38+
class="report-block-list-issue"
39+
>
40+
<issue-status-icon
41+
:status="status"
42+
class="append-right-5"
43+
/>
4844

49-
<component
50-
:is="component"
51-
v-if="component"
52-
:issue="issue"
53-
:status="issue.status || status"
54-
:is-new="isNew"
55-
/>
56-
</li>
57-
</ul>
58-
</div>
45+
<component
46+
:is="component"
47+
v-if="component"
48+
:issue="issue"
49+
:status="status"
50+
:is-new="isNew"
51+
/>
52+
</li>
5953
</template>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<script>
2+
import VirtualList from 'vue-virtual-scroll-list';
3+
4+
export default {
5+
name: 'SmartVirtualList',
6+
components: { VirtualList },
7+
props: {
8+
size: { type: Number, required: true },
9+
length: { type: Number, required: true },
10+
remain: { type: Number, required: true },
11+
rtag: { type: String, default: 'div' },
12+
wtag: { type: String, default: 'div' },
13+
wclass: { type: String, default: null },
14+
},
15+
};
16+
</script>
17+
<template>
18+
<virtual-list
19+
v-if="length > remain"
20+
v-bind="$attrs"
21+
:size="remain"
22+
:remain="remain"
23+
:rtag="rtag"
24+
:wtag="wtag"
25+
:wclass="wclass"
26+
class="js-virtual-list"
27+
>
28+
<slot></slot>
29+
</virtual-list>
30+
<component
31+
:is="rtag"
32+
v-else
33+
class="js-plain-element"
34+
>
35+
<component
36+
:is="wtag"
37+
:class="wclass"
38+
>
39+
<slot></slot>
40+
</component>
41+
</component>
42+
</template>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
title: Improve performance of rendering large reports
3+
merge_request: 22835
4+
author:
5+
type: performance

spec/javascripts/reports/components/grouped_test_reports_app_spec.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,11 @@ describe('Grouped Test Reports App', () => {
151151

152152
it('renders resolved failures', done => {
153153
setTimeout(() => {
154-
expect(vm.$el.querySelector('.js-mr-code-resolved-issues').textContent).toContain(
154+
expect(vm.$el.querySelector('.report-block-container').textContent).toContain(
155155
resolvedFailures.suites[0].resolved_failures[0].name,
156156
);
157157

158-
expect(vm.$el.querySelector('.js-mr-code-resolved-issues').textContent).toContain(
158+
expect(vm.$el.querySelector('.report-block-container').textContent).toContain(
159159
resolvedFailures.suites[0].resolved_failures[1].name,
160160
);
161161
done();

spec/javascripts/reports/components/report_section_spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ describe('Report section', () => {
120120
'Code quality improved on 1 point and degraded on 1 point',
121121
);
122122

123-
expect(vm.$el.querySelectorAll('.js-mr-code-resolved-issues li').length).toEqual(
123+
expect(vm.$el.querySelectorAll('.report-block-container li').length).toEqual(
124124
resolvedIssues.length,
125125
);
126126
});
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import Vue from 'vue';
2+
import SmartVirtualScrollList from '~/vue_shared/components/smart_virtual_list.vue';
3+
import mountComponent from 'spec/helpers/vue_mount_component_helper';
4+
5+
describe('Toggle Button', () => {
6+
let vm;
7+
8+
const createComponent = ({ length, remain }) => {
9+
const smartListProperties = {
10+
rtag: 'section',
11+
wtag: 'ul',
12+
wclass: 'test-class',
13+
// Size in pixels does not matter for our tests here
14+
size: 35,
15+
length,
16+
remain,
17+
};
18+
19+
const Component = Vue.extend({
20+
components: {
21+
SmartVirtualScrollList,
22+
},
23+
smartListProperties,
24+
items: Array(length).fill(1),
25+
template: `
26+
<smart-virtual-scroll-list v-bind="$options.smartListProperties">
27+
<li v-for="(val, key) in $options.items" :key="key">{{ key + 1 }}</li>
28+
</smart-virtual-scroll-list>`,
29+
});
30+
31+
return mountComponent(Component);
32+
};
33+
34+
afterEach(() => {
35+
vm.$destroy();
36+
});
37+
38+
describe('if the list is shorter than the maximum shown elements', () => {
39+
const listLength = 10;
40+
41+
beforeEach(() => {
42+
vm = createComponent({ length: listLength, remain: 20 });
43+
});
44+
45+
it('renders without the vue-virtual-scroll-list component', () => {
46+
expect(vm.$el.classList).not.toContain('js-virtual-list');
47+
expect(vm.$el.classList).toContain('js-plain-element');
48+
});
49+
50+
it('renders list with provided tags and classes for the wrapper elements', () => {
51+
expect(vm.$el.tagName).toEqual('SECTION');
52+
expect(vm.$el.firstChild.tagName).toEqual('UL');
53+
expect(vm.$el.firstChild.classList).toContain('test-class');
54+
});
55+
56+
it('renders all children list elements', () => {
57+
expect(vm.$el.querySelectorAll('li').length).toEqual(listLength);
58+
});
59+
});
60+
61+
describe('if the list is longer than the maximum shown elements', () => {
62+
const maxItemsShown = 20;
63+
64+
beforeEach(() => {
65+
vm = createComponent({ length: 1000, remain: maxItemsShown });
66+
});
67+
68+
it('uses the vue-virtual-scroll-list component', () => {
69+
expect(vm.$el.classList).toContain('js-virtual-list');
70+
expect(vm.$el.classList).not.toContain('js-plain-element');
71+
});
72+
73+
it('renders list with provided tags and classes for the wrapper elements', () => {
74+
expect(vm.$el.tagName).toEqual('SECTION');
75+
expect(vm.$el.firstChild.tagName).toEqual('UL');
76+
expect(vm.$el.firstChild.classList).toContain('test-class');
77+
});
78+
79+
it('renders at max twice the maximum shown elements', () => {
80+
expect(vm.$el.querySelectorAll('li').length).toBeLessThanOrEqual(2 * maxItemsShown);
81+
});
82+
});
83+
});

0 commit comments

Comments
 (0)