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
38b7f1ea
Commit
38b7f1ea
authored
Jan 25, 2022
by
wortschi
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add metric tile component
Changelog: added
parent
440a6f32
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
151 additions
and
52 deletions
+151
-52
app/assets/javascripts/cycle_analytics/components/metric_tile.vue
...ts/javascripts/cycle_analytics/components/metric_tile.vue
+51
-0
app/assets/javascripts/cycle_analytics/components/value_stream_metrics.vue
...ripts/cycle_analytics/components/value_stream_metrics.vue
+6
-20
ee/spec/features/groups/analytics/cycle_analytics/filters_and_data_spec.rb
...groups/analytics/cycle_analytics/filters_and_data_spec.rb
+1
-1
spec/features/cycle_analytics_spec.rb
spec/features/cycle_analytics_spec.rb
+1
-1
spec/frontend/cycle_analytics/metric_tile_spec.js
spec/frontend/cycle_analytics/metric_tile_spec.js
+81
-0
spec/frontend/cycle_analytics/value_stream_metrics_spec.js
spec/frontend/cycle_analytics/value_stream_metrics_spec.js
+11
-30
No files found.
app/assets/javascripts/cycle_analytics/components/metric_tile.vue
0 → 100644
View file @
38b7f1ea
<
script
>
import
{
GlSingleStat
}
from
'
@gitlab/ui/dist/charts
'
;
import
{
redirectTo
}
from
'
~/lib/utils/url_utility
'
;
import
MetricPopover
from
'
./metric_popover.vue
'
;
export
default
{
name
:
'
MetricTile
'
,
components
:
{
GlSingleStat
,
MetricPopover
,
},
props
:
{
metric
:
{
type
:
Object
,
required
:
true
,
},
},
computed
:
{
decimalPlaces
()
{
const
parsedFloat
=
parseFloat
(
this
.
metric
.
value
);
return
Number
.
isNaN
(
parsedFloat
)
||
Number
.
isInteger
(
parsedFloat
)
?
0
:
1
;
},
hasLinks
()
{
return
this
.
metric
.
links
?.
length
&&
this
.
metric
.
links
[
0
].
url
;
},
},
methods
:
{
clickHandler
({
links
})
{
if
(
this
.
hasLinks
)
{
redirectTo
(
links
[
0
].
url
);
}
},
},
};
</
script
>
<
template
>
<div
v-bind=
"$attrs"
>
<gl-single-stat
:id=
"metric.identifier"
:value=
"`$
{metric.value}`"
:title="metric.label"
:unit="metric.unit || ''"
:should-animate="true"
:animation-decimal-places="decimalPlaces"
:class="{ 'gl-hover-cursor-pointer': hasLinks }"
tabindex="0"
@click="clickHandler(metric)"
/>
<metric-popover
:metric=
"metric"
:target=
"metric.identifier"
/>
</div>
</
template
>
app/assets/javascripts/cycle_analytics/components/value_stream_metrics.vue
View file @
38b7f1ea
<
script
>
import
{
GlDeprecatedSkeletonLoading
as
GlSkeletonLoading
}
from
'
@gitlab/ui
'
;
import
{
GlSingleStat
}
from
'
@gitlab/ui/dist/charts
'
;
import
{
flatten
}
from
'
lodash
'
;
import
createFlash
from
'
~/flash
'
;
import
{
sprintf
,
s__
}
from
'
~/locale
'
;
import
{
redirectTo
}
from
'
~/lib/utils/url_utility
'
;
import
{
METRICS_POPOVER_CONTENT
}
from
'
../constants
'
;
import
{
removeFlash
,
prepareTimeMetricsData
}
from
'
../utils
'
;
import
Metric
Popover
from
'
./metric_popover
.vue
'
;
import
Metric
Tile
from
'
./metric_tile
.vue
'
;
const
requestData
=
({
request
,
endpoint
,
path
,
params
,
name
})
=>
{
return
request
({
endpoint
,
params
,
requestPath
:
path
})
...
...
@@ -33,9 +32,8 @@ const fetchMetricsData = (reqs = [], path, params) => {
export
default
{
name
:
'
ValueStreamMetrics
'
,
components
:
{
GlSingleStat
,
GlSkeletonLoading
,
Metric
Popover
,
Metric
Tile
,
},
props
:
{
requestPath
:
{
...
...
@@ -94,26 +92,14 @@ export default {
};
</
script
>
<
template
>
<div
class=
"gl-display-flex gl-flex-wrap"
data-testid=
"vsa-
time-
metrics"
>
<div
class=
"gl-display-flex gl-flex-wrap"
data-testid=
"vsa-metrics"
>
<gl-skeleton-loading
v-if=
"isLoading"
class=
"gl-h-auto gl-py-3 gl-pr-9 gl-my-6"
/>
<
div
<
metric-tile
v-for=
"metric in metrics"
v-show=
"!isLoading"
:key=
"metric.identifier"
:metric=
"metric"
class=
"gl-my-6 gl-pr-9"
>
<gl-single-stat
:id=
"metric.identifier"
:value=
"`$
{metric.value}`"
:title="metric.label"
:unit="metric.unit || ''"
:should-animate="true"
:animation-decimal-places="getDecimalPlaces(metric.value)"
:class="{ 'gl-hover-cursor-pointer': hasLinks(metric.links) }"
tabindex="0"
@click="clickHandler(metric)"
/>
<metric-popover
:metric=
"metric"
:target=
"metric.identifier"
/>
</div>
/>
</div>
</
template
>
ee/spec/features/groups/analytics/cycle_analytics/filters_and_data_spec.rb
View file @
38b7f1ea
...
...
@@ -19,7 +19,7 @@ RSpec.describe 'Group value stream analytics filters and data', :js do
path_nav_selector
=
'[data-testid="vsa-path-navigation"]'
filter_bar_selector
=
'[data-testid="vsa-filter-bar"]'
card_metric_selector
=
'[data-testid="vsa-
time-
metrics"] .gl-single-stat'
card_metric_selector
=
'[data-testid="vsa-metrics"] .gl-single-stat'
new_issues_count
=
3
new_issues_count
.
times
do
|
i
|
...
...
spec/features/cycle_analytics_spec.rb
View file @
38b7f1ea
...
...
@@ -11,7 +11,7 @@ RSpec.describe 'Value Stream Analytics', :js do
let_it_be
(
:stage_table_event_title_selector
)
{
'[data-testid="vsa-stage-event-title"]'
}
let_it_be
(
:stage_table_pagination_selector
)
{
'[data-testid="vsa-stage-pagination"]'
}
let_it_be
(
:stage_table_duration_column_header_selector
)
{
'[data-testid="vsa-stage-header-duration"]'
}
let_it_be
(
:metrics_selector
)
{
"[data-testid='vsa-
time-
metrics']"
}
let_it_be
(
:metrics_selector
)
{
"[data-testid='vsa-metrics']"
}
let_it_be
(
:metric_value_selector
)
{
"[data-testid='displayValue']"
}
let
(
:stage_table
)
{
find
(
stage_table_selector
)
}
...
...
spec/frontend/cycle_analytics/metric_tile_spec.js
0 → 100644
View file @
38b7f1ea
import
{
GlSingleStat
}
from
'
@gitlab/ui/dist/charts
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
MetricTile
from
'
~/cycle_analytics/components/metric_tile.vue
'
;
import
MetricPopover
from
'
~/cycle_analytics/components/metric_popover.vue
'
;
import
{
redirectTo
}
from
'
~/lib/utils/url_utility
'
;
jest
.
mock
(
'
~/lib/utils/url_utility
'
);
describe
(
'
MetricTile
'
,
()
=>
{
let
wrapper
;
const
createComponent
=
(
props
=
{})
=>
{
return
shallowMount
(
MetricTile
,
{
propsData
:
{
metric
:
{},
...
props
,
},
});
};
const
findSingleStat
=
()
=>
wrapper
.
findComponent
(
GlSingleStat
);
const
findPopover
=
()
=>
wrapper
.
findComponent
(
MetricPopover
);
afterEach
(()
=>
{
wrapper
.
destroy
();
});
describe
(
'
template
'
,
()
=>
{
describe
(
'
links
'
,
()
=>
{
it
(
'
when the metric has links, it redirects the user on click
'
,
()
=>
{
const
metric
=
{
identifier
:
'
deploys
'
,
value
:
'
10
'
,
label
:
'
Deploys
'
,
links
:
[{
url
:
'
foo/bar
'
}],
};
wrapper
=
createComponent
({
metric
});
const
singleStat
=
findSingleStat
();
singleStat
.
vm
.
$emit
(
'
click
'
);
expect
(
redirectTo
).
toHaveBeenCalledWith
(
'
foo/bar
'
);
});
it
(
"
when the metric doesn't have links, it won't the user on click
"
,
()
=>
{
const
metric
=
{
identifier
:
'
deploys
'
,
value
:
'
10
'
,
label
:
'
Deploys
'
};
wrapper
=
createComponent
({
metric
});
const
singleStat
=
findSingleStat
();
singleStat
.
vm
.
$emit
(
'
click
'
);
expect
(
redirectTo
).
not
.
toHaveBeenCalled
();
});
});
describe
(
'
decimal places
'
,
()
=>
{
it
(
`will render 0 decimal places for an integer value`
,
()
=>
{
const
metric
=
{
identifier
:
'
deploys
'
,
value
:
'
10
'
,
label
:
'
Deploys
'
};
wrapper
=
createComponent
({
metric
});
const
singleStat
=
findSingleStat
();
expect
(
singleStat
.
props
(
'
animationDecimalPlaces
'
)).
toBe
(
0
);
});
it
(
`will render 1 decimal place for a float value`
,
()
=>
{
const
metric
=
{
identifier
:
'
deploys
'
,
value
:
'
10.5
'
,
label
:
'
Deploys
'
};
wrapper
=
createComponent
({
metric
});
const
singleStat
=
findSingleStat
();
expect
(
singleStat
.
props
(
'
animationDecimalPlaces
'
)).
toBe
(
1
);
});
});
it
(
'
renders a metric popover
'
,
()
=>
{
const
metric
=
{
identifier
:
'
deploys
'
,
value
:
'
10
'
,
label
:
'
Deploys
'
};
wrapper
=
createComponent
({
metric
});
const
popover
=
findPopover
();
expect
(
popover
.
exists
()).
toBe
(
true
);
expect
(
popover
.
props
()).
toMatchObject
({
metric
,
target
:
metric
.
identifier
});
});
});
});
spec/frontend/cycle_analytics/value_stream_metrics_spec.js
View file @
38b7f1ea
import
{
GlDeprecatedSkeletonLoading
as
GlSkeletonLoading
}
from
'
@gitlab/ui
'
;
import
{
GlSingleStat
}
from
'
@gitlab/ui/dist/charts
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
nextTick
}
from
'
vue
'
;
import
metricsData
from
'
test_fixtures/projects/analytics/value_stream_analytics/summary.json
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
{
METRIC_TYPE_SUMMARY
}
from
'
~/api/analytics_api
'
;
import
ValueStreamMetrics
from
'
~/cycle_analytics/components/value_stream_metrics.vue
'
;
import
MetricTile
from
'
~/cycle_analytics/components/metric_tile.vue
'
;
import
createFlash
from
'
~/flash
'
;
import
{
redirectTo
}
from
'
~/lib/utils/url_utility
'
;
import
{
group
}
from
'
./mock_data
'
;
jest
.
mock
(
'
~/flash
'
);
jest
.
mock
(
'
~/lib/utils/url_utility
'
);
describe
(
'
ValueStreamMetrics
'
,
()
=>
{
let
wrapper
;
...
...
@@ -35,7 +33,7 @@ describe('ValueStreamMetrics', () => {
});
};
const
findMetrics
=
()
=>
wrapper
.
findAllComponents
(
GlSingleStat
);
const
findMetrics
=
()
=>
wrapper
.
findAllComponents
(
MetricTile
);
const
expectToHaveRequest
=
(
fields
)
=>
{
expect
(
mockGetValueStreamSummaryMetrics
).
toHaveBeenCalledWith
({
...
...
@@ -61,7 +59,7 @@ describe('ValueStreamMetrics', () => {
expect
(
wrapper
.
findComponent
(
GlSkeletonLoading
).
exists
()).
toBe
(
true
);
});
it
(
'
renders hidden
GlSingleStat
components for each metric
'
,
async
()
=>
{
it
(
'
renders hidden
MetricTile
components for each metric
'
,
async
()
=>
{
await
waitForPromises
();
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
...
...
@@ -89,34 +87,17 @@ describe('ValueStreamMetrics', () => {
});
describe
.
each
`
index |
value | title | unit | animationDecimalPlaces | clickable
${
0
}
|
${
metricsData
[
0
].
value
}
|
${
metricsData
[
0
].
title
}
|
${
metricsData
[
0
].
unit
}
|
${
0
}
|
${
fals
e
}
${
1
}
|
${
metricsData
[
1
].
value
}
|
${
metricsData
[
1
].
title
}
|
${
metricsData
[
1
].
unit
}
|
${
0
}
|
${
fals
e
}
${
2
}
|
${
metricsData
[
2
].
value
}
|
${
metricsData
[
2
].
title
}
|
${
metricsData
[
2
].
unit
}
|
${
0
}
|
${
fals
e
}
${
3
}
|
${
metricsData
[
3
].
value
}
|
${
metricsData
[
3
].
title
}
|
${
metricsData
[
3
].
unit
}
|
${
1
}
|
${
tru
e
}
`
(
'
metric tiles
'
,
({
i
ndex
,
value
,
title
,
unit
,
animationDecimalPlaces
,
clickable
})
=>
{
it
(
`renders a
single stat component for "
${
title
}
" with value and unit
`
,
()
=>
{
index |
identifier | value | label
${
0
}
|
${
metricsData
[
0
].
identifier
}
|
${
metricsData
[
0
].
value
}
|
${
metricsData
[
0
].
titl
e
}
${
1
}
|
${
metricsData
[
1
].
identifier
}
|
${
metricsData
[
1
].
value
}
|
${
metricsData
[
1
].
titl
e
}
${
2
}
|
${
metricsData
[
2
].
identifier
}
|
${
metricsData
[
2
].
value
}
|
${
metricsData
[
2
].
titl
e
}
${
3
}
|
${
metricsData
[
3
].
identifier
}
|
${
metricsData
[
3
].
value
}
|
${
metricsData
[
3
].
titl
e
}
`
(
'
metric tiles
'
,
({
i
dentifier
,
index
,
value
,
label
})
=>
{
it
(
`renders a
metric tile component for "
${
label
}
"
`
,
()
=>
{
const
metric
=
findMetrics
().
at
(
index
);
expect
(
metric
.
props
(
)).
toMatchObject
({
value
,
title
,
unit
:
unit
??
''
});
expect
(
metric
.
props
(
'
metric
'
)).
toMatchObject
({
identifier
,
value
,
label
});
expect
(
metric
.
isVisible
()).
toBe
(
true
);
});
it
(
`
${
clickable
?
'
redirects
'
:
"
doesn't redirect
"
}
when the user clicks the "
${
title
}
" metric`
,
()
=>
{
const
metric
=
findMetrics
().
at
(
index
);
metric
.
vm
.
$emit
(
'
click
'
);
if
(
clickable
)
{
expect
(
redirectTo
).
toHaveBeenCalledWith
(
metricsData
[
index
].
links
[
0
].
url
);
}
else
{
expect
(
redirectTo
).
not
.
toHaveBeenCalled
();
}
});
it
(
`will render
${
animationDecimalPlaces
}
decimal places for the
${
title
}
metric with the value "
${
value
}
"`
,
()
=>
{
const
metric
=
findMetrics
().
at
(
index
);
expect
(
metric
.
props
(
'
animationDecimalPlaces
'
)).
toBe
(
animationDecimalPlaces
);
});
});
it
(
'
will not display a loading icon
'
,
()
=>
{
...
...
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