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
70e259de
Commit
70e259de
authored
Sep 20, 2019
by
Martin Wortschack
Committed by
Clement Ho
Sep 20, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add metric chart component
- Introduce reusable metric chart component for productivity analytics
parent
9aa4f957
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
434 additions
and
0 deletions
+434
-0
ee/app/assets/javascripts/analytics/productivity_analytics/components/metric_chart.vue
...lytics/productivity_analytics/components/metric_chart.vue
+110
-0
ee/spec/frontend/analytics/productivity_analytics/components/__snapshots__/metric_chart_spec.js.snap
...lytics/components/__snapshots__/metric_chart_spec.js.snap
+88
-0
ee/spec/frontend/analytics/productivity_analytics/components/metric_chart_spec.js
...cs/productivity_analytics/components/metric_chart_spec.js
+230
-0
locale/gitlab.pot
locale/gitlab.pot
+6
-0
No files found.
ee/app/assets/javascripts/analytics/productivity_analytics/components/metric_chart.vue
0 → 100644
View file @
70e259de
<
script
>
import
_
from
'
underscore
'
;
import
{
s__
}
from
'
~/locale
'
;
import
{
GlDropdown
,
GlDropdownItem
,
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
export
default
{
name
:
'
MetricChart
'
,
components
:
{
GlDropdown
,
GlDropdownItem
,
GlLoadingIcon
,
Icon
,
},
props
:
{
title
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
description
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
isLoading
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
metricTypes
:
{
type
:
Array
,
required
:
false
,
default
:
()
=>
[],
},
selectedMetric
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
chartData
:
{
type
:
[
Object
,
Array
],
required
:
false
,
default
:
()
=>
{},
},
},
computed
:
{
hasMetricTypes
()
{
return
this
.
metricTypes
.
length
;
},
metricDropdownLabel
()
{
const
foundMetric
=
this
.
metricTypes
.
find
(
m
=>
m
.
key
===
this
.
selectedMetric
);
return
foundMetric
?
foundMetric
.
label
:
s__
(
'
MetricChart|Please select a metric
'
);
},
hasChartData
()
{
return
!
_
.
isEmpty
(
this
.
chartData
);
},
},
methods
:
{
isSelectedMetric
(
key
)
{
return
this
.
selectedMetric
===
key
;
},
},
};
</
script
>
<
template
>
<div>
<h5
v-if=
"title"
>
{{
title
}}
</h5>
<gl-loading-icon
v-if=
"isLoading"
size=
"md"
class=
"my-4 py-4"
/>
<template
v-else
>
<div
v-if=
"!hasChartData"
ref=
"noData"
class=
"bs-callout bs-callout-info"
>
{{
__
(
'
There is no data available. Please change your selection.
'
)
}}
</div>
<template
v-else
>
<gl-dropdown
v-if=
"hasMetricTypes"
class=
"mb-4 metric-dropdown"
toggle-class=
"dropdown-menu-toggle w-100"
menu-class=
"w-100 mw-100"
:text=
"metricDropdownLabel"
>
<gl-dropdown-item
v-for=
"metric in metricTypes"
:key=
"metric.key"
active-class=
"is-active"
class=
"w-100"
@
click=
"$emit('metricTypeChange', metric.key)"
>
<span
class=
"d-flex"
>
<icon
:title=
"s__('MetricChart|Selected')"
class=
"flex-shrink-0 append-right-4"
:class=
"
{
invisible: !isSelectedMetric(metric.key),
}"
name="mobile-issue-close"
:aria-label="s__('MetricChart|Selected')"
/>
{{
metric
.
label
}}
</span>
</gl-dropdown-item>
</gl-dropdown>
<p
v-if=
"description"
class=
"text-muted"
>
{{
description
}}
</p>
<div
ref=
"chart"
>
<slot
v-if=
"hasChartData"
></slot>
</div>
</
template
>
</template>
</div>
</template>
ee/spec/frontend/analytics/productivity_analytics/components/__snapshots__/metric_chart_spec.js.snap
0 → 100644
View file @
70e259de
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`MetricChart component template when isLoading is false and chart data is empty matches the snapshot 1`] = `
<div>
<!---->
<div
class="bs-callout bs-callout-info"
>
There is no data available. Please change your selection.
</div>
</div>
`;
exports[`MetricChart component template when isLoading is false and chartData is not empty and metricTypes exist matches the snapshot 1`] = `
<div>
<!---->
<gldropdown-stub
class="mb-4 metric-dropdown"
menu-class="w-100 mw-100"
text="Please select a metric"
toggle-class="dropdown-menu-toggle w-100"
>
<gldropdownitem-stub
active-class="is-active"
class="w-100"
>
<span
class="d-flex"
>
<icon-stub
aria-label="Selected"
class="flex-shrink-0 append-right-4 invisible"
cssclasses=""
name="mobile-issue-close"
size="16"
title="Selected"
/>
Time from last commit to merge
</span>
</gldropdownitem-stub>
<gldropdownitem-stub
active-class="is-active"
class="w-100"
>
<span
class="d-flex"
>
<icon-stub
aria-label="Selected"
class="flex-shrink-0 append-right-4 invisible"
cssclasses=""
name="mobile-issue-close"
size="16"
title="Selected"
/>
Time from first comment to last commit
</span>
</gldropdownitem-stub>
</gldropdown-stub>
<!---->
<div>
mockChart
</div>
</div>
`;
exports[`MetricChart component template when isLoading is true matches the snapshot 1`] = `
<div>
<!---->
<glloadingicon-stub
class="my-4 py-4"
color="orange"
label="Loading"
size="md"
/>
</div>
`;
ee/spec/frontend/analytics/productivity_analytics/components/metric_chart_spec.js
0 → 100644
View file @
70e259de
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
MetricChart
from
'
ee/analytics/productivity_analytics/components/metric_chart.vue
'
;
import
{
GlLoadingIcon
,
GlDropdown
,
GlDropdownItem
}
from
'
@gitlab/ui
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
describe
(
'
MetricChart component
'
,
()
=>
{
let
wrapper
;
const
defaultProps
=
{
title
:
'
My Chart
'
,
};
const
mockChart
=
'
mockChart
'
;
const
metricTypes
=
[
{
key
:
'
time_to_merge
'
,
label
:
'
Time from last commit to merge
'
,
},
{
key
:
'
time_to_last_commit
'
,
label
:
'
Time from first comment to last commit
'
,
},
];
const
factory
=
(
props
=
defaultProps
)
=>
{
wrapper
=
shallowMount
(
MetricChart
,
{
sync
:
false
,
propsData
:
{
...
props
},
slots
:
{
default
:
mockChart
,
},
});
};
afterEach
(()
=>
{
wrapper
.
destroy
();
});
const
findLoadingIndicator
=
()
=>
wrapper
.
find
(
GlLoadingIcon
);
const
findNoDataSection
=
()
=>
wrapper
.
find
({
ref
:
'
noData
'
});
const
findMetricDropdown
=
()
=>
wrapper
.
find
(
GlDropdown
);
const
findMetricDropdownItems
=
()
=>
findMetricDropdown
().
findAll
(
GlDropdownItem
);
const
findChartSlot
=
()
=>
wrapper
.
find
({
ref
:
'
chart
'
});
describe
(
'
template
'
,
()
=>
{
describe
(
'
when title exists
'
,
()
=>
{
beforeEach
(()
=>
{
factory
();
});
it
(
'
renders a title
'
,
()
=>
{
expect
(
wrapper
.
text
()).
toContain
(
'
My Chart
'
);
});
});
describe
(
"
when title doesn't exist
"
,
()
=>
{
beforeEach
(()
=>
{
factory
({
title
:
null
,
description
:
null
});
});
it
(
"
doesn't render a title
"
,
()
=>
{
expect
(
wrapper
.
text
()).
not
.
toContain
(
'
My Chart
'
);
});
});
describe
(
'
when isLoading is true
'
,
()
=>
{
beforeEach
(()
=>
{
factory
({
isLoading
:
true
});
});
it
(
'
matches the snapshot
'
,
()
=>
{
expect
(
wrapper
.
element
).
toMatchSnapshot
();
});
it
(
'
renders a loading indicator
'
,
()
=>
{
expect
(
findLoadingIndicator
().
exists
()).
toBe
(
true
);
});
});
describe
(
'
when isLoading is false
'
,
()
=>
{
const
isLoading
=
false
;
it
(
'
does not render a loading indicator
'
,
()
=>
{
factory
({
isLoading
});
expect
(
findLoadingIndicator
().
exists
()).
toBe
(
false
);
});
describe
(
'
and chart data is empty
'
,
()
=>
{
beforeEach
(()
=>
{
factory
({
isLoading
,
chartData
:
[]
});
});
it
(
'
matches the snapshot
'
,
()
=>
{
expect
(
wrapper
.
element
).
toMatchSnapshot
();
});
it
(
'
does not show the slot for the chart
'
,
()
=>
{
expect
(
findChartSlot
().
exists
()).
toBe
(
false
);
});
it
(
'
shows a "no data" info text
'
,
()
=>
{
expect
(
findNoDataSection
().
text
()).
toContain
(
'
There is no data available. Please change your selection.
'
,
);
});
});
describe
(
'
and chartData is not empty
'
,
()
=>
{
const
chartData
=
[[
0
,
1
]];
describe
(
'
and metricTypes exist
'
,
()
=>
{
beforeEach
(()
=>
{
factory
({
isLoading
,
metricTypes
,
chartData
});
});
it
(
'
matches the snapshot
'
,
()
=>
{
expect
(
wrapper
.
element
).
toMatchSnapshot
();
});
it
(
'
renders a metric dropdown
'
,
()
=>
{
expect
(
findMetricDropdown
().
exists
()).
toBe
(
true
);
});
it
(
'
renders a dropdown item for each item in metricTypes
'
,
()
=>
{
expect
(
findMetricDropdownItems
().
length
).
toBe
(
2
);
});
it
(
'
should emit `metricTypeChange` event when dropdown item gets clicked
'
,
()
=>
{
jest
.
spyOn
(
wrapper
.
vm
,
'
$emit
'
);
findMetricDropdownItems
()
.
at
(
0
)
.
vm
.
$emit
(
'
click
'
);
expect
(
wrapper
.
vm
.
$emit
).
toHaveBeenCalledWith
(
'
metricTypeChange
'
,
'
time_to_merge
'
);
});
it
(
'
should set the `invisible` class on the icon of the first dropdown item
'
,
()
=>
{
wrapper
.
setProps
({
selectedMetric
:
'
time_to_last_commit
'
});
expect
(
findMetricDropdownItems
()
.
at
(
0
)
.
find
(
Icon
)
.
classes
(),
).
toContain
(
'
invisible
'
);
});
});
describe
(
'
and a description exists
'
,
()
=>
{
it
(
'
renders a description
'
,
()
=>
{
factory
({
isLoading
,
chartData
,
description
:
'
Test description
'
});
expect
(
wrapper
.
text
()).
toContain
(
'
Test description
'
);
});
});
it
(
'
contains chart from slot
'
,
()
=>
{
factory
({
isLoading
,
chartData
});
expect
(
findChartSlot
().
text
()).
toBe
(
mockChart
);
});
});
});
});
describe
(
'
computed
'
,
()
=>
{
describe
(
'
hasMetricTypes
'
,
()
=>
{
it
(
'
returns true if metricTypes exist
'
,
()
=>
{
factory
({
metricTypes
});
expect
(
wrapper
.
vm
.
hasMetricTypes
).
toBe
(
2
);
});
it
(
'
returns true if no metricTypes exist
'
,
()
=>
{
factory
();
expect
(
wrapper
.
vm
.
hasMetricTypes
).
toBe
(
0
);
});
});
describe
(
'
metricDropdownLabel
'
,
()
=>
{
describe
(
'
when a metric is selected
'
,
()
=>
{
it
(
'
returns the label of the currently selected metric
'
,
()
=>
{
factory
({
metricTypes
,
selectedMetric
:
'
time_to_merge
'
});
expect
(
wrapper
.
vm
.
metricDropdownLabel
).
toBe
(
'
Time from last commit to merge
'
);
});
});
describe
(
'
when no metric is selected
'
,
()
=>
{
it
(
'
returns the default dropdown label
'
,
()
=>
{
factory
({
metricTypes
});
expect
(
wrapper
.
vm
.
metricDropdownLabel
).
toBe
(
'
Please select a metric
'
);
});
});
});
describe
(
'
hasChartData
'
,
()
=>
{
describe
(
'
when chartData is an object
'
,
()
=>
{
it
(
'
returns true if chartData is not empty
'
,
()
=>
{
factory
({
chartData
:
{
1
:
0
}
});
expect
(
wrapper
.
vm
.
hasChartData
).
toBe
(
true
);
});
it
(
'
returns false if chartData is empty
'
,
()
=>
{
factory
({
chartData
:
{}
});
expect
(
wrapper
.
vm
.
hasChartData
).
toBe
(
false
);
});
});
describe
(
'
when chartData is an array
'
,
()
=>
{
it
(
'
returns true if chartData is not empty
'
,
()
=>
{
factory
({
chartData
:
[[
1
,
0
]]
});
expect
(
wrapper
.
vm
.
hasChartData
).
toBe
(
true
);
});
it
(
'
returns false if chartData is empty
'
,
()
=>
{
factory
({
chartData
:
[]
});
expect
(
wrapper
.
vm
.
hasChartData
).
toBe
(
false
);
});
});
});
});
describe
(
'
methods
'
,
()
=>
{
describe
(
'
isSelectedMetric
'
,
()
=>
{
it
(
'
returns true if the given key matches the selectedMetric prop
'
,
()
=>
{
factory
({
selectedMetric
:
'
time_to_merge
'
});
expect
(
wrapper
.
vm
.
isSelectedMetric
(
'
time_to_merge
'
)).
toBe
(
true
);
});
});
});
});
locale/gitlab.pot
View file @
70e259de
...
...
@@ -9705,6 +9705,12 @@ msgstr ""
msgid "Metric was successfully updated."
msgstr ""
msgid "MetricChart|Please select a metric"
msgstr ""
msgid "MetricChart|Selected"
msgstr ""
msgid "Metrics"
msgstr ""
...
...
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