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
24281d62
Commit
24281d62
authored
Sep 24, 2019
by
Martin Wortschack
Committed by
Filipa Lacerda
Sep 24, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add metric chart component to app
- Leverage MetricChart component in productivity analytics app
parent
91aea099
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
333 additions
and
461 deletions
+333
-461
ee/app/assets/javascripts/analytics/productivity_analytics/components/app.vue
...ripts/analytics/productivity_analytics/components/app.vue
+99
-175
ee/app/assets/javascripts/analytics/productivity_analytics/store/modules/charts/getters.js
...cs/productivity_analytics/store/modules/charts/getters.js
+1
-2
ee/spec/frontend/analytics/productivity_analytics/components/app_spec.js
...d/analytics/productivity_analytics/components/app_spec.js
+219
-280
ee/spec/frontend/analytics/productivity_analytics/store/modules/charts/getters_spec.js
...oductivity_analytics/store/modules/charts/getters_spec.js
+13
-0
locale/gitlab.pot
locale/gitlab.pot
+1
-4
No files found.
ee/app/assets/javascripts/analytics/productivity_analytics/components/app.vue
View file @
24281d62
...
...
@@ -10,6 +10,7 @@ import {
}
from
'
@gitlab/ui
'
;
import
{
GlColumnChart
}
from
'
@gitlab/ui/dist/charts
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
MetricChart
from
'
./metric_chart.vue
'
;
import
MergeRequestTable
from
'
./mr_table.vue
'
;
import
{
chartKeys
}
from
'
../constants
'
;
...
...
@@ -22,6 +23,7 @@ export default {
GlColumnChart
,
GlButton
,
Icon
,
MetricChart
,
MergeRequestTable
,
},
directives
:
{
...
...
@@ -47,7 +49,7 @@ export default {
};
},
computed
:
{
...
mapState
(
'
filters
'
,
[
'
groupNamespace
'
,
'
projectPath
'
]),
...
mapState
(
'
filters
'
,
[
'
groupNamespace
'
]),
...
mapState
(
'
table
'
,
[
'
isLoadingTable
'
,
'
mergeRequests
'
,
'
pageInfo
'
,
'
columnMetric
'
]),
...
mapGetters
([
'
getMetricTypes
'
]),
...
mapGetters
(
'
charts
'
,
[
...
...
@@ -56,7 +58,7 @@ export default {
'
getChartData
'
,
'
getColumnChartDatazoomOption
'
,
'
getMetricDropdownLabel
'
,
'
is
SelectedMetric
'
,
'
get
SelectedMetric
'
,
'
hasNoAccessError
'
,
]),
...
mapGetters
(
'
table
'
,
[
...
...
@@ -85,7 +87,6 @@ export default {
},
methods
:
{
...
mapActions
([
'
setEndpoint
'
]),
...
mapActions
(
'
filters
'
,
[
'
setProjectPath
'
]),
...
mapActions
(
'
charts
'
,
[
'
fetchChartData
'
,
'
setMetricType
'
,
'
chartItemClicked
'
]),
...
mapActions
(
'
table
'
,
[
'
setSortField
'
,
...
...
@@ -141,136 +142,109 @@ export default {
/>
<template
v-if=
"showAppContent"
>
<h4>
{{
__
(
'
Merge Requests
'
)
}}
</h4>
<div
class=
"qa-time-to-merge mb-4"
>
<h5>
{{
__
(
'
Time to merge
'
)
}}
</h5>
<gl-loading-icon
v-if=
"chartLoading(chartKeys.main)"
size=
"md"
class=
"my-4 py-4"
/>
<template
v-else
>
<div
v-if=
"!chartHasData(chartKeys.main)"
class=
"bs-callout bs-callout-info"
>
{{
__
(
'
There is no data available. Please change your selection.
'
)
}}
</div>
<template
v-else
>
<p
class=
"text-muted"
>
{{
__
(
'
You can filter by "days to merge" by clicking on the columns in the chart.
'
)
}}
</p>
<gl-column-chart
:data=
"
{ full: getChartData(chartKeys.main) }"
:option="getColumnChartOption(chartKeys.main)"
:y-axis-title="__('Merge requests')"
:x-axis-title="__('Days')"
x-axis-type="category"
@chartItemClicked="onMainChartItemClicked"
/>
</
template
>
</template>
</div>
<metric-chart
ref=
"mainChart"
class=
"mb-4"
:title=
"__('Time to merge')"
:description=
"
__('You can filter by \'days to merge\' by clicking on the columns in the chart.')
"
:is-loading=
"chartLoading(chartKeys.main)"
:chart-data=
"getChartData(chartKeys.main)"
>
<gl-column-chart
:data=
"
{ full: getChartData(chartKeys.main) }"
:option="getColumnChartOption(chartKeys.main)"
:y-axis-title="__('Merge requests')"
:x-axis-title="__('Days')"
x-axis-type="category"
@chartItemClicked="onMainChartItemClicked"
/>
</metric-chart>
<template
v-if=
"showSecondaryCharts"
>
<div
class=
"row"
>
<div
class=
"qa-time-based col-lg-6 col-sm-12 mb-4"
>
<gl-loading-icon
v-if=
"chartLoading(chartKeys.timeBasedHistogram)"
size=
"md"
class=
"my-4 py-4"
/>
<template
v-else
>
<div
v-if=
"!chartHasData(chartKeys.timeBasedHistogram)"
class=
"bs-callout bs-callout-info"
>
{{
__
(
'
There is no data for the selected metric. Please change your selection.
'
)
}}
</div>
<template
v-else
>
<gl-dropdown
class=
"mb-4 metric-dropdown"
toggle-class=
"dropdown-menu-toggle w-100"
menu-class=
"w-100 mw-100"
:text=
"getMetricDropdownLabel(chartKeys.timeBasedHistogram)"
>
<gl-dropdown-item
v-for=
"metric in getMetricTypes(chartKeys.timeBasedHistogram)"
:key=
"metric.key"
active-class=
"is-active"
class=
"w-100"
@
click=
"
setMetricType(
{
metricType: metric.key,
chartKey: chartKeys.timeBasedHistogram,
})
"
>
<span
class=
"d-flex"
>
<icon
class=
"flex-shrink-0 append-right-4"
:class=
"
{
invisible: !isSelectedMetric({
metric: metric.key,
chartKey: chartKeys.timeBasedHistogram,
}),
}"
name="mobile-issue-close"
/>
{{
metric
.
label
}}
</span>
</gl-dropdown-item>
</gl-dropdown>
<p
class=
"text-muted"
>
{{
__
(
'
Not all data has been processed yet, the accuracy of the chart for the selected timeframe is limited.
'
,
)
}}
</p>
<gl-column-chart
:data=
"
{ full: getChartData(chartKeys.timeBasedHistogram) }"
:option="getColumnChartOption(chartKeys.timeBasedHistogram)"
:y-axis-title="__('Merge requests')"
:x-axis-title="__('Hours')"
x-axis-type="category"
/>
</
template
>
</template>
<div
ref=
"secondaryCharts"
>
<div
class=
"row"
>
<metric-chart
ref=
"timeBasedChart"
class=
"col-lg-6 col-sm-12 mb-4"
:description=
"
__(
'Not all data has been processed yet, the accuracy of the chart for the selected timeframe is limited.',
)
"
:is-loading=
"chartLoading(chartKeys.timeBasedHistogram)"
:metric-types=
"getMetricTypes(chartKeys.timeBasedHistogram)"
:selected-metric=
"getSelectedMetric(chartKeys.timeBasedHistogram)"
:chart-data=
"getChartData(chartKeys.timeBasedHistogram)"
@
metricTypeChange=
"
metric =>
setMetricType(
{ metricType: metric, chartKey: chartKeys.timeBasedHistogram })
"
>
<gl-column-chart
:data=
"
{ full: getChartData(chartKeys.timeBasedHistogram) }"
:option="getColumnChartOption(chartKeys.timeBasedHistogram)"
:y-axis-title="__('Merge requests')"
:x-axis-title="__('Hours')"
x-axis-type="category"
/>
</metric-chart>
<metric-chart
ref=
"commitBasedChart"
class=
"col-lg-6 col-sm-12 mb-4"
:description=
"
__(
'Not all data has been processed yet, the accuracy of the chart for the selected timeframe is limited.',
)
"
:is-loading=
"chartLoading(chartKeys.commitBasedHistogram)"
:metric-types=
"getMetricTypes(chartKeys.commitBasedHistogram)"
:selected-metric=
"getSelectedMetric(chartKeys.commitBasedHistogram)"
:chart-data=
"getChartData(chartKeys.commitBasedHistogram)"
@
metricTypeChange=
"
metric =>
setMetricType(
{ metricType: metric, chartKey: chartKeys.commitBasedHistogram })
"
>
<gl-column-chart
:data=
"
{ full: getChartData(chartKeys.commitBasedHistogram) }"
:option="getColumnChartOption(chartKeys.commitBasedHistogram)"
:y-axis-title="__('Merge requests')"
:x-axis-title="getMetricDropdownLabel(chartKeys.commitBasedHistogram)"
x-axis-type="category"
/>
</metric-chart>
</div>
<div
class=
"qa-commit-based col-lg-6 col-sm-12 mb-4"
>
<gl-loading-icon
v-if=
"chartLoading(chartKeys.commitBasedHistogram)"
size=
"md"
class=
"my-4 py-4"
/>
<
template
v-else
>
<div
v-if=
"!chartHasData(chartKeys.commitBasedHistogram)"
class=
"bs-callout bs-callout-info"
>
{{
__
(
'
There is no data for the selected metric. Please change your selection.
'
)
}}
</div>
<template
v-else
>
<div
class=
"js-mr-table-sort d-flex flex-column flex-md-row align-items-md-center justify-content-between mb-2"
>
<h5>
{{
__
(
'
List
'
)
}}
</h5>
<div
v-if=
"showMergeRequestTable"
class=
"d-flex flex-column flex-md-row align-items-md-center"
>
<strong
class=
"mr-2"
>
{{
__
(
'
Sort by
'
)
}}
</strong>
<div
class=
"d-flex"
>
<gl-dropdown
class=
"mb-4 metric-dropdown"
toggle-class=
"dropdown-menu-toggle w-100"
menu-class=
"w-100 mw-100"
:text=
"getMetricDropdownLabel(chartKeys.commitBasedHistogram)"
class=
"mr-2 flex-grow"
toggle-class=
"dropdown-menu-toggle"
:text=
"sortFieldDropdownLabel"
>
<gl-dropdown-item
v-for=
"metric in
getMetricTypes(chartKeys.commitBasedHistogram)
"
v-for=
"metric in
tableSortOptions
"
:key=
"metric.key"
active-class=
"is-active"
class=
"w-100"
@
click=
"
setMetricType(
{
metricType: metric.key,
chartKey: chartKeys.commitBasedHistogram,
})
"
@
click=
"setSortField(metric.key)"
>
<span
class=
"d-flex"
>
<icon
class=
"flex-shrink-0 append-right-4"
:class=
"
{
invisible: !isSelectedMetric({
metric: metric.key,
chartKey: chartKeys.commitBasedHistogram,
}),
invisible: !isSelectedSortField(metric.key),
}"
name="mobile-issue-close"
/>
...
...
@@ -278,66 +252,16 @@ export default {
</span>
</gl-dropdown-item>
</gl-dropdown>
<p
class=
"text-muted"
>
{{
__
(
'
Not all data has been processed yet, the accuracy of the chart for the selected timeframe is limited.
'
,
)
}}
</p>
<gl-column-chart
:data=
"
{ full: getChartData(chartKeys.commitBasedHistogram) }"
:option="getColumnChartOption(chartKeys.commitBasedHistogram)"
:y-axis-title="__('Merge requests')"
:x-axis-title="getMetricDropdownLabel(chartKeys.commitBasedHistogram)"
x-axis-type="category"
/>
</
template
>
</template>
</div>
</div>
<div
class=
"qa-mr-table-sort d-flex flex-column flex-md-row align-items-md-center justify-content-between mb-2"
>
<h5>
{{ __('List') }}
</h5>
<div
v-if=
"showMergeRequestTable"
class=
"d-flex flex-column flex-md-row align-items-md-center"
>
<strong
class=
"mr-2"
>
{{ __('Sort by') }}
</strong>
<div
class=
"d-flex"
>
<gl-dropdown
class=
"mr-2 flex-grow"
toggle-class=
"dropdown-menu-toggle"
:text=
"sortFieldDropdownLabel"
>
<gl-dropdown-item
v-for=
"metric in tableSortOptions"
:key=
"metric.key"
active-class=
"is-active"
class=
"w-100"
@
click=
"setSortField(metric.key)"
>
<span
class=
"d-flex"
>
<icon
class=
"flex-shrink-0 append-right-4"
:class=
"{
invisible: !isSelectedSortField(metric.key),
}"
name=
"mobile-issue-close"
/>
{{ metric.label }}
</span>
</gl-dropdown-item>
</gl-dropdown>
<gl-button
v-gl-tooltip
.
hover
:title=
"sortTooltipTitle"
@
click=
"toggleSortOrder"
>
<icon
:name=
"sortIcon"
/>
</gl-button>
<gl-button
v-gl-tooltip
.
hover
:title=
"sortTooltipTitle"
@
click=
"toggleSortOrder"
>
<icon
:name=
"sortIcon"
/>
</gl-button>
</div>
</div>
</div>
</div>
<div
class=
"qa-mr-table"
>
<div
class=
"js-mr-table"
>
<div
ref=
"foo"
></div>
<gl-loading-icon
v-if=
"isLoadingTable"
size=
"md"
class=
"my-4 py-4"
/>
<merge-request-table
v-if=
"showMergeRequestTable"
...
...
ee/app/assets/javascripts/analytics/productivity_analytics/store/modules/charts/getters.js
View file @
24281d62
...
...
@@ -107,8 +107,7 @@ export const getColumnChartDatazoomOption = state => chartKey => {
};
};
export
const
isSelectedMetric
=
state
=>
({
metric
,
chartKey
})
=>
state
.
charts
[
chartKey
].
params
.
metricType
===
metric
;
export
const
getSelectedMetric
=
state
=>
chartKey
=>
state
.
charts
[
chartKey
].
params
.
metricType
;
export
const
hasNoAccessError
=
state
=>
state
.
charts
[
chartKeys
.
main
].
errorCode
===
httpStatus
.
FORBIDDEN
;
...
...
ee/spec/frontend/analytics/productivity_analytics/components/app_spec.js
View file @
24281d62
import
{
createLocalVue
,
shallowMount
}
from
'
@vue/test-utils
'
;
import
Vuex
from
'
vuex
'
;
import
axios
from
'
axios
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
ProductivityApp
from
'
ee/analytics/productivity_analytics/components/app.vue
'
;
import
MergeRequestTable
from
'
ee/analytics/productivity_analytics/components/mr_table.vue
'
;
import
store
from
'
ee/analytics/productivity_analytics/store
'
;
...
...
@@ -7,13 +9,13 @@ import { chartKeys } from 'ee/analytics/productivity_analytics/constants';
import
{
TEST_HOST
}
from
'
helpers/test_constants
'
;
import
{
GlEmptyState
,
GlLoadingIcon
,
GlDropdown
,
GlDropdownItem
,
GlButton
}
from
'
@gitlab/ui
'
;
import
{
GlColumnChart
}
from
'
@gitlab/ui/dist/charts
'
;
import
resetStore
from
'
../helpers
'
;
const
localVue
=
createLocalVue
();
localVue
.
use
(
Vuex
);
describe
(
'
ProductivityApp component
'
,
()
=>
{
let
wrapper
;
let
mock
;
const
propsData
=
{
endpoint
:
TEST_HOST
,
...
...
@@ -30,7 +32,10 @@ describe('ProductivityApp component', () => {
setColumnMetric
:
jest
.
fn
(),
};
const
mainChartData
=
{
1
:
2
,
2
:
3
};
beforeEach
(()
=>
{
mock
=
new
MockAdapter
(
axios
);
wrapper
=
shallowMount
(
localVue
.
extend
(
ProductivityApp
),
{
localVue
,
store
,
...
...
@@ -40,23 +45,22 @@ describe('ProductivityApp component', () => {
...
actionSpies
,
},
});
jest
.
spyOn
(
store
,
'
dispatch
'
).
mockImplementation
();
});
afterEach
(()
=>
{
wrapper
.
destroy
();
resetStore
(
store
);
mock
.
restore
(
);
});
const
findTimeToMergeSection
=
()
=>
wrapper
.
find
(
'
.qa-time-to-merge
'
);
const
findMrTableSortSection
=
()
=>
wrapper
.
find
(
'
.qa-mr-table-sort
'
);
const
findMrTableSection
=
()
=>
wrapper
.
find
(
'
.qa-mr-table
'
);
const
findMrTable
=
()
=>
findMrTableSection
().
find
(
MergeRequestTable
);
const
findMainMetricChart
=
()
=>
wrapper
.
find
({
ref
:
'
mainChart
'
});
const
findSecondaryChartsSection
=
()
=>
wrapper
.
find
({
ref
:
'
secondaryCharts
'
});
const
findTimeBasedMetricChart
=
()
=>
wrapper
.
find
({
ref
:
'
timeBasedChart
'
});
const
findCommitBasedMetricChart
=
()
=>
wrapper
.
find
({
ref
:
'
commitBasedChart
'
});
const
findMrTableSortSection
=
()
=>
wrapper
.
find
(
'
.js-mr-table-sort
'
);
const
findSortFieldDropdown
=
()
=>
findMrTableSortSection
().
find
(
GlDropdown
);
const
findSortOrderToggle
=
()
=>
findMrTableSortSection
().
find
(
GlButton
);
const
find
TimeBasedSection
=
()
=>
wrapper
.
find
(
'
.qa-time-based
'
);
const
find
CommitBasedSection
=
()
=>
wrapper
.
find
(
'
.qa-commit-based
'
);
const
find
MrTableSection
=
()
=>
wrapper
.
find
(
'
.js-mr-table
'
);
const
find
MrTable
=
()
=>
findMrTableSection
().
find
(
MergeRequestTable
);
describe
(
'
template
'
,
()
=>
{
describe
(
'
without a group being selected
'
,
()
=>
{
...
...
@@ -70,12 +74,18 @@ describe('ProductivityApp component', () => {
describe
(
'
with a group being selected
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
state
.
filters
.
groupNamespace
=
'
gitlab-org
'
;
wrapper
.
vm
.
$store
.
dispatch
(
'
filters/setGroupNamespace
'
,
'
gitlab-org
'
);
mock
.
onGet
(
wrapper
.
vm
.
$store
.
state
.
endpoint
).
replyOnce
(
200
);
});
describe
(
'
and
user has no access to the group
'
,
()
=>
{
describe
(
'
user has no access to the group
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
state
.
charts
.
charts
[
chartKeys
.
main
].
errorCode
=
403
;
const
error
=
{
response
:
{
status
:
403
}
};
wrapper
.
vm
.
$store
.
dispatch
(
'
charts/receiveChartDataError
'
,
{
chartKey
:
chartKeys
.
main
,
error
,
});
wrapper
.
vm
.
$store
.
state
.
charts
.
charts
[
chartKeys
.
main
].
errorCode
=
403
;
});
it
(
'
renders the no access illustration
'
,
()
=>
{
...
...
@@ -86,350 +96,279 @@ describe('ProductivityApp component', () => {
});
});
describe
(
'
and
user has access to the group
'
,
()
=>
{
describe
(
'
user has access to the group
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
state
.
charts
.
charts
[
chartKeys
.
main
].
errorCode
=
null
;
wrapper
.
vm
.
$
store
.
state
.
charts
.
charts
[
chartKeys
.
main
].
errorCode
=
null
;
});
describe
(
'
Time to merge chart
'
,
()
=>
{
it
(
'
renders the title
'
,
()
=>
{
expect
(
findTimeToMergeSection
().
text
()).
toContain
(
'
Time to merge
'
);
describe
(
'
when the main chart is loading
'
,
()
=>
{
beforeEach
(
()
=>
{
wrapper
.
vm
.
$store
.
dispatch
(
'
charts/requestChartData
'
,
chartKeys
.
main
);
});
describe
(
'
when chart is loading
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
state
.
charts
.
charts
[
chartKeys
.
main
].
isLoading
=
true
;
});
it
(
'
renders a loading indicator
'
,
()
=>
{
expect
(
findTimeToMergeSection
()
.
find
(
GlLoadingIcon
)
.
exists
(),
).
toBe
(
true
);
});
it
(
'
renders a metric chart component for the main chart
'
,
()
=>
{
expect
(
findMainMetricChart
().
exists
()).
toBe
(
true
);
});
describe
(
'
when chart finished loading
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
state
.
charts
.
charts
[
chartKeys
.
main
].
isLoading
=
false
;
});
describe
(
'
and the chart has data
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
state
.
charts
.
charts
[
chartKeys
.
main
].
data
=
{
1
:
2
,
2
:
3
};
});
it
(
'
renders a column chart
'
,
()
=>
{
expect
(
findTimeToMergeSection
()
.
find
(
GlColumnChart
)
.
exists
(),
).
toBe
(
true
);
});
describe
(
'
when an item on the chart is clicked
'
,
()
=>
{
beforeEach
(()
=>
{
const
data
=
{
chart
:
null
,
params
:
{
data
:
{
value
:
[
0
,
1
],
},
},
};
findTimeToMergeSection
()
.
find
(
GlColumnChart
)
.
vm
.
$emit
(
'
chartItemClicked
'
,
data
);
});
it
(
'
dispatches chartItemClicked action
'
,
()
=>
{
expect
(
actionSpies
.
chartItemClicked
).
toHaveBeenCalledWith
({
chartKey
:
chartKeys
.
main
,
item
:
0
,
});
});
it
(
'
dispatches setMergeRequestsPage action
'
,
()
=>
{
expect
(
actionSpies
.
setMergeRequestsPage
).
toHaveBeenCalledWith
(
0
);
});
});
});
describe
(
"
and the chart doesn't have any data
"
,
()
=>
{
beforeEach
(()
=>
{
store
.
state
.
charts
.
charts
[
chartKeys
.
main
].
data
=
null
;
});
it
(
'
sets isLoading=true on the metric chart
'
,
()
=>
{
expect
(
findMainMetricChart
().
props
(
'
isLoading
'
)).
toBe
(
true
);
});
it
(
'
renders a "no data" message
'
,
()
=>
{
expect
(
findTimeToMergeSection
().
text
()).
toContain
(
'
There is no data available. Please change your selection.
'
,
);
});
});
it
(
'
does not render any other charts
'
,
()
=>
{
expect
(
findSecondaryChartsSection
().
exists
()).
toBe
(
false
);
});
});
describe
(
'
Time based histogram
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
state
.
charts
.
charts
[
chartKeys
.
main
].
isLoading
=
false
;
store
.
state
.
charts
.
charts
[
chartKeys
.
main
].
data
=
{
1
:
2
,
2
:
3
};
it
(
'
does not render the MR table
'
,
()
=>
{
expect
(
findMrTableSortSection
().
exists
()).
toBe
(
false
);
expect
(
findMrTableSection
().
exists
()).
toBe
(
false
);
});
});
describe
(
'
when chart is loading
'
,
()
=>
{
describe
(
'
when the main chart finished loading
'
,
()
=>
{
describe
(
'
and has data
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
state
.
charts
.
charts
[
chartKeys
.
timeBasedHistogram
].
isLoading
=
true
;
wrapper
.
vm
.
$store
.
dispatch
(
'
charts/receiveChartDataSuccess
'
,
{
chartKey
:
chartKeys
.
main
,
data
:
mainChartData
,
});
});
it
(
'
renders a loading indicator
'
,
()
=>
{
expect
(
findTimeBasedSection
()
.
find
(
GlLoadingIcon
)
.
exists
(),
).
toBe
(
true
);
it
(
'
sets isLoading=false on the metric chart
'
,
()
=>
{
expect
(
findMainMetricChart
().
props
(
'
isLoading
'
)).
toBe
(
false
);
});
});
describe
(
'
when chart finished loading
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
state
.
charts
.
charts
[
chartKeys
.
timeBasedHistogram
].
isLoading
=
false
;
it
(
'
passes non-empty chartData to the metric chart
'
,
()
=>
{
expect
(
findMainMetricChart
().
props
(
'
chartData
'
)).
not
.
toEqual
([]);
});
describe
(
'
and the chart has data
'
,
()
=>
{
describe
(
'
when an item on the chart is clicked
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
state
.
charts
.
charts
[
chartKeys
.
timeBasedHistogram
].
data
=
{
1
:
2
,
2
:
3
};
});
const
data
=
{
chart
:
null
,
params
:
{
data
:
{
value
:
[
0
,
1
],
},
},
};
it
(
'
renders a metric type dropdown
'
,
()
=>
{
expect
(
findTimeBasedSection
()
.
find
(
GlDropdown
)
.
exists
(),
).
toBe
(
true
);
findMainMetricChart
()
.
find
(
GlColumnChart
)
.
vm
.
$emit
(
'
chartItemClicked
'
,
data
);
});
it
(
'
should change the metric type
'
,
()
=>
{
findTimeBasedSection
()
.
findAll
(
GlDropdownItem
)
.
at
(
0
)
.
vm
.
$emit
(
'
click
'
);
expect
(
actionSpies
.
setMetricType
).
toHaveBeenCalledWith
({
metricType
:
'
time_to_first_comment
'
,
chartKey
:
chartKeys
.
timeBasedHistogram
,
it
(
'
dispatches chartItemClicked action
'
,
()
=>
{
expect
(
actionSpies
.
chartItemClicked
).
toHaveBeenCalledWith
({
chartKey
:
chartKeys
.
main
,
item
:
0
,
});
});
it
(
'
renders a column chart
'
,
()
=>
{
expect
(
findTimeBasedSection
()
.
find
(
GlColumnChart
)
.
exists
(),
).
toBe
(
true
);
it
(
'
dispatches setMergeRequestsPage action
'
,
()
=>
{
expect
(
actionSpies
.
setMergeRequestsPage
).
toHaveBeenCalledWith
(
0
);
});
});
describe
(
"
and the chart doesn't have any data
"
,
()
=>
{
beforeEach
(
()
=>
{
store
.
state
.
charts
.
charts
[
chartKeys
.
timeBasedHistogram
].
data
=
null
;
describe
(
'
Time based histogram
'
,
()
=>
{
it
(
'
renders a metric chart component
'
,
()
=>
{
expect
(
findTimeBasedMetricChart
().
exists
()).
toBe
(
true
)
;
});
it
(
'
renders a "no data" message
'
,
()
=>
{
expect
(
findTimeBasedSection
().
text
()).
toContain
(
'
There is no data for the selected metric. Please change your selection.
'
,
);
});
});
});
});
describe
(
'
when chart finished loading
'
,
()
=>
{
describe
(
'
and the chart has data
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
.
vm
.
$store
.
dispatch
(
'
charts/receiveChartDataSuccess
'
,
{
chartKey
:
chartKeys
.
timeBasedHistogram
,
data
:
{
1
:
2
,
2
:
3
},
});
});
describe
(
'
Commit based histogram
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
state
.
charts
.
charts
[
chartKeys
.
main
].
isLoading
=
false
;
store
.
state
.
charts
.
charts
[
chartKeys
.
main
].
data
=
{
1
:
2
,
2
:
3
};
});
it
(
'
sets isLoading=false on the metric chart
'
,
()
=>
{
expect
(
findTimeBasedMetricChart
().
props
(
'
isLoading
'
)).
toBe
(
false
);
});
describe
(
'
when chart is loading
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
state
.
charts
.
charts
[
chartKeys
.
commitBasedHistogram
].
isLoading
=
true
;
});
it
(
'
passes non-empty chartData to the metric chart
'
,
()
=>
{
expect
(
findTimeBasedMetricChart
().
props
(
'
chartData
'
)).
not
.
toEqual
([]);
});
it
(
'
renders a loading indicator
'
,
()
=>
{
expect
(
findCommitBasedSection
()
.
find
(
GlLoadingIcon
)
.
exists
(),
).
toBe
(
true
);
});
});
it
(
'
should call setMetricType when `metricTypeChange` is emitted on the metric chart
'
,
()
=>
{
findTimeBasedMetricChart
().
vm
.
$emit
(
'
metricTypeChange
'
,
'
time_to_merge
'
);
describe
(
'
when chart finished loading
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
state
.
charts
.
charts
[
chartKeys
.
commitBasedHistogram
].
isLoading
=
false
;
expect
(
actionSpies
.
setMetricType
).
toHaveBeenCalledWith
({
metricType
:
'
time_to_merge
'
,
chartKey
:
chartKeys
.
timeBasedHistogram
,
});
});
});
});
});
describe
(
'
and the chart has data
'
,
()
=>
{
beforeEach
(
()
=>
{
store
.
state
.
charts
.
charts
[
chartKeys
.
commitBasedHistogram
].
data
=
{
1
:
2
,
2
:
3
}
;
describe
(
'
Commit based histogram
'
,
()
=>
{
it
(
'
renders a metric chart component
'
,
()
=>
{
expect
(
findCommitBasedMetricChart
().
exists
()).
toBe
(
true
)
;
});
it
(
'
renders a column chart
'
,
()
=>
{
expect
(
findCommitBasedSection
()
.
find
(
GlColumnChart
)
.
exists
(),
).
toBe
(
true
);
});
describe
(
'
when chart finished loading
'
,
()
=>
{
describe
(
'
and the chart has data
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
.
vm
.
$store
.
dispatch
(
'
charts/receiveChartDataSuccess
'
,
{
chartKey
:
chartKeys
.
commitBasedHistogram
,
data
:
{
1
:
2
,
2
:
3
},
});
});
describe
(
'
metric dropdown
'
,
()
=>
{
it
(
'
renders a metric type dropdown
'
,
()
=>
{
expect
(
findCommitBasedSection
()
.
find
(
GlDropdown
)
.
exists
(),
).
toBe
(
true
);
});
it
(
'
sets isLoading=false on the metric chart
'
,
()
=>
{
expect
(
findCommitBasedMetricChart
().
props
(
'
isLoading
'
)).
toBe
(
false
);
});
describe
(
'
when the user changes the metric
'
,
()
=>
{
beforeEach
(()
=>
{
findCommitBasedSection
()
.
findAll
(
GlDropdownItem
)
.
at
(
0
)
.
vm
.
$emit
(
'
click
'
);
it
(
'
passes non-empty chartData to the metric chart
'
,
()
=>
{
expect
(
findCommitBasedMetricChart
().
props
(
'
chartData
'
)).
not
.
toEqual
([]);
});
it
(
'
should dispatch setMetricType action
'
,
()
=>
{
expect
(
actionSpies
.
setMetricType
).
toHaveBeenCalledWith
({
metricType
:
'
commits_count
'
,
chartKey
:
chartKeys
.
commitBasedHistogram
,
describe
(
'
when the user changes the metric
'
,
()
=>
{
beforeEach
(()
=>
{
findCommitBasedMetricChart
().
vm
.
$emit
(
'
metricTypeChange
'
,
'
loc_per_commit
'
);
});
});
it
(
"
should update the chart's x axis label
"
,
()
=>
{
const
columnChart
=
findCommitBasedSection
().
find
(
GlColumnChart
);
expect
(
columnChart
.
props
(
'
xAxisTitle
'
)).
toBe
(
'
Number of commits per MR
'
);
it
(
'
should call setMetricType when `metricTypeChange` is emitted on the metric chart
'
,
()
=>
{
expect
(
actionSpies
.
setMetricType
).
toHaveBeenCalledWith
({
metricType
:
'
loc_per_commit
'
,
chartKey
:
chartKeys
.
commitBasedHistogram
,
});
});
it
(
"
should update the chart's x axis label
"
,
()
=>
{
const
columnChart
=
findCommitBasedMetricChart
().
find
(
GlColumnChart
);
expect
(
columnChart
.
props
(
'
xAxisTitle
'
)).
toBe
(
'
Number of commits per MR
'
);
});
});
});
});
});
describe
(
"
and the chart doesn't have any data
"
,
()
=>
{
beforeEach
(()
=>
{
store
.
state
.
charts
.
charts
[
chartKeys
.
commitBasedHistogram
].
data
=
null
;
});
describe
(
'
MR table
'
,
()
=>
{
describe
(
'
when table is loading
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
.
vm
.
$store
.
dispatch
(
'
table/requestMergeRequests
'
);
});
it
(
'
renders a "no data" message
'
,
()
=>
{
expect
(
findTimeBasedSection
().
text
()).
toContain
(
'
There is no data for the selected metric. Please change your selection.
'
,
);
it
(
'
renders a loading indicator
'
,
()
=>
{
expect
(
findMrTableSection
()
.
find
(
GlLoadingIcon
)
.
exists
(),
).
toBe
(
true
);
});
});
});
});
});
describe
(
'
MR table
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
state
.
charts
.
charts
[
chartKeys
.
main
].
isLoading
=
false
;
store
.
state
.
charts
.
charts
[
chartKeys
.
main
].
data
=
{
1
:
2
,
2
:
3
};
});
describe
(
'
when table is loading
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
state
.
table
.
isLoadingTable
=
true
;
});
describe
(
'
when table finished loading
'
,
()
=>
{
describe
(
'
and the table has data
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
.
vm
.
$store
.
dispatch
(
'
table/receiveMergeRequestsSuccess
'
,
{
headers
:
{},
data
:
[{
id
:
1
,
title
:
'
This is a test MR
'
}],
});
});
it
(
'
renders a loading indicator
'
,
()
=>
{
expect
(
findMrTableSection
()
.
find
(
GlLoadingIcon
)
.
exists
(),
).
toBe
(
true
);
});
});
it
(
'
renders the MR table
'
,
()
=>
{
expect
(
findMrTable
().
exists
()).
toBe
(
true
);
});
describe
(
'
when table finished loading
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
state
.
table
.
isLoadingTable
=
false
;
});
it
(
'
doesn’t render a "no data" message
'
,
()
=>
{
expect
(
findMrTableSection
()
.
find
(
'
.js-no-data
'
)
.
exists
(),
).
toBe
(
false
);
});
describe
(
'
and the table has data
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
state
.
table
.
mergeRequests
=
[{
id
:
1
,
title
:
'
This is a test MR
'
}];
});
it
(
'
should change the column metric
'
,
()
=>
{
findMrTable
().
vm
.
$emit
(
'
columnMetricChange
'
,
'
time_to_first_comment
'
);
expect
(
actionSpies
.
setColumnMetric
).
toHaveBeenCalledWith
(
'
time_to_first_comment
'
,
);
});
it
(
'
renders the MR table
'
,
()
=>
{
expect
(
findMrTable
().
exists
()).
toBe
(
true
);
});
it
(
'
should change the page
'
,
()
=>
{
const
page
=
2
;
findMrTable
().
vm
.
$emit
(
'
pageChange
'
,
page
);
expect
(
actionSpies
.
setMergeRequestsPage
).
toHaveBeenCalledWith
(
page
);
});
it
(
'
doesn’t render a "no data" message
'
,
()
=>
{
expect
(
findMrTableSection
()
.
find
(
'
.js-no-data
'
)
.
exists
(),
).
toBe
(
false
);
});
describe
(
'
sort controls
'
,
()
=>
{
it
(
'
renders the sort dropdown and button
'
,
()
=>
{
expect
(
findSortFieldDropdown
().
exists
()).
toBe
(
true
);
expect
(
findSortOrderToggle
().
exists
()).
toBe
(
true
);
});
it
(
'
should change the column metric
'
,
()
=>
{
findMrTable
().
vm
.
$emit
(
'
columnMetricChange
'
,
'
time_to_first_comment
'
);
expect
(
actionSpies
.
setColumnMetric
).
toHaveBeenCalledWith
(
'
time_to_first_comment
'
);
});
it
(
'
should change the sort field
'
,
()
=>
{
findSortFieldDropdown
()
.
findAll
(
GlDropdownItem
)
.
at
(
0
)
.
vm
.
$emit
(
'
click
'
);
it
(
'
should change the page
'
,
()
=>
{
const
page
=
2
;
findMrTable
().
vm
.
$emit
(
'
pageChange
'
,
page
);
expect
(
actionSpies
.
setMergeRequestsPage
).
toHaveBeenCalledWith
(
page
);
});
expect
(
actionSpies
.
setSortField
).
toHaveBeenCalled
();
});
describe
(
'
and there are merge requests available
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
state
.
table
.
mergeRequests
=
[{
id
:
1
}];
it
(
'
should toggle the sort order
'
,
()
=>
{
findSortOrderToggle
().
vm
.
$emit
(
'
click
'
);
expect
(
actionSpies
.
toggleSortOrder
).
toHaveBeenCalled
();
});
});
});
describe
(
'
sort controls
'
,
()
=>
{
it
(
'
renders the sort dropdown and button
'
,
()
=>
{
expect
(
findSortFieldDropdown
().
exists
()).
toBe
(
true
);
expect
(
findSortOrderToggle
().
exists
()).
toBe
(
true
);
describe
(
"
and the table doesn't have any data
"
,
()
=>
{
beforeEach
(()
=>
{
wrapper
.
vm
.
$store
.
dispatch
(
'
table/receiveMergeRequestsSuccess
'
,
{
headers
:
{},
data
:
[],
});
});
it
(
'
should change the sort field
'
,
()
=>
{
findSortFieldDropdown
()
.
findAll
(
GlDropdownItem
)
.
at
(
0
)
.
vm
.
$emit
(
'
click
'
);
it
(
'
renders a "no data" message
'
,
()
=>
{
expect
(
findMrTableSection
()
.
find
(
'
.js-no-data
'
)
.
exists
(),
).
toBe
(
true
);
});
expect
(
actionSpies
.
setSortField
).
toHaveBeenCalled
();
it
(
'
doesn`t render the MR table
'
,
()
=>
{
expect
(
findMrTable
().
exists
()).
not
.
toBe
(
true
);
});
it
(
'
should toggle the sort order
'
,
()
=>
{
findSortOrderToggle
().
vm
.
$emit
(
'
click
'
);
expect
(
actionSpies
.
toggleSortOrder
).
toHaveBeenCalled
(
);
it
(
'
doesn`t render the sort dropdown and button
'
,
()
=>
{
expect
(
findSortFieldDropdown
().
exists
()).
not
.
toBe
(
true
);
expect
(
findSortOrderToggle
().
exists
()).
not
.
toBe
(
true
);
});
});
});
});
});
describe
(
"
and the table doesn't have any data
"
,
()
=>
{
beforeEach
(()
=>
{
store
.
state
.
table
.
mergeRequests
=
[];
describe
(
'
and has no data
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
.
vm
.
$store
.
dispatch
(
'
charts/receiveChartDataSuccess
'
,
{
chartKey
:
chartKeys
.
main
,
data
:
{},
});
});
it
(
'
renders a "no data" message
'
,
()
=>
{
expect
(
findMrTableSection
()
.
find
(
'
.js-no-data
'
)
.
exists
(),
).
toBe
(
true
);
});
it
(
'
sets isLoading=false on the metric chart
'
,
()
=>
{
expect
(
findMainMetricChart
().
props
(
'
isLoading
'
)).
toBe
(
false
);
});
it
(
'
doesn`t render the MR table
'
,
()
=>
{
expect
(
findMrTable
().
exists
()).
not
.
toBe
(
true
);
});
it
(
'
passes an empty array as chartData to the metric chart
'
,
()
=>
{
expect
(
findMainMetricChart
().
props
(
'
chartData
'
)).
toEqual
([]
);
});
it
(
'
doesn`t render the sort dropdown and button
'
,
()
=>
{
expect
(
findSortFieldDropdown
().
exists
()).
not
.
toBe
(
true
);
expect
(
findSortOrderToggle
().
exists
()).
not
.
toBe
(
true
);
});
it
(
'
does not render any other charts
'
,
()
=>
{
expect
(
findSecondaryChartsSection
().
exists
()).
toBe
(
false
);
});
it
(
'
does not render the MR table
'
,
()
=>
{
expect
(
findMrTableSortSection
().
exists
()).
toBe
(
false
);
expect
(
findMrTableSection
().
exists
()).
toBe
(
false
);
});
});
});
...
...
ee/spec/frontend/analytics/productivity_analytics/store/modules/charts/getters_spec.js
View file @
24281d62
...
...
@@ -178,6 +178,19 @@ describe('Productivity analytics chart getters', () => {
});
});
describe
(
'
getSelectedMetric
'
,
()
=>
{
it
(
'
returns the currently selected metric for a given chartKey
'
,
()
=>
{
const
metricType
=
'
time_to_last_commit
'
;
state
.
charts
[
chartKeys
.
timeBasedHistogram
].
params
=
{
metricType
,
};
expect
(
getters
.
getSelectedMetric
(
state
)(
chartKeys
.
timeBasedHistogram
)).
toBe
(
'
time_to_last_commit
'
,
);
});
});
describe
(
'
hasNoAccessError
'
,
()
=>
{
it
(
'
returns true if errorCode is set to 403
'
,
()
=>
{
state
.
charts
[
chartKeys
.
main
].
errorCode
=
403
;
...
...
locale/gitlab.pot
View file @
24281d62
...
...
@@ -15728,9 +15728,6 @@ msgstr ""
msgid "There is no data available. Please change your selection."
msgstr ""
msgid "There is no data for the selected metric. Please change your selection."
msgstr ""
msgid "There was a problem communicating with your device."
msgstr ""
...
...
@@ -17981,7 +17978,7 @@ msgstr ""
msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
msgstr ""
msgid "You can filter by
\"days to merge\"
by clicking on the columns in the chart."
msgid "You can filter by
'days to merge'
by clicking on the columns in the chart."
msgstr ""
msgid "You can invite a new member to <strong>%{project_name}</strong> or invite another group."
...
...
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