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
2ca109da
Commit
2ca109da
authored
Apr 05, 2019
by
Scott Hampton
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add metrics reports merge request widget
Adding a merge request widget that shows metrics reports.
parent
dd7ebf75
Changes
22
Hide whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
967 additions
and
3 deletions
+967
-3
app/assets/javascripts/reports/components/issue_status_icon.vue
...sets/javascripts/reports/components/issue_status_icon.vue
+6
-1
app/assets/javascripts/reports/components/report_item.vue
app/assets/javascripts/reports/components/report_item.vue
+6
-1
app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
...merge_request_widget/components/mr_widget_status_icon.vue
+1
-1
ee/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
...avascripts/vue_merge_request_widget/mr_widget_options.vue
+7
-0
ee/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
...cripts/vue_merge_request_widget/stores/mr_widget_store.js
+1
-0
ee/app/assets/javascripts/vue_shared/components/reports/issue_body.js
...s/javascripts/vue_shared/components/reports/issue_body.js
+3
-0
ee/app/assets/javascripts/vue_shared/metrics_reports/components/metrics_reports_issue_body.vue
...metrics_reports/components/metrics_reports_issue_body.vue
+56
-0
ee/app/assets/javascripts/vue_shared/metrics_reports/constants.js
...ssets/javascripts/vue_shared/metrics_reports/constants.js
+3
-0
ee/app/assets/javascripts/vue_shared/metrics_reports/grouped_metrics_reports_app.vue
...ue_shared/metrics_reports/grouped_metrics_reports_app.vue
+104
-0
ee/app/assets/javascripts/vue_shared/metrics_reports/store/actions.js
...s/javascripts/vue_shared/metrics_reports/store/actions.js
+24
-0
ee/app/assets/javascripts/vue_shared/metrics_reports/store/getters.js
...s/javascripts/vue_shared/metrics_reports/store/getters.js
+22
-0
ee/app/assets/javascripts/vue_shared/metrics_reports/store/index.js
...ets/javascripts/vue_shared/metrics_reports/store/index.js
+16
-0
ee/app/assets/javascripts/vue_shared/metrics_reports/store/mutation_types.js
...cripts/vue_shared/metrics_reports/store/mutation_types.js
+5
-0
ee/app/assets/javascripts/vue_shared/metrics_reports/store/mutations.js
...javascripts/vue_shared/metrics_reports/store/mutations.js
+34
-0
ee/app/assets/javascripts/vue_shared/metrics_reports/store/state.js
...ets/javascripts/vue_shared/metrics_reports/store/state.js
+20
-0
ee/changelogs/unreleased/9788-add-generic-metrics-report-type-to-merge-requests.yml
...788-add-generic-metrics-report-type-to-merge-requests.yml
+5
-0
ee/spec/javascripts/vue_shared/metrics_reports/components/metrics_reports_issue_body_spec.js
...ics_reports/components/metrics_reports_issue_body_spec.js
+98
-0
ee/spec/javascripts/vue_shared/metrics_reports/grouped_metrics_reports_app_spec.js
...hared/metrics_reports/grouped_metrics_reports_app_spec.js
+190
-0
ee/spec/javascripts/vue_shared/metrics_reports/store/actions_spec.js
...ascripts/vue_shared/metrics_reports/store/actions_spec.js
+152
-0
ee/spec/javascripts/vue_shared/metrics_reports/store/getters_spec.js
...ascripts/vue_shared/metrics_reports/store/getters_spec.js
+114
-0
ee/spec/javascripts/vue_shared/metrics_reports/store/mutations_spec.js
...cripts/vue_shared/metrics_reports/store/mutations_spec.js
+77
-0
locale/gitlab.pot
locale/gitlab.pot
+23
-0
No files found.
app/assets/javascripts/reports/components/issue_status_icon.vue
View file @
2ca109da
...
...
@@ -13,6 +13,11 @@ export default {
type
:
String
,
required
:
true
,
},
statusIconSize
:
{
type
:
Number
,
required
:
false
,
default
:
32
,
},
},
computed
:
{
iconName
()
{
...
...
@@ -45,6 +50,6 @@ export default {
}"
class="report-block-list-icon"
>
<icon
:name=
"iconName"
:size=
"
32
"
/>
<icon
:name=
"iconName"
:size=
"
statusIconSize
"
/>
</div>
</
template
>
app/assets/javascripts/reports/components/report_item.vue
View file @
2ca109da
...
...
@@ -24,6 +24,11 @@ export default {
type
:
String
,
required
:
true
,
},
statusIconSize
:
{
type
:
Number
,
required
:
false
,
default
:
32
,
},
isNew
:
{
type
:
Boolean
,
required
:
false
,
...
...
@@ -34,7 +39,7 @@ export default {
</
script
>
<
template
>
<li
:class=
"
{ 'is-dismissed': issue.isDismissed }" class="report-block-list-issue">
<issue-status-icon
:status=
"status"
class=
"append-right-5"
/>
<issue-status-icon
:status=
"status"
:status-icon-size=
"statusIconSize"
class=
"append-right-5"
/>
<component
:is=
"component"
v-if=
"component"
:issue=
"issue"
:status=
"status"
:is-new=
"isNew"
/>
</li>
...
...
app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
View file @
2ca109da
...
...
@@ -33,7 +33,7 @@ export default {
</
script
>
<
template
>
<div
class=
"space-children d-flex append-right-10 widget-status-icon"
>
<div
v-if=
"isLoading"
class=
"mr-widget-icon"
><gl-loading-icon
/></div>
<div
v-if=
"isLoading"
class=
"mr-widget-icon"
><gl-loading-icon
size=
"md"
/></div>
<ci-icon
v-else
:status=
"statusObj"
:size=
"24"
/>
...
...
ee/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
View file @
2ca109da
<
script
>
import
ReportSection
from
'
~/reports/components/report_section.vue
'
;
import
GroupedSecurityReportsApp
from
'
ee/vue_shared/security_reports/grouped_security_reports_app.vue
'
;
import
GroupedMetricsReportsApp
from
'
ee/vue_shared/metrics_reports/grouped_metrics_reports_app.vue
'
;
import
reportsMixin
from
'
ee/vue_shared/security_reports/mixins/reports_mixin
'
;
import
{
componentNames
}
from
'
ee/vue_shared/components/reports/issue_body
'
;
import
MrWidgetLicenses
from
'
ee/vue_shared/license_management/mr_widget_license_report.vue
'
;
...
...
@@ -16,6 +17,7 @@ export default {
MrWidgetApprovals
,
MrWidgetGeoSecondaryNode
,
GroupedSecurityReportsApp
,
GroupedMetricsReportsApp
,
ReportSection
,
},
extends
:
CEWidgetOptions
,
...
...
@@ -239,6 +241,11 @@ export default {
:component=
"$options.componentNames.PerformanceIssueBody"
class=
"js-performance-widget mr-widget-border-top mr-report"
/>
<grouped-metrics-reports-app
v-if=
"mr.metricsReportsPath"
:endpoint=
"mr.metricsReportsPath"
class=
"js-metrics-reports-container"
/>
<grouped-security-reports-app
v-if=
"shouldRenderSecurityReport"
:head-blob-path=
"mr.headBlobPath"
...
...
ee/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
View file @
2ca109da
...
...
@@ -26,6 +26,7 @@ export default class MergeRequestStore extends CEMergeRequestStore {
this
.
initCodeclimate
(
data
);
this
.
initPerformanceReport
(
data
);
this
.
licenseManagement
=
data
.
license_management
;
this
.
metricsReportsPath
=
data
.
metrics_reports_path
;
}
setData
(
data
,
isRebased
)
{
...
...
ee/app/assets/javascripts/vue_shared/components/reports/issue_body.js
View file @
2ca109da
...
...
@@ -8,6 +8,7 @@ import LicenseIssueBody from 'ee/vue_shared/license_management/components/licens
import
SastIssueBody
from
'
ee/vue_shared/security_reports/components/sast_issue_body.vue
'
;
import
SastContainerIssueBody
from
'
ee/vue_shared/security_reports/components/sast_container_issue_body.vue
'
;
import
DastIssueBody
from
'
ee/vue_shared/security_reports/components/dast_issue_body.vue
'
;
import
MetricsReportsIssueBody
from
'
ee/vue_shared/metrics_reports/components/metrics_reports_issue_body.vue
'
;
export
const
components
=
{
...
componentsCE
,
...
...
@@ -17,6 +18,7 @@ export const components = {
SastContainerIssueBody
,
SastIssueBody
,
DastIssueBody
,
MetricsReportsIssueBody
,
};
export
const
componentNames
=
{
...
...
@@ -27,4 +29,5 @@ export const componentNames = {
SastContainerIssueBody
:
SastContainerIssueBody
.
name
,
SastIssueBody
:
SastIssueBody
.
name
,
DastIssueBody
:
DastIssueBody
.
name
,
MetricsReportsIssueBody
:
MetricsReportsIssueBody
.
name
,
};
ee/app/assets/javascripts/vue_shared/metrics_reports/components/metrics_reports_issue_body.vue
0 → 100644
View file @
2ca109da
<
script
>
import
{
__
}
from
'
~/locale
'
;
import
{
GlBadge
}
from
'
@gitlab/ui
'
;
export
default
{
name
:
'
MetricsReportsIssueBody
'
,
components
:
{
GlBadge
,
},
props
:
{
issue
:
{
type
:
Object
,
required
:
true
,
validator
(
obj
)
{
return
obj
.
name
!==
undefined
&&
obj
.
value
!==
undefined
;
},
},
},
computed
:
{
shouldShowBadge
()
{
return
this
.
issue
.
isNew
||
this
.
issue
.
wasRemoved
;
},
badgeText
()
{
if
(
this
.
issue
.
isNew
)
{
return
__
(
'
New
'
);
}
return
__
(
'
Removed
'
);
},
/*
* If metric is new or removed, we do not need to show previous value
*/
previousValue
()
{
if
(
this
.
shouldShowBadge
)
{
return
''
;
}
if
(
this
.
issue
.
previous_value
)
{
return
`(
${
this
.
issue
.
previous_value
}
)`
;
}
return
__
(
'
(No changes)
'
);
},
},
};
</
script
>
<
template
>
<div
class=
"report-block-list-issue-description"
>
<div
class=
"report-block-list-issue-description-text js-metrics-reports-issue-text"
>
{{
issue
.
name
}}
:
{{
issue
.
value
}}
{{
previousValue
}}
</div>
<gl-badge
v-if=
"shouldShowBadge"
variant=
"info"
class=
"js-metrics-reports-issue-badge"
>
{{
badgeText
}}
</gl-badge>
</div>
</
template
>
ee/app/assets/javascripts/vue_shared/metrics_reports/constants.js
0 → 100644
View file @
2ca109da
export
const
LOADING
=
'
LOADING
'
;
export
const
ERROR
=
'
ERROR
'
;
export
const
SUCCESS
=
'
SUCCESS
'
;
ee/app/assets/javascripts/vue_shared/metrics_reports/grouped_metrics_reports_app.vue
0 → 100644
View file @
2ca109da
<
script
>
import
{
mapActions
,
mapGetters
,
mapState
}
from
'
vuex
'
;
import
{
componentNames
}
from
'
ee/vue_shared/components/reports/issue_body
'
;
import
SmartVirtualList
from
'
~/vue_shared/components/smart_virtual_list.vue
'
;
import
ReportSection
from
'
~/reports/components/report_section.vue
'
;
import
SummaryRow
from
'
~/reports/components/summary_row.vue
'
;
import
ReportItem
from
'
~/reports/components/report_item.vue
'
;
import
{
n__
,
s__
,
sprintf
}
from
'
~/locale
'
;
import
createStore
from
'
./store
'
;
export
default
{
name
:
'
GroupedMetricsReportsApp
'
,
store
:
createStore
(),
components
:
{
ReportSection
,
SummaryRow
,
ReportItem
,
SmartVirtualList
,
},
props
:
{
endpoint
:
{
type
:
String
,
required
:
true
,
},
},
componentNames
,
// Typical height of a report item in px
typicalReportItemHeight
:
32
,
/*
* The maximum amount of shown issues. This is calculated by
* ( max-height of report-block-list / typicalReportItemHeight ) + some safety margin
* We will use VirtualList if we have more items than this number.
* For entries lower than this number, the virtual scroll list calculates the total height of the element wrongly.
*/
maxShownReportItems
:
20
,
computed
:
{
...
mapState
([
'
numberOfChanges
'
,
'
isLoading
'
,
'
hasError
'
]),
...
mapGetters
([
'
summaryStatus
'
,
'
metrics
'
]),
groupedSummaryText
()
{
if
(
this
.
isLoading
)
{
return
s__
(
'
Reports|Metrics reports are loading
'
);
}
if
(
this
.
hasError
)
{
return
s__
(
'
Reports|Metrics reports failed loading results
'
);
}
if
(
this
.
numberOfChanges
<
1
)
{
return
s__
(
'
Reports|Metrics reports did not change
'
);
}
const
pointsString
=
n__
(
'
point
'
,
'
points
'
,
this
.
numberOfChanges
);
return
sprintf
(
s__
(
'
Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}
'
),
{
numberOfChanges
:
this
.
numberOfChanges
,
pointsString
,
});
},
hasChanges
()
{
return
this
.
numberOfChanges
>
0
;
},
hasMetrics
()
{
return
this
.
metrics
.
length
>
0
;
},
},
created
()
{
this
.
setEndpoint
(
this
.
endpoint
);
this
.
fetchMetrics
();
},
methods
:
{
...
mapActions
([
'
setEndpoint
'
,
'
fetchMetrics
'
]),
},
};
</
script
>
<
template
>
<report-section
:status=
"summaryStatus"
:success-text=
"groupedSummaryText"
:loading-text=
"groupedSummaryText"
:error-text=
"groupedSummaryText"
:has-issues=
"hasMetrics"
class=
"mr-widget-border-top grouped-security-reports mr-report"
>
<div
slot=
"body"
class=
"mr-widget-grouped-section report-block"
>
<smart-virtual-list
:length=
"metrics.length"
:remain=
"$options.maxShownReportItems"
:size=
"$options.typicalReportItemHeight"
class=
"report-block-container"
wtag=
"ul"
wclass=
"report-block-list"
>
<report-item
v-for=
"(metric, index) in metrics"
:key=
"index"
:issue=
"metric"
status=
"none"
:status-icon-size=
"24"
:component=
"$options.componentNames.MetricsReportsIssueBody"
class=
"prepend-left-4 prepend-top-4 append-bottom-8"
/>
</smart-virtual-list>
</div>
</report-section>
</
template
>
ee/app/assets/javascripts/vue_shared/metrics_reports/store/actions.js
0 → 100644
View file @
2ca109da
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
*
as
types
from
'
./mutation_types
'
;
export
const
setEndpoint
=
({
commit
},
endpoint
)
=>
commit
(
types
.
SET_ENDPOINT
,
endpoint
);
export
const
requestMetrics
=
({
commit
})
=>
commit
(
types
.
REQUEST_METRICS
);
export
const
fetchMetrics
=
({
state
,
dispatch
})
=>
{
dispatch
(
'
requestMetrics
'
);
return
axios
.
get
(
state
.
endpoint
)
.
then
(
response
=>
dispatch
(
'
receiveMetricsSuccess
'
,
response
.
data
))
.
catch
(()
=>
dispatch
(
'
receiveMetricsError
'
));
};
export
const
receiveMetricsSuccess
=
({
commit
},
response
)
=>
{
commit
(
types
.
RECEIVE_METRICS_SUCCESS
,
response
);
};
export
const
receiveMetricsError
=
({
commit
})
=>
commit
(
types
.
RECEIVE_METRICS_ERROR
);
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export
default
()
=>
{};
ee/app/assets/javascripts/vue_shared/metrics_reports/store/getters.js
0 → 100644
View file @
2ca109da
import
{
LOADING
,
ERROR
,
SUCCESS
}
from
'
../constants
'
;
export
const
summaryStatus
=
state
=>
{
if
(
state
.
isLoading
)
{
return
LOADING
;
}
if
(
state
.
hasError
||
state
.
numberOfChanges
>
0
)
{
return
ERROR
;
}
return
SUCCESS
;
};
export
const
metrics
=
state
=>
[
...
state
.
newMetrics
.
map
(
metric
=>
({
...
metric
,
isNew
:
true
})),
...
state
.
existingMetrics
,
...
state
.
removedMetrics
.
map
(
metric
=>
({
...
metric
,
wasRemoved
:
true
})),
];
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export
default
()
=>
{};
ee/app/assets/javascripts/vue_shared/metrics_reports/store/index.js
0 → 100644
View file @
2ca109da
import
Vue
from
'
vue
'
;
import
Vuex
from
'
vuex
'
;
import
*
as
actions
from
'
./actions
'
;
import
*
as
getters
from
'
./getters
'
;
import
mutations
from
'
./mutations
'
;
import
state
from
'
./state
'
;
Vue
.
use
(
Vuex
);
export
default
()
=>
new
Vuex
.
Store
({
actions
,
mutations
,
getters
,
state
:
state
(),
});
ee/app/assets/javascripts/vue_shared/metrics_reports/store/mutation_types.js
0 → 100644
View file @
2ca109da
export
const
SET_ENDPOINT
=
'
SET_ENDPOINT
'
;
export
const
REQUEST_METRICS
=
'
REQUEST_METRICS
'
;
export
const
RECEIVE_METRICS_SUCCESS
=
'
RECEIVE_METRICS_SUCCESS
'
;
export
const
RECEIVE_METRICS_ERROR
=
'
RECEIVE_METRICS_ERROR
'
;
ee/app/assets/javascripts/vue_shared/metrics_reports/store/mutations.js
0 → 100644
View file @
2ca109da
import
*
as
types
from
'
./mutation_types
'
;
export
default
{
[
types
.
SET_ENDPOINT
](
state
,
endpoint
)
{
state
.
endpoint
=
endpoint
;
},
[
types
.
REQUEST_METRICS
](
state
)
{
state
.
isLoading
=
true
;
},
[
types
.
RECEIVE_METRICS_SUCCESS
](
state
,
response
)
{
// Make sure to clean previous state in case it was an error
state
.
hasError
=
false
;
state
.
isLoading
=
false
;
state
.
newMetrics
=
response
.
new_metrics
||
[];
state
.
existingMetrics
=
response
.
existing_metrics
||
[];
state
.
removedMetrics
=
response
.
removed_metrics
||
[];
state
.
numberOfChanges
=
state
.
existingMetrics
.
filter
(
metric
=>
metric
.
previous_value
!==
undefined
).
length
+
state
.
newMetrics
.
length
+
state
.
removedMetrics
.
length
;
},
[
types
.
RECEIVE_METRICS_ERROR
](
state
)
{
state
.
isLoading
=
false
;
state
.
hasError
=
true
;
state
.
newMetrics
=
[];
state
.
existingMetrics
=
[];
state
.
removedMetrics
=
[];
state
.
numberOfChanges
=
0
;
},
};
ee/app/assets/javascripts/vue_shared/metrics_reports/store/state.js
0 → 100644
View file @
2ca109da
export
default
()
=>
({
endpoint
:
null
,
isLoading
:
false
,
hasError
:
false
,
/**
* Each metric will have the following format:
* {
* name: {String},
* value: {String},
* previous_value: {String}
* }
*/
newMetrics
:
[],
existingMetrics
:
[],
removedMetrics
:
[],
numberOfChanges
:
0
,
});
ee/changelogs/unreleased/9788-add-generic-metrics-report-type-to-merge-requests.yml
0 → 100644
View file @
2ca109da
---
title
:
Added metrics reports widget to merge request page
merge_request
:
10380
author
:
type
:
added
ee/spec/javascripts/vue_shared/metrics_reports/components/metrics_reports_issue_body_spec.js
0 → 100644
View file @
2ca109da
import
{
shallowMount
,
createLocalVue
}
from
'
@vue/test-utils
'
;
import
MetricReportsIssueBody
from
'
ee/vue_shared/metrics_reports/components/metrics_reports_issue_body.vue
'
;
const
localVue
=
createLocalVue
();
describe
(
'
Metrics reports issue body
'
,
()
=>
{
const
Component
=
localVue
.
extend
(
MetricReportsIssueBody
);
let
wrapper
;
afterEach
(()
=>
{
if
(
wrapper
)
{
wrapper
.
destroy
();
}
});
describe
(
'
when metric did not change
'
,
()
=>
{
it
(
'
should render metric with no changes text
'
,
()
=>
{
wrapper
=
shallowMount
(
Component
,
{
sync
:
false
,
localVue
,
propsData
:
{
issue
:
{
name
:
'
name
'
,
value
:
'
value
'
,
},
},
});
const
metric
=
wrapper
.
element
.
querySelector
(
'
.js-metrics-reports-issue-text
'
);
expect
(
metric
.
innerText
.
trim
()).
toEqual
(
'
name: value (No changes)
'
);
});
});
describe
(
'
when metric changed
'
,
()
=>
{
it
(
'
should render metric with change
'
,
()
=>
{
wrapper
=
shallowMount
(
Component
,
{
sync
:
false
,
localVue
,
propsData
:
{
issue
:
{
name
:
'
name
'
,
value
:
'
value
'
,
previous_value
:
'
prev
'
,
},
},
});
const
metric
=
wrapper
.
element
.
querySelector
(
'
.js-metrics-reports-issue-text
'
);
expect
(
metric
.
innerText
.
trim
()).
toEqual
(
'
name: value (prev)
'
);
});
});
describe
(
'
when metric is new
'
,
()
=>
{
it
(
'
should render metric with new badge
'
,
()
=>
{
wrapper
=
shallowMount
(
Component
,
{
sync
:
false
,
localVue
,
propsData
:
{
issue
:
{
name
:
'
name
'
,
value
:
'
value
'
,
isNew
:
true
,
},
},
});
const
metric
=
wrapper
.
element
.
querySelector
(
'
.js-metrics-reports-issue-text
'
);
const
badge
=
wrapper
.
element
.
querySelector
(
'
.js-metrics-reports-issue-badge
'
);
expect
(
metric
.
innerText
.
trim
()).
toEqual
(
'
name: value
'
);
expect
(
badge
.
innerText
.
trim
()).
toEqual
(
'
New
'
);
});
});
describe
(
'
when metric was removed
'
,
()
=>
{
it
(
'
should render metric with removed badge
'
,
()
=>
{
wrapper
=
shallowMount
(
Component
,
{
sync
:
false
,
localVue
,
propsData
:
{
issue
:
{
name
:
'
name
'
,
value
:
'
value
'
,
wasRemoved
:
true
,
},
},
});
const
metric
=
wrapper
.
element
.
querySelector
(
'
.js-metrics-reports-issue-text
'
);
const
badge
=
wrapper
.
element
.
querySelector
(
'
.js-metrics-reports-issue-badge
'
);
expect
(
metric
.
innerText
.
trim
()).
toEqual
(
'
name: value
'
);
expect
(
badge
.
innerText
.
trim
()).
toEqual
(
'
Removed
'
);
});
});
});
ee/spec/javascripts/vue_shared/metrics_reports/grouped_metrics_reports_app_spec.js
0 → 100644
View file @
2ca109da
import
{
mount
,
createLocalVue
}
from
'
@vue/test-utils
'
;
import
Vuex
from
'
vuex
'
;
import
GroupedMetricsReportsApp
from
'
ee/vue_shared/metrics_reports/grouped_metrics_reports_app.vue
'
;
import
MetricsReportsIssueBody
from
'
ee/vue_shared/metrics_reports/components/metrics_reports_issue_body.vue
'
;
import
store
from
'
ee/vue_shared/metrics_reports/store
'
;
const
localVue
=
createLocalVue
();
localVue
.
use
(
Vuex
);
describe
(
'
Grouped metrics reports app
'
,
()
=>
{
const
Component
=
localVue
.
extend
(
GroupedMetricsReportsApp
);
let
wrapper
;
let
mockStore
;
const
mountComponent
=
()
=>
{
wrapper
=
mount
(
Component
,
{
sync
:
false
,
store
:
mockStore
,
localVue
,
propsData
:
{
endpoint
:
'
metrics.json
'
,
},
methods
:
{
fetchMetrics
:
()
=>
{},
},
});
};
beforeEach
(()
=>
{
mockStore
=
store
();
mountComponent
();
});
afterEach
(()
=>
{
wrapper
.
destroy
();
});
describe
(
'
while loading
'
,
()
=>
{
beforeEach
(()
=>
{
mockStore
.
state
.
isLoading
=
true
;
mountComponent
();
});
it
(
'
renders loading state
'
,
()
=>
{
const
header
=
wrapper
.
element
.
querySelector
(
'
.js-code-text
'
);
expect
(
header
.
innerText
.
trim
()).
toEqual
(
'
Metrics reports are loading
'
);
});
});
describe
(
'
with error
'
,
()
=>
{
beforeEach
(()
=>
{
mockStore
.
state
.
isLoading
=
false
;
mockStore
.
state
.
hasError
=
true
;
mountComponent
();
});
it
(
'
renders error state
'
,
()
=>
{
const
header
=
wrapper
.
element
.
querySelector
(
'
.js-code-text
'
);
expect
(
header
.
innerText
.
trim
()).
toEqual
(
'
Metrics reports failed loading results
'
);
});
});
describe
(
'
with metrics
'
,
()
=>
{
describe
(
'
with no changes
'
,
()
=>
{
beforeEach
(()
=>
{
mockStore
.
state
.
numberOfChanges
=
0
;
mockStore
.
state
.
existingMetrics
=
[
{
name
:
'
name
'
,
value
:
'
value
'
,
},
];
mountComponent
();
});
it
(
'
renders no changes header
'
,
()
=>
{
const
header
=
wrapper
.
element
.
querySelector
(
'
.js-code-text
'
);
expect
(
header
.
innerText
.
trim
()).
toContain
(
'
Metrics reports did not change
'
);
});
});
describe
(
'
with one change
'
,
()
=>
{
beforeEach
(()
=>
{
mockStore
.
state
.
numberOfChanges
=
1
;
mockStore
.
state
.
existingMetrics
=
[
{
name
:
'
name
'
,
value
:
'
value
'
,
previous_value
:
'
prev
'
,
},
];
mountComponent
();
});
it
(
'
renders one change header
'
,
()
=>
{
const
header
=
wrapper
.
element
.
querySelector
(
'
.js-code-text
'
);
expect
(
header
.
innerText
.
trim
()).
toContain
(
'
Metrics reports changed on 1 point
'
);
});
});
describe
(
'
with multiple changes
'
,
()
=>
{
beforeEach
(()
=>
{
mockStore
.
state
.
numberOfChanges
=
2
;
mockStore
.
state
.
existingMetrics
=
[
{
name
:
'
name
'
,
value
:
'
value
'
,
previous_value
:
'
prev
'
,
},
{
name
:
'
name
'
,
value
:
'
value
'
,
previous_value
:
'
prev
'
,
},
];
mountComponent
();
});
it
(
'
renders multiple changes header
'
,
()
=>
{
const
header
=
wrapper
.
element
.
querySelector
(
'
.js-code-text
'
);
expect
(
header
.
innerText
.
trim
()).
toContain
(
'
Metrics reports changed on 2 points
'
);
});
});
describe
(
'
with new metrics
'
,
()
=>
{
beforeEach
(()
=>
{
mockStore
.
state
.
numberOfChanges
=
1
;
mockStore
.
state
.
newMetrics
=
[
{
name
:
'
name
'
,
value
:
'
value
'
,
},
];
mountComponent
();
});
it
(
'
renders new changes header
'
,
()
=>
{
const
header
=
wrapper
.
element
.
querySelector
(
'
.js-code-text
'
);
expect
(
header
.
innerText
.
trim
()).
toContain
(
'
Metrics reports changed on 1 point
'
);
});
});
describe
(
'
with removed metrics
'
,
()
=>
{
beforeEach
(()
=>
{
mockStore
.
state
.
numberOfChanges
=
1
;
mockStore
.
state
.
removedMetrics
=
[
{
name
:
'
name
'
,
value
:
'
value
'
,
},
];
mountComponent
();
});
it
(
'
renders new changes header
'
,
()
=>
{
const
header
=
wrapper
.
element
.
querySelector
(
'
.js-code-text
'
);
expect
(
header
.
innerText
.
trim
()).
toContain
(
'
Metrics reports changed on 1 point
'
);
});
});
describe
(
'
when has metrics
'
,
()
=>
{
beforeEach
(()
=>
{
mockStore
.
state
.
numberOfChanges
=
1
;
mockStore
.
state
.
existingMetrics
=
[
{
name
:
'
name
'
,
value
:
'
value
'
,
previous_value
:
'
prev
'
,
},
];
mountComponent
();
});
it
(
'
renders custom metric issue body
'
,
()
=>
{
const
issueBody
=
wrapper
.
find
(
MetricsReportsIssueBody
);
expect
(
issueBody
.
props
(
'
issue
'
).
name
).
toEqual
(
'
name
'
);
expect
(
issueBody
.
props
(
'
issue
'
).
value
).
toEqual
(
'
value
'
);
expect
(
issueBody
.
props
(
'
issue
'
).
previous_value
).
toEqual
(
'
prev
'
);
});
});
});
});
ee/spec/javascripts/vue_shared/metrics_reports/store/actions_spec.js
0 → 100644
View file @
2ca109da
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
setEndpoint
,
requestMetrics
,
fetchMetrics
,
receiveMetricsSuccess
,
receiveMetricsError
,
}
from
'
ee/vue_shared/metrics_reports/store/actions
'
;
import
*
as
types
from
'
ee/vue_shared/metrics_reports/store/mutation_types
'
;
import
state
from
'
ee/vue_shared/metrics_reports/store/state
'
;
import
testAction
from
'
spec/helpers/vuex_action_helper
'
;
describe
(
'
metrics reports actions
'
,
()
=>
{
let
mockedState
;
let
mock
;
beforeEach
(()
=>
{
mockedState
=
state
();
mock
=
new
MockAdapter
(
axios
);
});
afterEach
(()
=>
{
mock
.
restore
();
});
describe
(
'
setEndpoint
'
,
()
=>
{
it
(
'
should commit set endpoint
'
,
done
=>
{
testAction
(
setEndpoint
,
'
path
'
,
mockedState
,
[
{
type
:
types
.
SET_ENDPOINT
,
payload
:
'
path
'
,
},
],
[],
done
,
);
});
});
describe
(
'
requestMetrics
'
,
()
=>
{
it
(
'
should commit request mutation
'
,
done
=>
{
testAction
(
requestMetrics
,
null
,
mockedState
,
[
{
type
:
types
.
REQUEST_METRICS
,
},
],
[],
done
,
);
});
});
describe
(
'
fetchMetrics
'
,
()
=>
{
it
(
'
should call metrics endpoint
'
,
done
=>
{
const
data
=
{
metrics
:
[
{
name
:
'
name
'
,
value
:
'
value
'
,
},
],
};
const
endpoint
=
'
/mock-endpoint.json
'
;
mockedState
.
endpoint
=
endpoint
;
mock
.
onGet
(
endpoint
).
replyOnce
(
200
,
data
);
testAction
(
fetchMetrics
,
null
,
mockedState
,
[],
[
{
type
:
'
requestMetrics
'
,
},
{
payload
:
data
,
type
:
'
receiveMetricsSuccess
'
,
},
],
done
,
);
});
it
(
'
handles errors
'
,
done
=>
{
const
endpoint
=
'
/mock-endpoint.json
'
;
mockedState
.
endpoint
=
endpoint
;
mock
.
onGet
(
endpoint
).
replyOnce
(
500
);
testAction
(
fetchMetrics
,
null
,
mockedState
,
[],
[
{
type
:
'
requestMetrics
'
,
},
{
type
:
'
receiveMetricsError
'
,
},
],
done
,
);
});
});
describe
(
'
receiveMetricsSuccess
'
,
()
=>
{
it
(
'
should commit request mutation
'
,
done
=>
{
const
response
=
{
metrics
:
[]
};
testAction
(
receiveMetricsSuccess
,
response
,
mockedState
,
[
{
type
:
types
.
RECEIVE_METRICS_SUCCESS
,
payload
:
response
,
},
],
[],
done
,
);
});
});
describe
(
'
receiveMetricsError
'
,
()
=>
{
it
(
'
should commit request mutation
'
,
done
=>
{
testAction
(
receiveMetricsError
,
null
,
mockedState
,
[
{
type
:
types
.
RECEIVE_METRICS_ERROR
,
},
],
[],
done
,
);
});
});
});
ee/spec/javascripts/vue_shared/metrics_reports/store/getters_spec.js
0 → 100644
View file @
2ca109da
import
state
from
'
ee/vue_shared/metrics_reports/store/state
'
;
import
{
summaryStatus
,
metrics
}
from
'
ee/vue_shared/metrics_reports/store/getters
'
;
import
{
LOADING
,
ERROR
,
SUCCESS
}
from
'
ee/vue_shared/metrics_reports/constants
'
;
describe
(
'
metrics reports getters
'
,
()
=>
{
describe
(
'
summaryStatus
'
,
()
=>
{
describe
(
'
when loading
'
,
()
=>
{
it
(
'
returns loading status
'
,
()
=>
{
const
mockState
=
state
();
mockState
.
isLoading
=
true
;
expect
(
summaryStatus
(
mockState
)).
toEqual
(
LOADING
);
});
});
describe
(
'
when there are errors
'
,
()
=>
{
it
(
'
returns error status
'
,
()
=>
{
const
mockState
=
state
();
mockState
.
hasError
=
true
;
mockState
.
numberOfChanges
=
0
;
expect
(
summaryStatus
(
mockState
)).
toEqual
(
ERROR
);
});
});
describe
(
'
when there are changes
'
,
()
=>
{
it
(
'
returns changes status
'
,
()
=>
{
const
mockState
=
state
();
mockState
.
numberOfChanges
=
1
;
expect
(
summaryStatus
(
mockState
)).
toEqual
(
ERROR
);
});
});
describe
(
'
when successful
'
,
()
=>
{
it
(
'
returns loading status
'
,
()
=>
{
const
mockState
=
state
();
mockState
.
numberOfChanges
=
0
;
expect
(
summaryStatus
(
mockState
)).
toEqual
(
SUCCESS
);
});
});
});
describe
(
'
metrics
'
,
()
=>
{
describe
(
'
when state has new metrics
'
,
()
=>
{
it
(
'
returns array with new metrics
'
,
()
=>
{
const
mockState
=
state
();
mockState
.
newMetrics
=
[{
name
:
'
name
'
,
value
:
'
value
'
}];
const
metricsResult
=
metrics
(
mockState
);
expect
(
metricsResult
.
length
).
toEqual
(
1
);
expect
(
metricsResult
[
0
].
name
).
toEqual
(
'
name
'
);
expect
(
metricsResult
[
0
].
value
).
toEqual
(
'
value
'
);
expect
(
metricsResult
[
0
].
isNew
).
toEqual
(
true
);
});
});
describe
(
'
when state has existing metrics
'
,
()
=>
{
it
(
'
returns array with existing metrics
'
,
()
=>
{
const
mockState
=
state
();
mockState
.
existingMetrics
=
[{
name
:
'
name
'
,
value
:
'
value
'
,
previous_value
:
'
prev
'
}];
const
metricsResult
=
metrics
(
mockState
);
expect
(
metricsResult
.
length
).
toEqual
(
1
);
expect
(
metricsResult
[
0
].
name
).
toEqual
(
'
name
'
);
expect
(
metricsResult
[
0
].
value
).
toEqual
(
'
value
'
);
expect
(
metricsResult
[
0
].
previous_value
).
toEqual
(
'
prev
'
);
});
});
describe
(
'
when state has removed metrics
'
,
()
=>
{
it
(
'
returns array with removed metrics
'
,
()
=>
{
const
mockState
=
state
();
mockState
.
removedMetrics
=
[{
name
:
'
name
'
,
value
:
'
value
'
}];
const
metricsResult
=
metrics
(
mockState
);
expect
(
metricsResult
.
length
).
toEqual
(
1
);
expect
(
metricsResult
[
0
].
name
).
toEqual
(
'
name
'
);
expect
(
metricsResult
[
0
].
value
).
toEqual
(
'
value
'
);
expect
(
metricsResult
[
0
].
wasRemoved
).
toEqual
(
true
);
});
});
describe
(
'
when state has new, existing, and removed metrics
'
,
()
=>
{
it
(
'
returns array with new, existing, and removed metrics combined
'
,
()
=>
{
const
mockState
=
state
();
mockState
.
newMetrics
=
[{
name
:
'
name1
'
,
value
:
'
value1
'
}];
mockState
.
existingMetrics
=
[{
name
:
'
name2
'
,
value
:
'
value2
'
,
previous_value
:
'
prev
'
}];
mockState
.
removedMetrics
=
[{
name
:
'
name3
'
,
value
:
'
value3
'
}];
const
metricsResult
=
metrics
(
mockState
);
expect
(
metricsResult
.
length
).
toEqual
(
3
);
expect
(
metricsResult
[
0
].
name
).
toEqual
(
'
name1
'
);
expect
(
metricsResult
[
0
].
value
).
toEqual
(
'
value1
'
);
expect
(
metricsResult
[
0
].
isNew
).
toEqual
(
true
);
expect
(
metricsResult
[
1
].
name
).
toEqual
(
'
name2
'
);
expect
(
metricsResult
[
1
].
value
).
toEqual
(
'
value2
'
);
expect
(
metricsResult
[
2
].
name
).
toEqual
(
'
name3
'
);
expect
(
metricsResult
[
2
].
value
).
toEqual
(
'
value3
'
);
expect
(
metricsResult
[
2
].
wasRemoved
).
toEqual
(
true
);
});
});
describe
(
'
when state has no metrics
'
,
()
=>
{
it
(
'
returns empty array
'
,
()
=>
{
const
mockState
=
state
();
const
metricsResult
=
metrics
(
mockState
);
expect
(
metricsResult
.
length
).
toEqual
(
0
);
});
});
});
});
ee/spec/javascripts/vue_shared/metrics_reports/store/mutations_spec.js
0 → 100644
View file @
2ca109da
import
state
from
'
ee/vue_shared/metrics_reports/store/state
'
;
import
mutations
from
'
ee/vue_shared/metrics_reports/store/mutations
'
;
import
*
as
types
from
'
ee/vue_shared/metrics_reports/store/mutation_types
'
;
describe
(
'
metrics reports mutations
'
,
()
=>
{
let
mockState
;
beforeEach
(()
=>
{
mockState
=
state
();
});
describe
(
'
SET_ENDPOINT
'
,
()
=>
{
it
(
'
should set endpoint
'
,
()
=>
{
mutations
[
types
.
SET_ENDPOINT
](
mockState
,
'
endpoint
'
);
expect
(
mockState
.
endpoint
).
toEqual
(
'
endpoint
'
);
});
});
describe
(
'
REQUEST_METRICS
'
,
()
=>
{
it
(
'
should set isLoading to true
'
,
()
=>
{
mutations
[
types
.
REQUEST_METRICS
](
mockState
);
expect
(
mockState
.
isLoading
).
toEqual
(
true
);
});
});
describe
(
'
RECEIVE_METRICS_SUCCESS
'
,
()
=>
{
it
(
'
should set metrics with zero changes
'
,
()
=>
{
const
data
=
{
existing_metrics
:
[
{
name
:
'
name
'
,
value
:
'
value
'
,
},
],
};
mutations
[
types
.
RECEIVE_METRICS_SUCCESS
](
mockState
,
data
);
expect
(
mockState
.
existingMetrics
[
0
].
name
).
toEqual
(
data
.
existing_metrics
[
0
].
name
);
expect
(
mockState
.
existingMetrics
[
0
].
value
).
toEqual
(
data
.
existing_metrics
[
0
].
value
);
expect
(
mockState
.
numberOfChanges
).
toEqual
(
0
);
expect
(
mockState
.
isLoading
).
toEqual
(
false
);
});
it
(
'
should set metrics with one changes
'
,
()
=>
{
const
data
=
{
existing_metrics
:
[
{
name
:
'
name
'
,
value
:
'
value
'
,
previous_value
:
'
prev
'
,
},
],
};
mutations
[
types
.
RECEIVE_METRICS_SUCCESS
](
mockState
,
data
);
expect
(
mockState
.
existingMetrics
[
0
].
name
).
toEqual
(
data
.
existing_metrics
[
0
].
name
);
expect
(
mockState
.
existingMetrics
[
0
].
value
).
toEqual
(
data
.
existing_metrics
[
0
].
value
);
expect
(
mockState
.
existingMetrics
[
0
].
previous_value
).
toEqual
(
data
.
existing_metrics
[
0
].
previous_value
,
);
expect
(
mockState
.
numberOfChanges
).
toEqual
(
1
);
expect
(
mockState
.
isLoading
).
toEqual
(
false
);
});
});
describe
(
'
RECEIVE_METRICS_ERROR
'
,
()
=>
{
it
(
'
should set endpoint
'
,
()
=>
{
mutations
[
types
.
RECEIVE_METRICS_ERROR
](
mockState
);
expect
(
mockState
.
hasError
).
toEqual
(
true
);
expect
(
mockState
.
isLoading
).
toEqual
(
false
);
});
});
});
locale/gitlab.pot
View file @
2ca109da
...
...
@@ -260,6 +260,9 @@ msgstr ""
msgid "%{user_name} profile page"
msgstr ""
msgid "(No changes)"
msgstr ""
msgid "(external source)"
msgstr ""
...
...
@@ -8777,6 +8780,9 @@ msgstr ""
msgid "Remove this label? This will affect all projects within the group. Are you sure?"
msgstr ""
msgid "Removed"
msgstr ""
msgid "Removed group can not be restored!"
msgstr ""
...
...
@@ -8837,6 +8843,18 @@ msgstr ""
msgid "Reports|Failure"
msgstr ""
msgid "Reports|Metrics reports are loading"
msgstr ""
msgid "Reports|Metrics reports changed on %{numberOfChanges} %{pointsString}"
msgstr ""
msgid "Reports|Metrics reports did not change"
msgstr ""
msgid "Reports|Metrics reports failed loading results"
msgstr ""
msgid "Reports|Severity"
msgstr ""
...
...
@@ -13231,6 +13249,11 @@ msgstr ""
msgid "personal access token"
msgstr ""
msgid "point"
msgid_plural "points"
msgstr[0] ""
msgstr[1] ""
msgid "private"
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