Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
c335a26b
Commit
c335a26b
authored
Mar 18, 2021
by
Quang-Minh Nguyen
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Allow to sort the details chronologically
Issue
https://gitlab.com/gitlab-org/gitlab/-/issues/324649
parent
28f1bf87
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
251 additions
and
46 deletions
+251
-46
app/assets/javascripts/performance_bar/components/detailed_metric.vue
...avascripts/performance_bar/components/detailed_metric.vue
+58
-12
spec/frontend/performance_bar/components/detailed_metric_spec.js
...ontend/performance_bar/components/detailed_metric_spec.js
+193
-34
No files found.
app/assets/javascripts/performance_bar/components/detailed_metric.vue
View file @
c335a26b
<
script
>
<
script
>
import
{
GlButton
,
GlModal
,
GlModalDirective
}
from
'
@gitlab/ui
'
;
import
{
GlButton
,
GlModal
,
GlModalDirective
,
GlSegmentedControl
}
from
'
@gitlab/ui
'
;
import
{
s__
}
from
'
~/locale
'
;
import
RequestWarning
from
'
./request_warning.vue
'
;
import
RequestWarning
from
'
./request_warning.vue
'
;
export
const
SortOrderDuration
=
'
SortOrderDuration
'
;
export
const
SortOrderChronological
=
'
SortOrderChronological
'
;
export
default
{
export
default
{
components
:
{
components
:
{
RequestWarning
,
RequestWarning
,
GlButton
,
GlButton
,
GlModal
,
GlModal
,
GlSegmentedControl
,
},
},
directives
:
{
directives
:
{
'
gl-modal
'
:
GlModalDirective
,
'
gl-modal
'
:
GlModalDirective
,
...
@@ -39,6 +45,7 @@ export default {
...
@@ -39,6 +45,7 @@ export default {
data
()
{
data
()
{
return
{
return
{
openedBacktraces
:
[],
openedBacktraces
:
[],
sortOrder
:
SortOrderDuration
,
};
};
},
},
computed
:
{
computed
:
{
...
@@ -60,12 +67,32 @@ export default {
...
@@ -60,12 +67,32 @@ export default {
return
summary
;
return
summary
;
},
},
metricDetailsLabel
()
{
metricDetailsLabel
()
{
return
this
.
metricDetails
.
duration
if
(
this
.
metricDetails
.
duration
&&
this
.
metricDetails
.
calls
)
{
?
`
${
this
.
metricDetails
.
duration
}
/
${
this
.
metricDetails
.
calls
}
`
return
`
${
this
.
metricDetails
.
duration
}
/
${
this
.
metricDetails
.
calls
}
`
;
:
this
.
metricDetails
.
calls
;
}
else
if
(
this
.
metricDetails
.
calls
)
{
return
this
.
metricDetails
.
calls
;
}
return
'
0
'
;
},
displaySortOrder
()
{
return
(
this
.
metricDetails
.
details
.
length
!==
0
&&
this
.
metricDetails
.
details
.
every
((
item
)
=>
item
.
start
)
);
},
},
detailsList
()
{
detailsList
()
{
return
this
.
metricDetails
.
details
;
const
list
=
this
.
metricDetails
.
details
.
slice
()
.
map
((
item
,
index
)
=>
({
...
item
,
id
:
index
}));
if
(
this
.
sortOrder
===
SortOrderDuration
)
{
return
list
.
sort
((
a
,
b
)
=>
(
a
.
duration
<
b
.
duration
?
1
:
-
1
));
}
else
if
(
this
.
sortOrder
===
SortOrderChronological
)
{
return
list
.
sort
((
a
,
b
)
=>
(
a
.
start
<
b
.
start
?
-
1
:
1
));
}
return
list
;
},
},
warnings
()
{
warnings
()
{
return
this
.
metricDetails
.
warnings
||
[];
return
this
.
metricDetails
.
warnings
||
[];
...
@@ -93,7 +120,14 @@ export default {
...
@@ -93,7 +120,14 @@ export default {
itemHasOpenedBacktrace
(
toggledIndex
)
{
itemHasOpenedBacktrace
(
toggledIndex
)
{
return
this
.
openedBacktraces
.
find
((
openedIndex
)
=>
openedIndex
===
toggledIndex
)
>=
0
;
return
this
.
openedBacktraces
.
find
((
openedIndex
)
=>
openedIndex
===
toggledIndex
)
>=
0
;
},
},
changeSortOrder
(
order
)
{
this
.
sortOrder
=
order
;
},
},
},
sortOrders
:
[
{
value
:
SortOrderDuration
,
text
:
s__
(
'
PerformanceBar|Sort by duration
'
)
},
{
value
:
SortOrderChronological
,
text
:
s__
(
'
PerformanceBar|Sort chronologically
'
)
},
],
};
};
</
script
>
</
script
>
<
template
>
<
template
>
...
@@ -104,7 +138,12 @@ export default {
...
@@ -104,7 +138,12 @@ export default {
data-qa-selector="detailed_metric_content"
data-qa-selector="detailed_metric_content"
>
>
<gl-button
v-gl-modal=
"modalId"
class=
"gl-mr-2"
type=
"button"
variant=
"link"
>
<gl-button
v-gl-modal=
"modalId"
class=
"gl-mr-2"
type=
"button"
variant=
"link"
>
<span
class=
"gl-text-blue-300 gl-font-weight-bold"
>
{{
metricDetailsLabel
}}
</span>
<span
class=
"gl-text-blue-300 gl-font-weight-bold"
data-testid=
"performance-bar-details-label"
>
{{
metricDetailsLabel
}}
</span>
</gl-button>
</gl-button>
<gl-modal
:modal-id=
"modalId"
:title=
"header"
size=
"lg"
footer-class=
"d-none"
scrollable
>
<gl-modal
:modal-id=
"modalId"
:title=
"header"
size=
"lg"
footer-class=
"d-none"
scrollable
>
<div
class=
"gl-display-flex gl-align-items-center gl-justify-content-space-between"
>
<div
class=
"gl-display-flex gl-align-items-center gl-justify-content-space-between"
>
...
@@ -120,17 +159,24 @@ export default {
...
@@ -120,17 +159,24 @@ export default {
<div
class=
"gl-font-size-h1 gl-font-weight-bold"
>
{{
value
}}
</div>
<div
class=
"gl-font-size-h1 gl-font-weight-bold"
>
{{
value
}}
</div>
</div>
</div>
</div>
</div>
<gl-segmented-control
v-if=
"displaySortOrder"
data-testid=
"performance-bar-sort-order"
:options=
"$options.sortOrders"
:checked=
"sortOrder"
@
input=
"changeSortOrder"
/>
</div>
</div>
<hr
/>
<hr
/>
<table
class=
"table gl-table"
>
<table
class=
"table gl-table"
>
<template
v-if=
"detailsList.length"
>
<template
v-if=
"detailsList.length"
>
<tr
v-for=
"
(item, index) in detailsList"
:key=
"index
"
>
<tr
v-for=
"
item in detailsList"
:key=
"item.id
"
>
<td>
<td
data-testid=
"performance-item-duration"
>
<span
v-if=
"item.duration"
>
{{
<span
v-if=
"item.duration"
>
{{
sprintf
(
__
(
'
%{duration
}
ms
'
),
{
duration
:
item
.
duration
}
)
sprintf
(
__
(
'
%{duration
}
ms
'
),
{
duration
:
item
.
duration
}
)
}}
<
/span
>
}}
<
/span
>
<
/td
>
<
/td
>
<
td
>
<
td
data
-
testid
=
"
performance-item-content
"
>
<
div
>
<
div
>
<
div
<
div
v
-
for
=
"
(key, keyIndex) in keys
"
v
-
for
=
"
(key, keyIndex) in keys
"
...
@@ -147,12 +193,12 @@ export default {
...
@@ -147,12 +193,12 @@ export default {
variant
=
"
default
"
variant
=
"
default
"
icon
=
"
ellipsis_h
"
icon
=
"
ellipsis_h
"
size
=
"
small
"
size
=
"
small
"
:
selected
=
"
itemHasOpenedBacktrace(i
ndex
)
"
:
selected
=
"
itemHasOpenedBacktrace(i
tem.id
)
"
:
aria
-
label
=
"
__('Toggle backtrace')
"
:
aria
-
label
=
"
__('Toggle backtrace')
"
@
click
=
"
toggleBacktrace(i
ndex
)
"
@
click
=
"
toggleBacktrace(i
tem.id
)
"
/>
/>
<
/div
>
<
/div
>
<
pre
v
-
if
=
"
itemHasOpenedBacktrace(i
ndex
)
"
class
=
"
backtrace-row mt-2
"
>
{{
<
pre
v
-
if
=
"
itemHasOpenedBacktrace(i
tem.id
)
"
class
=
"
backtrace-row mt-2
"
>
{{
item
.
backtrace
item
.
backtrace
}}
<
/pre
>
}}
<
/pre
>
<
/div
>
<
/div
>
...
...
spec/frontend/performance_bar/components/detailed_metric_spec.js
View file @
c335a26b
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
nextTick
}
from
'
vue
'
;
import
{
nextTick
}
from
'
vue
'
;
import
{
trimText
}
from
'
helpers/text_helper
'
;
import
{
trimText
}
from
'
helpers/text_helper
'
;
import
DetailedMetric
from
'
~/performance_bar/components/detailed_metric.vue
'
;
import
{
extendedWrapper
}
from
'
helpers/vue_test_utils_helper
'
;
import
DetailedMetric
,
{
SortOrderDuration
,
SortOrderChronological
,
}
from
'
~/performance_bar/components/detailed_metric.vue
'
;
import
RequestWarning
from
'
~/performance_bar/components/request_warning.vue
'
;
import
RequestWarning
from
'
~/performance_bar/components/request_warning.vue
'
;
describe
(
'
detailedMetric
'
,
()
=>
{
describe
(
'
detailedMetric
'
,
()
=>
{
let
wrapper
;
let
wrapper
;
const
createComponent
=
(
props
)
=>
{
const
createComponent
=
(
props
)
=>
{
wrapper
=
shallowMount
(
DetailedMetric
,
{
wrapper
=
extendedWrapper
(
shallowMount
(
DetailedMetric
,
{
propsData
:
{
propsData
:
{
...
props
,
...
props
,
},
},
});
}),
);
};
};
const
findAllTraceBlocks
=
()
=>
wrapper
.
findAll
(
'
pre
'
);
const
findAllTraceBlocks
=
()
=>
wrapper
.
findAll
(
'
pre
'
);
const
findTraceBlockAtIndex
=
(
index
)
=>
findAllTraceBlocks
().
at
(
index
);
const
findTraceBlockAtIndex
=
(
index
)
=>
findAllTraceBlocks
().
at
(
index
);
const
findExpandBacktraceBtns
=
()
=>
wrapper
.
findAll
(
'
[data-testid="backtrace-expand-btn"]
'
);
const
findExpandBacktraceBtns
=
()
=>
wrapper
.
findAll
(
'
[data-testid="backtrace-expand-btn"]
'
);
const
findExpandedBacktraceBtnAtIndex
=
(
index
)
=>
findExpandBacktraceBtns
().
at
(
index
);
const
findExpandedBacktraceBtnAtIndex
=
(
index
)
=>
findExpandBacktraceBtns
().
at
(
index
);
const
findDetailsLabel
=
()
=>
wrapper
.
findByTestId
(
'
performance-bar-details-label
'
);
const
findSortOrderSwitcher
=
()
=>
wrapper
.
findByTestId
(
'
performance-bar-sort-order
'
);
const
findAllDetailDurations
=
()
=>
wrapper
.
findAll
(
'
[data-testid="performance-item-duration"]
'
).
wrappers
.
map
((
w
)
=>
w
.
text
());
const
findAllSummaryItems
=
()
=>
wrapper
.
findAll
(
'
[data-testid="performance-bar-summary-item"]
'
).
wrappers
.
map
((
w
)
=>
w
.
text
());
afterEach
(()
=>
{
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
.
destroy
();
...
@@ -42,25 +54,61 @@ describe('detailedMetric', () => {
...
@@ -42,25 +54,61 @@ describe('detailedMetric', () => {
describe
(
'
when the current request has details
'
,
()
=>
{
describe
(
'
when the current request has details
'
,
()
=>
{
const
requestDetails
=
[
const
requestDetails
=
[
{
duration
:
'
100
'
,
feature
:
'
find_commit
'
,
request
:
'
abcdef
'
,
backtrace
:
[
'
hello
'
,
'
world
'
]
},
{
{
duration
:
'
23
'
,
duration
:
23
,
feature
:
'
rebase_in_progress
'
,
feature
:
'
rebase_in_progress
'
,
request
:
''
,
request
:
''
,
backtrace
:
[
'
other
'
,
'
example
'
],
backtrace
:
[
'
other
'
,
'
example
'
],
},
},
{
duration
:
100
,
feature
:
'
find_commit
'
,
request
:
'
abcdef
'
,
backtrace
:
[
'
hello
'
,
'
world
'
]
},
];
];
describe
(
'
with a default metric name
'
,
()
=>
{
describe
(
'
with an empty detail
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
({
currentRequest
:
{
details
:
{
gitaly
:
{
duration
:
'
0ms
'
,
calls
:
0
,
details
:
[],
warnings
:
[],
},
},
},
metric
:
'
gitaly
'
,
header
:
'
Gitaly calls
'
,
keys
:
[
'
feature
'
,
'
request
'
],
});
});
it
(
'
displays an empty title
'
,
()
=>
{
expect
(
findDetailsLabel
().
text
()).
toContain
(
'
0
'
);
});
it
(
'
displays an empty modal
'
,
()
=>
{
expect
(
wrapper
.
text
().
replace
(
/
\s
+/g
,
'
'
)).
toContain
(
'
No gitaly calls for this request
'
);
});
it
(
'
does not display sort by switcher
'
,
()
=>
{
expect
(
findSortOrderSwitcher
().
exists
()).
toBe
(
false
);
});
});
describe
(
'
when the details have a summary field
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
createComponent
({
createComponent
({
currentRequest
:
{
currentRequest
:
{
details
:
{
details
:
{
gitaly
:
{
gitaly
:
{
duration
:
'
123ms
'
,
duration
:
'
123ms
'
,
calls
:
'
456
'
,
calls
:
456
,
details
:
requestDetails
,
details
:
requestDetails
,
warnings
:
[
'
gitaly calls: 456 over 30
'
],
warnings
:
[
'
gitaly calls: 456 over 30
'
],
summary
:
{
'
In controllers
'
:
100
,
'
In middlewares
'
:
20
,
},
},
},
},
},
},
},
...
@@ -70,8 +118,53 @@ describe('detailedMetric', () => {
...
@@ -70,8 +118,53 @@ describe('detailedMetric', () => {
});
});
});
});
it
(
'
displays details
'
,
()
=>
{
it
(
'
displays a summary section
'
,
()
=>
{
expect
(
wrapper
.
text
().
replace
(
/
\s
+/g
,
'
'
)).
toContain
(
'
123ms / 456
'
);
expect
(
findAllSummaryItems
()).
toEqual
([
'
Total 456
'
,
'
Total duration 123ms
'
,
'
In controllers 100
'
,
'
In middlewares 20
'
,
]);
});
});
describe
(
"
when the details don't have a start field
"
,
()
=>
{
beforeEach
(()
=>
{
createComponent
({
currentRequest
:
{
details
:
{
gitaly
:
{
duration
:
'
123ms
'
,
calls
:
456
,
details
:
requestDetails
,
warnings
:
[
'
gitaly calls: 456 over 30
'
],
},
},
},
metric
:
'
gitaly
'
,
header
:
'
Gitaly calls
'
,
keys
:
[
'
feature
'
,
'
request
'
],
});
});
it
(
'
displays details header
'
,
()
=>
{
expect
(
findDetailsLabel
().
text
()).
toContain
(
'
123ms / 456
'
);
});
it
(
'
displays a basic summary section
'
,
()
=>
{
expect
(
findAllSummaryItems
()).
toEqual
([
'
Total 456
'
,
'
Total duration 123ms
'
]);
});
it
(
'
sorts the details by descending duration order
'
,
()
=>
{
expect
(
wrapper
.
findAll
(
'
[data-testid="performance-item-duration"]
'
)
.
wrappers
.
map
((
w
)
=>
w
.
text
()),
).
toEqual
([
'
100ms
'
,
'
23ms
'
]);
});
it
(
'
does not display sort by switcher
'
,
()
=>
{
expect
(
findSortOrderSwitcher
().
exists
()).
toBe
(
false
);
});
});
it
(
'
adds a modal with a table of the details
'
,
()
=>
{
it
(
'
adds a modal with a table of the details
'
,
()
=>
{
...
@@ -119,17 +212,75 @@ describe('detailedMetric', () => {
...
@@ -119,17 +212,75 @@ describe('detailedMetric', () => {
findExpandedBacktraceBtnAtIndex
(
0
).
vm
.
$emit
(
'
click
'
);
findExpandedBacktraceBtnAtIndex
(
0
).
vm
.
$emit
(
'
click
'
);
await
nextTick
();
await
nextTick
();
expect
(
findAllTraceBlocks
()).
toHaveLength
(
1
);
expect
(
findAllTraceBlocks
()).
toHaveLength
(
1
);
expect
(
findTraceBlockAtIndex
(
0
).
text
()).
toContain
(
requestDetails
[
0
].
backtrace
[
0
]);
expect
(
findTraceBlockAtIndex
(
0
).
text
()).
toContain
(
requestDetails
[
1
].
backtrace
[
0
]);
secondExpandButton
.
vm
.
$emit
(
'
click
'
);
secondExpandButton
.
vm
.
$emit
(
'
click
'
);
await
nextTick
();
await
nextTick
();
expect
(
findAllTraceBlocks
()).
toHaveLength
(
2
);
expect
(
findAllTraceBlocks
()).
toHaveLength
(
2
);
expect
(
findTraceBlockAtIndex
(
1
).
text
()).
toContain
(
requestDetails
[
1
].
backtrace
[
0
]);
expect
(
findTraceBlockAtIndex
(
1
).
text
()).
toContain
(
requestDetails
[
0
].
backtrace
[
0
]);
secondExpandButton
.
vm
.
$emit
(
'
click
'
);
secondExpandButton
.
vm
.
$emit
(
'
click
'
);
await
nextTick
();
await
nextTick
();
expect
(
findAllTraceBlocks
()).
toHaveLength
(
1
);
expect
(
findAllTraceBlocks
()).
toHaveLength
(
1
);
expect
(
findTraceBlockAtIndex
(
0
).
text
()).
toContain
(
requestDetails
[
0
].
backtrace
[
0
]);
expect
(
findTraceBlockAtIndex
(
0
).
text
()).
toContain
(
requestDetails
[
1
].
backtrace
[
0
]);
});
});
describe
(
'
when the details have a start field
'
,
()
=>
{
const
requestDetailsWithStart
=
[
{
start
:
'
2021-03-18 11:41:49.846356 +0700
'
,
duration
:
23
,
feature
:
'
rebase_in_progress
'
,
request
:
''
,
},
{
start
:
'
2021-03-18 11:42:11.645711 +0700
'
,
duration
:
75
,
feature
:
'
find_commit
'
,
request
:
'
abcdef
'
,
},
{
start
:
'
2021-03-18 11:42:10.645711 +0700
'
,
duration
:
100
,
feature
:
'
find_commit
'
,
request
:
'
abcdef
'
,
},
];
beforeEach
(()
=>
{
createComponent
({
currentRequest
:
{
details
:
{
gitaly
:
{
duration
:
'
123ms
'
,
calls
:
456
,
details
:
requestDetailsWithStart
,
warnings
:
[
'
gitaly calls: 456 over 30
'
],
},
},
},
metric
:
'
gitaly
'
,
header
:
'
Gitaly calls
'
,
keys
:
[
'
feature
'
,
'
request
'
],
});
});
it
(
'
sorts the details by descending duration order
'
,
()
=>
{
expect
(
findAllDetailDurations
()).
toEqual
([
'
100ms
'
,
'
75ms
'
,
'
23ms
'
]);
});
it
(
'
displays sort by switcher
'
,
()
=>
{
expect
(
findSortOrderSwitcher
().
exists
()).
toBe
(
true
);
});
it
(
'
allows switch sorting orders
'
,
async
()
=>
{
findSortOrderSwitcher
().
vm
.
$emit
(
'
input
'
,
SortOrderChronological
);
await
nextTick
();
expect
(
findAllDetailDurations
()).
toEqual
([
'
23ms
'
,
'
100ms
'
,
'
75ms
'
]);
findSortOrderSwitcher
().
vm
.
$emit
(
'
input
'
,
SortOrderDuration
);
await
nextTick
();
expect
(
findAllDetailDurations
()).
toEqual
([
'
100ms
'
,
'
75ms
'
,
'
23ms
'
]);
});
});
});
});
...
@@ -156,7 +307,6 @@ describe('detailedMetric', () => {
...
@@ -156,7 +307,6 @@ describe('detailedMetric', () => {
expect
(
wrapper
.
text
()).
toContain
(
'
custom
'
);
expect
(
wrapper
.
text
()).
toContain
(
'
custom
'
);
});
});
});
});
});
describe
(
'
when the details has no duration
'
,
()
=>
{
describe
(
'
when the details has no duration
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
...
@@ -175,12 +325,21 @@ describe('detailedMetric', () => {
...
@@ -175,12 +325,21 @@ describe('detailedMetric', () => {
});
});
});
});
it
(
'
displays calls in the label
'
,
()
=>
{
expect
(
findDetailsLabel
().
text
()).
toContain
(
'
456
'
);
});
it
(
'
displays a basic summary section
'
,
()
=>
{
expect
(
findAllSummaryItems
()).
toEqual
([
'
Total 456
'
]);
});
it
(
'
renders only the number of calls
'
,
async
()
=>
{
it
(
'
renders only the number of calls
'
,
async
()
=>
{
expect
(
trimText
(
wrapper
.
text
())).
toEqual
(
'
456
notification bullet
'
);
expect
(
trimText
(
wrapper
.
text
())).
toContain
(
'
notification bullet
'
);
findExpandedBacktraceBtnAtIndex
(
0
).
vm
.
$emit
(
'
click
'
);
findExpandedBacktraceBtnAtIndex
(
0
).
vm
.
$emit
(
'
click
'
);
await
nextTick
();
await
nextTick
();
expect
(
trimText
(
wrapper
.
text
())).
toEqual
(
'
456 notification backtrace bullet
'
);
expect
(
trimText
(
wrapper
.
text
())).
toContain
(
'
notification backtrace bullet
'
);
});
});
});
});
});
});
});
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment