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
876d90a2
Commit
876d90a2
authored
Nov 03, 2017
by
Filipa Lacerda
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Transform code quality component into reusable report collapsible section
parent
b1606d65
Changes
14
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
943 additions
and
365 deletions
+943
-365
app/assets/stylesheets/pages/merge_requests.scss
app/assets/stylesheets/pages/merge_requests.scss
+17
-4
ee/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_code_quality_issues.vue
...quest_widget/components/mr_widget_code_quality_issues.vue
+0
-42
ee/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_report_collapsible_section.vue
...idget/components/mr_widget_report_collapsible_section.vue
+152
-0
ee/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_report_issues.vue
...rge_request_widget/components/mr_widget_report_issues.vue
+73
-0
ee/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
...javascripts/vue_merge_request_widget/mr_widget_options.js
+138
-5
ee/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js
...ts/vue_merge_request_widget/services/mr_widget_service.js
+2
-2
ee/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
...cripts/vue_merge_request_widget/stores/mr_widget_store.js
+75
-4
spec/javascripts/vue_mr_widget/components/mr_widget_code_quality_issues_spec.js
...r_widget/components/mr_widget_code_quality_issues_spec.js
+0
-99
spec/javascripts/vue_mr_widget/components/mr_widget_code_quality_spec.js
...s/vue_mr_widget/components/mr_widget_code_quality_spec.js
+0
-205
spec/javascripts/vue_mr_widget/components/mr_widget_report_collapsible_section_spec.js
...t/components/mr_widget_report_collapsible_section_spec.js
+100
-0
spec/javascripts/vue_mr_widget/components/mr_widget_report_issues_spec.js
.../vue_mr_widget/components/mr_widget_report_issues_spec.js
+104
-0
spec/javascripts/vue_mr_widget/ee_mr_widget_options_spec.js
spec/javascripts/vue_mr_widget/ee_mr_widget_options_spec.js
+176
-0
spec/javascripts/vue_mr_widget/mock_data.js
spec/javascripts/vue_mr_widget/mock_data.js
+84
-1
spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js
.../javascripts/vue_mr_widget/stores/mr_widget_store_spec.js
+22
-3
No files found.
app/assets/stylesheets/pages/merge_requests.scss
View file @
876d90a2
...
...
@@ -777,7 +777,10 @@
}
.mr-widget-code-quality
{
padding-top
:
$gl-padding-top
;
ci-status-icon-warning
svg
{
fill
:
$theme-gray-600
;
}
.code-quality-container
{
border-top
:
1px
solid
$gray-darker
;
...
...
@@ -788,15 +791,25 @@
.mr-widget-code-quality-list
{
list-style
:
none
;
padding
:
4px
36
px
;
padding
:
0px
12
px
;
margin
:
0
;
line-height
:
$code_line_height
;
li
.success
{
.mr-widget-code-quality-icon
{
margin-right
:
12px
;
fill
:
currentColor
;
svg
{
width
:
10px
;
height
:
10px
;
}
}
.success
{
color
:
$green-500
;
}
li
.failed
{
.failed
{
color
:
$red-500
;
}
}
...
...
ee/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_code_quality_issues.vue
deleted
100644 → 0
View file @
b1606d65
<
script
>
export
default
{
name
:
'
MRWidgetCodeQualityIssues
'
,
props
:
{
issues
:
{
type
:
Array
,
required
:
true
,
},
type
:
{
type
:
String
,
required
:
true
,
},
},
};
</
script
>
<
template
>
<ul
class=
"mr-widget-code-quality-list"
>
<li
class=
"commit-sha"
:class=
"
{
failed: type === 'failed',
success: type === 'success'
}
"v-for="issue in issues">
<i
class=
"fa"
:class=
"
{
'fa-minus': type === 'failed',
'fa-plus': type === 'success'
}"
aria-hidden="true">
</i>
<span>
<span
v-if=
"type === 'success'"
>
Fixed:
</span>
{{
issue
.
check_name
}}
{{
issue
.
location
.
path
}}
{{
issue
.
location
.
positions
}}
{{
issue
.
location
.
lines
}}
</span>
</li>
</ul>
</
template
>
ee/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_
code_quality
.vue
→
ee/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_
report_collapsible_section
.vue
View file @
876d90a2
...
...
@@ -2,20 +2,44 @@
import
statusIcon
from
'
~/vue_merge_request_widget/components/mr_widget_status_icon
'
;
import
loadingIcon
from
'
~/vue_shared/components/loading_icon.vue
'
;
import
'
~/lib/utils/text_utility
'
;
import
issuesBlock
from
'
./mr_widget_
code_quality
_issues.vue
'
;
import
issuesBlock
from
'
./mr_widget_
report
_issues.vue
'
;
export
default
{
name
:
'
MRWidgetCodeQuality
'
,
props
:
{
mr
:
{
type
:
Object
,
// security | codequality
type
:
{
type
:
String
,
required
:
true
,
},
service
:
{
type
:
Object
,
// loading | success | error
status
:
{
type
:
String
,
required
:
true
,
},
loadingText
:
{
type
:
String
,
required
:
true
,
},
errorText
:
{
type
:
String
,
required
:
true
,
},
successText
:
{
type
:
String
,
required
:
true
,
},
unresolvedIssues
:
{
type
:
Array
,
required
:
false
,
default
:
()
=>
[],
},
resolvedIssues
:
{
type
:
Array
,
required
:
false
,
default
:
()
=>
[],
},
},
components
:
{
...
...
@@ -28,100 +52,37 @@ export default {
return
{
collapseText
:
'
Expand
'
,
isCollapsed
:
true
,
isLoading
:
false
,
loadingFailed
:
false
,
};
},
computed
:
{
status
()
{
if
(
this
.
loadingFailed
||
this
.
mr
.
codeclimateMetrics
.
newIssues
.
length
)
{
return
'
failed
'
;
}
return
'
success
'
;
isLoading
()
{
return
this
.
status
===
'
loading
'
;
},
hasNoneIssues
()
{
const
{
newIssues
,
resolvedIssues
}
=
this
.
mr
.
codeclimateMetrics
;
return
!
newIssues
.
length
&&
!
resolvedIssues
.
length
;
loadingFailed
()
{
return
this
.
status
===
'
error
'
;
},
hasIssues
()
{
const
{
newIssues
,
resolvedIssues
}
=
this
.
mr
.
codeclimateMetrics
;
return
newIssues
.
length
||
resolvedIssues
.
length
;
isSuccess
()
{
return
this
.
status
===
'
success
'
;
},
codeText
()
{
const
{
newIssues
,
resolvedIssues
}
=
this
.
mr
.
codeclimateMetrics
;
let
newIssuesText
;
let
resolvedIssuesText
;
let
text
=
[];
if
(
this
.
hasNoneIssues
)
{
text
.
push
(
'
No changes to code quality
'
);
}
else
if
(
this
.
hasIssues
)
{
if
(
newIssues
.
length
)
{
newIssuesText
=
` degraded on
${
newIssues
.
length
}
${
this
.
pointsText
(
newIssues
)}
`
;
}
if
(
resolvedIssues
.
length
)
{
resolvedIssuesText
=
` improved on
${
resolvedIssues
.
length
}
${
this
.
pointsText
(
resolvedIssues
)}
`
;
}
const
connector
=
(
newIssues
.
length
>
0
&&
resolvedIssues
.
length
>
0
)
?
'
and
'
:
null
;
text
=
[
'
Code quality
'
];
if
(
resolvedIssuesText
)
{
text
.
push
(
resolvedIssuesText
);
}
if
(
connector
)
{
text
.
push
(
connector
);
}
if
(
newIssuesText
)
{
text
.
push
(
newIssuesText
);
}
statusIconName
()
{
if
(
this
.
loadingFailed
||
this
.
unresolvedIssues
.
length
)
{
return
'
warning
'
;
}
return
text
.
join
(
''
);
return
'
success
'
;
},
hasIssues
()
{
return
this
.
unresolvedIssues
.
length
||
this
.
resolvedIssues
.
length
;
},
},
methods
:
{
pointsText
(
issues
)
{
return
gl
.
text
.
pluralize
(
'
point
'
,
issues
.
length
);
},
toggleCollapsed
()
{
this
.
isCollapsed
=
!
this
.
isCollapsed
;
const
text
=
this
.
isCollapsed
?
'
Expand
'
:
'
Collapse
'
;
this
.
collapseText
=
text
;
},
handleError
()
{
this
.
isLoading
=
false
;
this
.
loadingFailed
=
true
;
},
},
created
()
{
const
{
head_path
,
base_path
}
=
this
.
mr
.
codeclimate
;
this
.
isLoading
=
true
;
Promise
.
all
([
this
.
service
.
fetchCodeclimate
(
head_path
)
.
then
(
resp
=>
resp
.
json
()),
this
.
service
.
fetchCodeclimate
(
base_path
)
.
then
(
resp
=>
resp
.
json
()),
])
.
then
((
values
)
=>
{
this
.
mr
.
compareCodeclimateMetrics
(
values
[
0
],
values
[
1
]);
this
.
isLoading
=
false
;
})
.
catch
(()
=>
this
.
handleError
());
},
};
</
script
>
...
...
@@ -132,23 +93,21 @@ export default {
v-if=
"isLoading"
class=
"media"
>
<div
class=
"mr-widget-icon"
>
<i
class=
"fa fa-spinner fa-spin"
aria-hidden=
"true"
>
</i>
<loading-icon
/>
</div>
<div
class=
"media-body"
>
Loading codeclimate report
{{
loadingText
}}
</div>
</div>
<div
v-else-if=
"
!isLoading && !loadingFailed
"
v-else-if=
"
isSuccess
"
class=
"media"
>
<status-icon
:status=
"status"
/>
<status-icon
:status=
"statusIconName"
/>
<div
class=
"media-body space-children"
>
<span
class=
"js-code-text"
>
{{
code
Text
}}
{{
success
Text
}}
</span>
<button
...
...
@@ -159,32 +118,34 @@ export default {
{{
collapseText
}}
</button>
</div>
</div>
<div
class=
"code-quality-container"
v-if=
"hasIssues"
v-show=
"!isCollapsed"
>
<issues-block
class=
"js-mr-code-resolved-issues"
v-if=
"mr.codeclimateMetrics.resolvedIssues.length"
type=
"success"
:issues=
"mr.codeclimateMetrics.resolvedIssues"
/>
</div>
<issues-block
class=
"js-mr-code-new-issues"
v-if=
"mr.codeclimateMetrics.newIssues.length"
type=
"failed"
:issues=
"mr.codeclimateMetrics.newIssues"
/>
</div>
<div
class=
"code-quality-container"
v-if=
"hasIssues"
v-show=
"!isCollapsed"
>
<issues-block
class=
"js-mr-code-resolved-issues"
v-if=
"resolvedIssues.length"
:type=
"type"
status=
"success"
:issues=
"resolvedIssues"
/>
<issues-block
class=
"js-mr-code-new-issues"
v-if=
"unresolvedIssues.length"
:type=
"type"
status=
"failed"
:issues=
"unresolvedIssues"
/>
</div>
<div
v-else-if=
"loadingFailed"
class=
"media"
>
<status-icon
status=
"failed"
/>
<div
class=
"media-body"
>
Failed to load codeclimate report
{{
errorText
}}
</div>
</div>
</section>
...
...
ee/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_report_issues.vue
0 → 100644
View file @
876d90a2
<
script
>
import
{
spriteIcon
}
from
'
~/lib/utils/common_utils
'
;
export
default
{
name
:
'
mrWidgetReportIssues
'
,
props
:
{
issues
:
{
type
:
Array
,
required
:
true
,
},
// security || codequality
type
:
{
type
:
String
,
required
:
true
,
},
// failed || success
status
:
{
type
:
String
,
required
:
true
,
},
},
computed
:
{
icon
()
{
return
this
.
isStatusFailed
?
spriteIcon
(
'
cut
'
)
:
spriteIcon
(
'
plus
'
);
},
isStatusFailed
()
{
return
this
.
status
===
'
failed
'
;
},
isStatusSuccess
()
{
return
this
.
status
===
'
success
'
;
},
isTypeQuality
()
{
return
this
.
type
===
'
codequality
'
;
},
isTypeSecurity
()
{
return
this
.
type
===
'
security
'
;
},
},
};
</
script
>
<
template
>
<ul
class=
"mr-widget-code-quality-list"
>
<li
:class=
"
{
failed: isStatusFailed,
success: isStatusSuccess
}
"v-for="issue in issues">
<span
class=
"mr-widget-code-quality-icon"
v-html=
"icon"
>
</span>
<template
v-if=
"isStatusSuccess && isTypeQuality"
>
Fixed:
</
template
>
<
template
v-if=
"isTypeSecurity && issue.priority"
>
{{
issue
.
priority
}}
:
</
template
>
{{issue.name}}
<
template
v-if=
"issue.path"
>
in
<a
:href=
"issue.urlPath"
target=
"_blank"
rel=
"noopener noreferrer nofollow"
>
{{
issue
.
path
}}
<template
v-if=
"issue.line"
>
:
{{
issue
.
line
}}
</
template
>
</a>
</template>
</li>
</ul>
</template>
ee/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
View file @
876d90a2
...
...
@@ -2,7 +2,7 @@ import CEWidgetOptions from '~/vue_merge_request_widget/mr_widget_options';
import
WidgetApprovals
from
'
./components/approvals/mr_widget_approvals
'
;
import
GeoSecondaryNode
from
'
./components/states/mr_widget_secondary_geo_node
'
;
import
RebaseState
from
'
./components/states/mr_widget_rebase.vue
'
;
import
WidgetCodeQuality
from
'
./components/mr_widget_code_quality
.vue
'
;
import
collapsibleSection
from
'
./components/mr_widget_report_collapsible_section
.vue
'
;
export
default
{
extends
:
CEWidgetOptions
,
...
...
@@ -10,7 +10,15 @@ export default {
'
mr-widget-approvals
'
:
WidgetApprovals
,
'
mr-widget-geo-secondary-node
'
:
GeoSecondaryNode
,
'
mr-widget-rebase
'
:
RebaseState
,
'
mr-widget-code-quality
'
:
WidgetCodeQuality
,
collapsibleSection
,
},
data
()
{
return
{
isLoadingCodequality
:
false
,
isLoadingSecurity
:
false
,
loadingCodequalityFailed
:
false
,
loadingSecurityFailed
:
false
,
};
},
computed
:
{
shouldRenderApprovals
()
{
...
...
@@ -20,6 +28,116 @@ export default {
const
{
codeclimate
}
=
this
.
mr
;
return
codeclimate
&&
codeclimate
.
head_path
&&
codeclimate
.
base_path
;
},
shouldRenderSecurityReport
()
{
return
this
.
mr
.
security
&&
this
.
mr
.
security
.
sast
;
},
codequalityText
()
{
const
{
newIssues
,
resolvedIssues
}
=
this
.
mr
.
codeclimateMetrics
;
let
newIssuesText
;
let
resolvedIssuesText
;
let
text
=
[];
if
(
!
newIssues
.
length
&&
!
resolvedIssues
.
length
)
{
text
.
push
(
'
No changes to code quality
'
);
}
else
if
(
newIssues
.
length
||
resolvedIssues
.
length
)
{
if
(
newIssues
.
length
)
{
newIssuesText
=
` degraded on
${
newIssues
.
length
}
${
this
.
pointsText
(
newIssues
)}
`
;
}
if
(
resolvedIssues
.
length
)
{
resolvedIssuesText
=
` improved on
${
resolvedIssues
.
length
}
${
this
.
pointsText
(
resolvedIssues
)}
`
;
}
const
connector
=
(
newIssues
.
length
>
0
&&
resolvedIssues
.
length
>
0
)
?
'
and
'
:
null
;
text
=
[
'
Code quality
'
];
if
(
resolvedIssuesText
)
{
text
.
push
(
resolvedIssuesText
);
}
if
(
connector
)
{
text
.
push
(
connector
);
}
if
(
newIssuesText
)
{
text
.
push
(
newIssuesText
);
}
}
return
text
.
join
(
''
);
},
securityText
()
{
const
{
securityReport
}
=
this
.
mr
;
if
(
securityReport
.
length
)
{
const
vulnerabilitiesText
=
gl
.
text
.
pluralize
(
'
vulnerabilities
'
,
securityReport
.
length
);
return
`
${
securityReport
.
length
}
security
${
vulnerabilitiesText
}
detected`
;
}
return
'
No security vulnerabilities detected
'
;
},
codequalityStatus
()
{
if
(
this
.
isLoadingCodequality
)
{
return
'
loading
'
;
}
else
if
(
this
.
loadingCodequalityFailed
)
{
return
'
error
'
;
}
return
'
success
'
;
},
securityStatus
()
{
if
(
this
.
isLoadingSecurity
)
{
return
'
loading
'
;
}
else
if
(
this
.
loadingSecurityFailed
)
{
return
'
error
'
;
}
return
'
success
'
;
},
},
methods
:
{
fetchCodeQuality
()
{
const
{
head_path
,
base_path
}
=
this
.
mr
.
codeclimate
;
this
.
isLoadingCodequality
=
true
;
Promise
.
all
([
this
.
service
.
fetchReport
(
head_path
),
this
.
service
.
fetchReport
(
base_path
),
])
.
then
((
values
)
=>
{
this
.
mr
.
compareCodeclimateMetrics
(
values
[
0
],
values
[
1
]);
this
.
isLoadingCodequality
=
false
;
})
.
catch
(()
=>
{
this
.
isLoadingCodequality
=
false
;
this
.
loadingCodequalityFailed
=
true
;
});
},
fetchSecurity
()
{
this
.
isLoadingSecurity
=
true
;
this
.
service
.
fetchReport
(
this
.
mr
.
security
.
sast
)
.
then
((
data
)
=>
{
this
.
mr
.
setSecurityReport
(
data
);
this
.
isLoadingSecurity
=
false
;
})
.
catch
(()
=>
{
this
.
isLoadingSecurity
=
false
;
this
.
loadingSecurityFailed
=
true
;
});
},
pointsText
(
issues
)
{
return
gl
.
text
.
pluralize
(
'
point
'
,
issues
.
length
);
},
},
created
()
{
if
(
this
.
shouldRenderCodeQuality
)
{
this
.
fetchCodeQuality
();
}
if
(
this
.
shouldRenderSecurityReport
)
{
this
.
fetchSecurity
();
}
},
template
:
`
<div class="mr-state-widget prepend-top-default">
...
...
@@ -35,10 +153,25 @@ export default {
v-if="mr.approvalsRequired"
:mr="mr"
:service="service" />
<mr-widget-code-quality
<collapsible-section
class="js-codequality-widget"
v-if="shouldRenderCodeQuality"
:mr="mr"
:service="service"
type="codequality"
:status="codequalityStatus"
loadingText="Loading codeclimate report"
errorText="Failed to load codeclimate report"
:successText="codequalityText"
:unresolvedIssues="mr.codeclimateMetrics.newIssues"
:resolvedIssues="mr.codeclimateMetrics.resolvedIssues"
/>
<collapsible-section
v-if="shouldRenderSecurityReport"
type="security"
:status="securityStatus"
loadingText="Loading security report"
errorText="Failed to load security report"
:successText="securityText"
:unresolvedIssues="mr.securityReport"
/>
<div class="mr-widget-section">
<component
...
...
ee/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js
View file @
876d90a2
...
...
@@ -35,7 +35,7 @@ export default class MRWidgetService extends CEWidgetService {
return
this
.
rebaseResource
.
save
();
}
fetch
Codeclimate
(
endpoint
)
{
// eslint-disable-line
return
Vue
.
http
.
get
(
endpoint
);
fetch
Report
(
endpoint
)
{
// eslint-disable-line
return
Vue
.
http
.
get
(
endpoint
)
.
then
(
res
=>
res
.
json
())
;
}
}
ee/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
View file @
876d90a2
...
...
@@ -4,6 +4,7 @@ export default class MergeRequestStore extends CEMergeRequestStore {
constructor
(
data
)
{
super
(
data
);
this
.
initCodeclimate
(
data
);
this
.
initSecurityReport
(
data
);
}
setData
(
data
)
{
...
...
@@ -56,12 +57,82 @@ export default class MergeRequestStore extends CEMergeRequestStore {
};
}
compareCodeclimateMetrics
(
headIssues
,
baseIssues
)
{
this
.
codeclimateMetrics
.
newIssues
=
this
.
filterByFingerprint
(
headIssues
,
baseIssues
)
;
this
.
codeclimateMetrics
.
resolvedIssues
=
this
.
filterByFingerprint
(
baseIssues
,
headIssues
)
;
initSecurityReport
(
data
)
{
this
.
security
=
data
.
security
;
this
.
securityReport
=
[]
;
}
filterByFingerprint
(
firstArray
,
secondArray
)
{
// eslint-disable-line
setSecurityReport
(
issues
)
{
this
.
securityReport
=
MergeRequestStore
.
parseIssues
(
issues
);
}
// TODO: get changes from codequality MR
compareCodeclimateMetrics
(
headIssues
,
baseIssues
,
headBlobPath
,
baseBlobPath
)
{
const
parsedHeadIssues
=
MergeRequestStore
.
parseIssues
(
headIssues
,
headBlobPath
);
const
parsedBaseIssues
=
MergeRequestStore
.
parseIssues
(
baseIssues
,
baseBlobPath
);
this
.
codeclimateMetrics
.
newIssues
=
MergeRequestStore
.
filterByFingerprint
(
parsedHeadIssues
,
parsedBaseIssues
,
);
this
.
codeclimateMetrics
.
resolvedIssues
=
MergeRequestStore
.
filterByFingerprint
(
parsedBaseIssues
,
parsedHeadIssues
,
);
}
/**
* In order to reuse the same component we need
* to set both codequality and security issues to have the same data structure:
* [
* {
* name: String,
* priority: String,
* fingerprint: String,
* path: String,
* line: Number,
* urlPath: String
* }
* ]
* @param {array} issues
* @return {array}
*/
static
parseIssues
(
issues
,
path
)
{
return
issues
.
map
((
issue
)
=>
{
const
parsedIssue
=
{
name
:
issue
.
check_name
||
issue
.
message
,
...
issue
,
};
// code quality
if
(
issue
.
location
)
{
let
parseCodeQualityUrl
;
if
(
issue
.
location
.
path
)
{
parseCodeQualityUrl
=
`
${
path
}
/
${
issue
.
location
.
path
}
`
;
parsedIssue
.
path
=
issue
.
location
.
path
;
}
if
(
issue
.
location
.
lines
&&
issue
.
location
.
lines
.
begin
)
{
parsedIssue
.
line
=
issue
.
location
.
lines
.
begin
;
parsedIssue
.
urlPath
=
parseCodeQualityUrl
+=
`#L
${
issue
.
location
.
lines
.
begin
}
`
;
}
}
else
{
// security
let
parsedSecurityUrl
;
if
(
issue
.
file
)
{
parsedSecurityUrl
=
`
${
path
}
/
${
issue
.
file
}
`
;
parsedIssue
.
path
=
issue
.
file
;
}
if
(
issue
.
line
)
{
parsedIssue
.
urlPath
=
parsedSecurityUrl
+=
`#L
${
issue
.
line
}
`
;
}
}
return
parsedIssue
;
});
}
static
filterByFingerprint
(
firstArray
,
secondArray
)
{
return
firstArray
.
filter
(
item
=>
!
secondArray
.
find
(
el
=>
el
.
fingerprint
===
item
.
fingerprint
));
}
}
spec/javascripts/vue_mr_widget/components/mr_widget_code_quality_issues_spec.js
deleted
100644 → 0
View file @
b1606d65
import
Vue
from
'
vue
'
;
import
mrWidgetCodeQualityIssues
from
'
ee/vue_merge_request_widget/components/mr_widget_code_quality_issues.vue
'
;
describe
(
'
Merge Request Code Quality Issues
'
,
()
=>
{
let
vm
;
let
MRWidgetCodeQualityIssues
;
let
mountComponent
;
beforeEach
(()
=>
{
MRWidgetCodeQualityIssues
=
Vue
.
extend
(
mrWidgetCodeQualityIssues
);
mountComponent
=
props
=>
new
MRWidgetCodeQualityIssues
({
propsData
:
props
}).
$mount
();
});
describe
(
'
Renders provided list of issues
'
,
()
=>
{
describe
(
'
with positions and lines
'
,
()
=>
{
beforeEach
(()
=>
{
vm
=
mountComponent
({
type
:
'
success
'
,
issues
:
[{
check_name
:
'
foo
'
,
location
:
{
path
:
'
bar
'
,
positions
:
'
81
'
,
lines
:
'
21
'
,
},
}],
});
});
it
(
'
should render issue
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
li span
'
).
textContent
.
trim
().
replace
(
/
\s
+/g
,
''
),
).
toEqual
(
'
Fixed:foobar8121
'
);
});
});
describe
(
'
without positions and lines
'
,
()
=>
{
beforeEach
(()
=>
{
vm
=
mountComponent
({
type
:
'
success
'
,
issues
:
[{
check_name
:
'
foo
'
,
location
:
{
path
:
'
bar
'
,
},
}],
});
});
it
(
'
should render issue without position and lines
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
li span
'
).
textContent
.
trim
().
replace
(
/
\s
+/g
,
''
),
).
toEqual
(
'
Fixed:foobar
'
);
});
});
describe
(
'
for type failed
'
,
()
=>
{
beforeEach
(()
=>
{
vm
=
mountComponent
({
type
:
'
failed
'
,
issues
:
[{
check_name
:
'
foo
'
,
location
:
{
path
:
'
bar
'
,
positions
:
'
81
'
,
lines
:
'
21
'
,
},
}],
});
});
it
(
'
should render failed minus icon
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
li
'
).
classList
.
contains
(
'
failed
'
)).
toEqual
(
true
);
expect
(
vm
.
$el
.
querySelector
(
'
li i
'
).
classList
.
contains
(
'
fa-minus
'
)).
toEqual
(
true
);
});
});
describe
(
'
for type success
'
,
()
=>
{
beforeEach
(()
=>
{
vm
=
mountComponent
({
type
:
'
success
'
,
issues
:
[{
check_name
:
'
foo
'
,
location
:
{
path
:
'
bar
'
,
positions
:
'
81
'
,
lines
:
'
21
'
,
},
}],
});
});
it
(
'
should render success plus icon
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
li
'
).
classList
.
contains
(
'
success
'
)).
toEqual
(
true
);
expect
(
vm
.
$el
.
querySelector
(
'
li i
'
).
classList
.
contains
(
'
fa-plus
'
)).
toEqual
(
true
);
});
});
});
});
spec/javascripts/vue_mr_widget/components/mr_widget_code_quality_spec.js
deleted
100644 → 0
View file @
b1606d65
import
Vue
from
'
vue
'
;
import
mrWidgetCodeQuality
from
'
ee/vue_merge_request_widget/components/mr_widget_code_quality.vue
'
;
import
Store
from
'
ee/vue_merge_request_widget/stores/mr_widget_store
'
;
import
Service
from
'
ee/vue_merge_request_widget/services/mr_widget_service
'
;
import
mockData
,
{
baseIssues
,
headIssues
}
from
'
../mock_data
'
;
describe
(
'
Merge Request Code Quality
'
,
()
=>
{
let
vm
;
let
MRWidgetCodeQuality
;
let
store
;
let
mountComponent
;
let
service
;
beforeEach
(()
=>
{
MRWidgetCodeQuality
=
Vue
.
extend
(
mrWidgetCodeQuality
);
store
=
new
Store
(
mockData
);
service
=
new
Service
(
''
);
mountComponent
=
props
=>
new
MRWidgetCodeQuality
({
propsData
:
props
}).
$mount
();
});
afterEach
(()
=>
{
vm
.
$destroy
();
});
describe
(
'
when it is loading
'
,
()
=>
{
beforeEach
(()
=>
{
vm
=
mountComponent
({
mr
:
store
,
service
,
});
});
it
(
'
should render loading indicator
'
,
()
=>
{
expect
(
vm
.
$el
.
textContent
.
trim
()).
toEqual
(
'
Loading codeclimate report
'
);
});
});
describe
(
'
with successful request
'
,
()
=>
{
const
interceptor
=
(
request
,
next
)
=>
{
if
(
request
.
url
===
'
head.json
'
)
{
next
(
request
.
respondWith
(
JSON
.
stringify
(
headIssues
),
{
status
:
200
,
}));
}
if
(
request
.
url
===
'
base.json
'
)
{
next
(
request
.
respondWith
(
JSON
.
stringify
(
baseIssues
),
{
status
:
200
,
}));
}
};
beforeEach
(()
=>
{
Vue
.
http
.
interceptors
.
push
(
interceptor
);
vm
=
mountComponent
({
mr
:
store
,
service
,
});
});
afterEach
(()
=>
{
Vue
.
http
.
interceptors
=
_
.
without
(
Vue
.
http
.
interceptors
,
interceptor
);
});
it
(
'
should render provided data
'
,
(
done
)
=>
{
setTimeout
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.js-code-text
'
).
textContent
.
trim
(),
).
toEqual
(
'
Code quality improved on 1 point and degraded on 1 point
'
);
done
();
},
0
);
});
describe
(
'
text connector
'
,
()
=>
{
it
(
'
should only render information about fixed issues
'
,
(
done
)
=>
{
setTimeout
(()
=>
{
vm
.
mr
.
codeclimateMetrics
.
newIssues
=
[];
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.js-code-text
'
).
textContent
.
trim
(),
).
toEqual
(
'
Code quality improved on 1 point
'
);
done
();
});
},
0
);
});
it
(
'
should only render information about added issues
'
,
(
done
)
=>
{
setTimeout
(()
=>
{
vm
.
mr
.
codeclimateMetrics
.
resolvedIssues
=
[];
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.js-code-text
'
).
textContent
.
trim
(),
).
toEqual
(
'
Code quality degraded on 1 point
'
);
done
();
});
},
0
);
});
});
describe
(
'
toggleCollapsed
'
,
()
=>
{
it
(
'
toggles issues
'
,
(
done
)
=>
{
setTimeout
(()
=>
{
vm
.
$el
.
querySelector
(
'
button
'
).
click
();
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.code-quality-container
'
).
geAttribute
(
'
style
'
),
).
toEqual
(
null
);
expect
(
vm
.
$el
.
querySelector
(
'
button
'
).
textContent
.
trim
(),
).
toEqual
(
'
Collapse
'
);
vm
.
$el
.
querySelector
(
'
button
'
).
click
();
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.code-quality-container
'
).
geAttribute
(
'
style
'
),
).
toEqual
(
'
display: none;
'
);
expect
(
vm
.
$el
.
querySelector
(
'
button
'
).
textContent
.
trim
(),
).
toEqual
(
'
Expand
'
);
});
});
done
();
},
0
);
});
});
});
describe
(
'
with empty successful request
'
,
()
=>
{
const
emptyInterceptor
=
(
request
,
next
)
=>
{
if
(
request
.
url
===
'
head.json
'
)
{
next
(
request
.
respondWith
(
JSON
.
stringify
([]),
{
status
:
200
,
}));
}
if
(
request
.
url
===
'
base.json
'
)
{
next
(
request
.
respondWith
(
JSON
.
stringify
([]),
{
status
:
200
,
}));
}
};
beforeEach
(()
=>
{
Vue
.
http
.
interceptors
.
push
(
emptyInterceptor
);
vm
=
mountComponent
({
mr
:
store
,
service
,
});
});
afterEach
(()
=>
{
Vue
.
http
.
interceptors
=
_
.
without
(
Vue
.
http
.
interceptors
,
emptyInterceptor
);
});
it
(
'
should render provided data
'
,
(
done
)
=>
{
setTimeout
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.js-code-text
'
).
textContent
.
trim
(),
).
toEqual
(
'
No changes to code quality
'
);
done
();
},
0
);
});
});
describe
(
'
with failed request
'
,
()
=>
{
const
errorInterceptor
=
(
request
,
next
)
=>
{
if
(
request
.
url
===
'
head.json
'
)
{
next
(
request
.
respondWith
(
JSON
.
stringify
([]),
{
status
:
500
,
}));
}
if
(
request
.
url
===
'
base.json
'
)
{
next
(
request
.
respondWith
(
JSON
.
stringify
([]),
{
status
:
500
,
}));
}
};
beforeEach
(()
=>
{
Vue
.
http
.
interceptors
.
push
(
errorInterceptor
);
vm
=
mountComponent
({
mr
:
store
,
service
,
});
});
afterEach
(()
=>
{
Vue
.
http
.
interceptors
=
_
.
without
(
Vue
.
http
.
interceptors
,
errorInterceptor
);
});
it
(
'
should render error indicator
'
,
(
done
)
=>
{
setTimeout
(()
=>
{
expect
(
vm
.
$el
.
textContent
.
trim
()).
toEqual
(
'
Failed to load codeclimate report
'
);
done
();
},
0
);
});
});
});
spec/javascripts/vue_mr_widget/components/mr_widget_report_collapsible_section_spec.js
0 → 100644
View file @
876d90a2
import
Vue
from
'
vue
'
;
import
mrWidgetCodeQuality
from
'
ee/vue_merge_request_widget/components/mr_widget_report_collapsible_section.vue
'
;
import
mountComponent
from
'
../../helpers/vue_mount_component_helper
'
;
import
{
codequalityParsedIssues
}
from
'
../mock_data
'
;
describe
(
'
Merge Request collapsible section
'
,
()
=>
{
let
vm
;
let
MRWidgetCodeQuality
;
beforeEach
(()
=>
{
MRWidgetCodeQuality
=
Vue
.
extend
(
mrWidgetCodeQuality
);
});
afterEach
(()
=>
{
vm
.
$destroy
();
});
describe
(
'
when it is loading
'
,
()
=>
{
it
(
'
should render loading indicator
'
,
()
=>
{
vm
=
mountComponent
(
MRWidgetCodeQuality
,
{
type
:
'
codequality
'
,
status
:
'
loading
'
,
loadingText
:
'
Loading codeclimate report
'
,
errorText
:
'
foo
'
,
successText
:
'
Code quality improved on 1 point and degraded on 1 point
'
,
});
expect
(
vm
.
$el
.
textContent
.
trim
()).
toEqual
(
'
Loading codeclimate report
'
);
});
});
describe
(
'
with successful request
'
,
()
=>
{
it
(
'
should render provided data
'
,
()
=>
{
vm
=
mountComponent
(
MRWidgetCodeQuality
,
{
type
:
'
codequality
'
,
status
:
'
success
'
,
loadingText
:
'
Loading codeclimate report
'
,
errorText
:
'
foo
'
,
successText
:
'
Code quality improved on 1 point and degraded on 1 point
'
,
resolvedIssues
:
codequalityParsedIssues
,
});
expect
(
vm
.
$el
.
querySelector
(
'
.js-code-text
'
).
textContent
.
trim
(),
).
toEqual
(
'
Code quality improved on 1 point and degraded on 1 point
'
);
expect
(
vm
.
$el
.
querySelectorAll
(
'
.js-mr-code-resolved-issues li
'
).
length
,
).
toEqual
(
codequalityParsedIssues
.
length
);
});
describe
(
'
toggleCollapsed
'
,
()
=>
{
it
(
'
toggles issues
'
,
()
=>
{
vm
=
mountComponent
(
MRWidgetCodeQuality
,
{
type
:
'
codequality
'
,
status
:
'
success
'
,
loadingText
:
'
Loading codeclimate report
'
,
errorText
:
'
foo
'
,
successText
:
'
Code quality improved on 1 point and degraded on 1 point
'
,
resolvedIssues
:
codequalityParsedIssues
,
});
vm
.
$el
.
querySelector
(
'
button
'
).
click
();
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.code-quality-container
'
).
geAttribute
(
'
style
'
),
).
toEqual
(
null
);
expect
(
vm
.
$el
.
querySelector
(
'
button
'
).
textContent
.
trim
(),
).
toEqual
(
'
Collapse
'
);
vm
.
$el
.
querySelector
(
'
button
'
).
click
();
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.code-quality-container
'
).
geAttribute
(
'
style
'
),
).
toEqual
(
'
display: none;
'
);
expect
(
vm
.
$el
.
querySelector
(
'
button
'
).
textContent
.
trim
(),
).
toEqual
(
'
Expand
'
);
});
});
});
});
});
describe
(
'
with failed request
'
,
()
=>
{
it
(
'
should render error indicator
'
,
()
=>
{
vm
=
mountComponent
(
MRWidgetCodeQuality
,
{
type
:
'
codequality
'
,
status
:
'
error
'
,
loadingText
:
'
Loading codeclimate report
'
,
errorText
:
'
Failed to load codeclimate report
'
,
successText
:
'
Code quality improved on 1 point and degraded on 1 point
'
,
});
expect
(
vm
.
$el
.
textContent
.
trim
()).
toEqual
(
'
Failed to load codeclimate report
'
);
});
});
});
spec/javascripts/vue_mr_widget/components/mr_widget_report_issues_spec.js
0 → 100644
View file @
876d90a2
import
Vue
from
'
vue
'
;
import
mrWidgetCodeQualityIssues
from
'
ee/vue_merge_request_widget/components/mr_widget_report_issues.vue
'
;
import
mountComponent
from
'
../../helpers/vue_mount_component_helper
'
;
import
{
securityParsedIssues
,
codequalityParsedIssues
}
from
'
../mock_data
'
;
describe
(
'
merge request report issues
'
,
()
=>
{
let
vm
;
let
MRWidgetCodeQualityIssues
;
beforeEach
(()
=>
{
MRWidgetCodeQualityIssues
=
Vue
.
extend
(
mrWidgetCodeQualityIssues
);
});
afterEach
(()
=>
{
vm
.
$destroy
();
});
describe
(
'
for codequality issues
'
,
()
=>
{
describe
(
'
resolved issues
'
,
()
=>
{
beforeEach
(()
=>
{
vm
=
mountComponent
(
MRWidgetCodeQualityIssues
,
{
issues
:
codequalityParsedIssues
,
type
:
'
codequality
'
,
status
:
'
success
'
,
});
});
it
(
'
should render a list of resolved issues
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelectorAll
(
'
.mr-widget-code-quality-list li
'
).
length
).
toEqual
(
codequalityParsedIssues
.
length
);
});
it
(
'
should render "Fixed" keyword
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.mr-widget-code-quality-list li
'
).
textContent
).
toContain
(
'
Fixed
'
);
expect
(
vm
.
$el
.
querySelector
(
'
.mr-widget-code-quality-list li
'
).
textContent
.
trim
().
replace
(
/
\s
+/g
,
''
),
).
toEqual
(
'
Fixed:InsecureDependencyinGemfile.lock:12
'
);
});
});
describe
(
'
unresolved issues
'
,
()
=>
{
beforeEach
(()
=>
{
vm
=
mountComponent
(
MRWidgetCodeQualityIssues
,
{
issues
:
codequalityParsedIssues
,
type
:
'
codequality
'
,
status
:
'
failed
'
,
});
});
it
(
'
should render a list of unresolved issues
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelectorAll
(
'
.mr-widget-code-quality-list li
'
).
length
).
toEqual
(
codequalityParsedIssues
.
length
);
});
it
(
'
should not render "Fixed" keyword
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.mr-widget-code-quality-list li
'
).
textContent
).
not
.
toContain
(
'
Fixed
'
);
});
});
});
describe
(
'
for security issues
'
,
()
=>
{
beforeEach
(()
=>
{
vm
=
mountComponent
(
MRWidgetCodeQualityIssues
,
{
issues
:
securityParsedIssues
,
type
:
'
security
'
,
status
:
'
failed
'
,
});
});
it
(
'
should render a list of unresolved issues
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelectorAll
(
'
.mr-widget-code-quality-list li
'
).
length
).
toEqual
(
securityParsedIssues
.
length
);
});
it
(
'
should render priority
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.mr-widget-code-quality-list li
'
).
textContent
).
toContain
(
securityParsedIssues
[
0
].
priority
);
});
});
describe
(
'
with location
'
,
()
=>
{
it
(
'
should render location
'
,
()
=>
{
vm
=
mountComponent
(
MRWidgetCodeQualityIssues
,
{
issues
:
securityParsedIssues
,
type
:
'
security
'
,
status
:
'
failed
'
,
});
expect
(
vm
.
$el
.
querySelector
(
'
.mr-widget-code-quality-list li
'
).
textContent
).
toContain
(
'
in
'
);
expect
(
vm
.
$el
.
querySelector
(
'
.mr-widget-code-quality-list li a
'
).
getAttribute
(
'
href
'
)).
toEqual
(
securityParsedIssues
[
0
].
urlPath
);
});
});
describe
(
'
without location
'
,
()
=>
{
it
(
'
should not render location
'
,
()
=>
{
vm
=
mountComponent
(
MRWidgetCodeQualityIssues
,
{
issues
:
[{
name
:
'
foo
'
,
}],
type
:
'
security
'
,
status
:
'
failed
'
,
});
expect
(
vm
.
$el
.
querySelector
(
'
.mr-widget-code-quality-list li
'
).
textContent
).
not
.
toContain
(
'
in
'
);
expect
(
vm
.
$el
.
querySelector
(
'
.mr-widget-code-quality-list li a
'
)).
toEqual
(
null
);
});
});
});
spec/javascripts/vue_mr_widget/ee_mr_widget_options_spec.js
0 → 100644
View file @
876d90a2
import
Vue
from
'
vue
'
;
import
mrWidgetOptions
from
'
ee/vue_merge_request_widget/mr_widget_options
'
;
import
MRWidgetService
from
'
ee/vue_merge_request_widget/services/mr_widget_service
'
;
import
MRWidgetStore
from
'
ee/vue_merge_request_widget/stores/mr_widget_store
'
;
import
mockData
,
{
baseIssues
,
headIssues
}
from
'
./mock_data
'
;
describe
(
'
ee merge request widget options
'
,
()
=>
{
let
vm
;
let
Component
;
let
mountComponent
;
beforeEach
(()
=>
{
delete
mrWidgetOptions
.
extends
.
el
;
// Prevent component mounting
gl
.
mrWidgetData
=
{
...
mockData
,
codeclimate
:
{
head_path
:
'
head.json
'
,
base_path
:
'
base.json
'
,
},
};
Component
=
Vue
.
extend
(
mrWidgetOptions
);
Component
.
mr
=
new
MRWidgetStore
(
gl
.
mrWidgetData
);
Component
.
service
=
new
MRWidgetService
({});
mountComponent
=
()
=>
new
Component
().
$mount
();
});
afterEach
(()
=>
{
vm
.
$destroy
();
});
describe
(
'
security
'
,
()
=>
{
beforeEach
(()
=>
{
});
});
describe
(
'
code quality
'
,
()
=>
{
describe
(
'
when it is loading
'
,
()
=>
{
it
(
'
should render loading indicator
'
,
()
=>
{
vm
=
mountComponent
();
expect
(
vm
.
$el
.
querySelector
(
'
.js-codequality-widget
'
).
textContent
.
trim
(),
).
toContain
(
'
Loading codeclimate report
'
);
});
});
describe
(
'
with successful request
'
,
()
=>
{
const
interceptor
=
(
request
,
next
)
=>
{
if
(
request
.
url
===
'
head.json
'
)
{
next
(
request
.
respondWith
(
JSON
.
stringify
(
headIssues
),
{
status
:
200
,
}));
}
if
(
request
.
url
===
'
base.json
'
)
{
next
(
request
.
respondWith
(
JSON
.
stringify
(
baseIssues
),
{
status
:
200
,
}));
}
};
beforeEach
(()
=>
{
Vue
.
http
.
interceptors
.
push
(
interceptor
);
vm
=
mountComponent
();
});
afterEach
(()
=>
{
Vue
.
http
.
interceptors
=
_
.
without
(
Vue
.
http
.
interceptors
,
interceptor
);
});
it
(
'
should render provided data
'
,
(
done
)
=>
{
setTimeout
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.js-code-text
'
).
textContent
.
trim
(),
).
toEqual
(
'
Code quality improved on 1 point and degraded on 1 point
'
);
done
();
},
0
);
});
describe
(
'
text connector
'
,
()
=>
{
it
(
'
should only render information about fixed issues
'
,
(
done
)
=>
{
setTimeout
(()
=>
{
vm
.
mr
.
codeclimateMetrics
.
newIssues
=
[];
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.js-code-text
'
).
textContent
.
trim
(),
).
toEqual
(
'
Code quality improved on 1 point
'
);
done
();
});
},
0
);
});
it
(
'
should only render information about added issues
'
,
(
done
)
=>
{
setTimeout
(()
=>
{
vm
.
mr
.
codeclimateMetrics
.
resolvedIssues
=
[];
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.js-code-text
'
).
textContent
.
trim
(),
).
toEqual
(
'
Code quality degraded on 1 point
'
);
done
();
});
},
0
);
});
});
});
describe
(
'
with empty successful request
'
,
()
=>
{
const
emptyInterceptor
=
(
request
,
next
)
=>
{
if
(
request
.
url
===
'
head.json
'
)
{
next
(
request
.
respondWith
(
JSON
.
stringify
([]),
{
status
:
200
,
}));
}
if
(
request
.
url
===
'
base.json
'
)
{
next
(
request
.
respondWith
(
JSON
.
stringify
([]),
{
status
:
200
,
}));
}
};
beforeEach
(()
=>
{
Vue
.
http
.
interceptors
.
push
(
emptyInterceptor
);
vm
=
mountComponent
();
});
afterEach
(()
=>
{
Vue
.
http
.
interceptors
=
_
.
without
(
Vue
.
http
.
interceptors
,
emptyInterceptor
);
});
it
(
'
should render provided data
'
,
(
done
)
=>
{
setTimeout
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.js-code-text
'
).
textContent
.
trim
(),
).
toEqual
(
'
No changes to code quality
'
);
done
();
},
0
);
});
});
describe
(
'
with failed request
'
,
()
=>
{
const
errorInterceptor
=
(
request
,
next
)
=>
{
if
(
request
.
url
===
'
head.json
'
)
{
next
(
request
.
respondWith
(
JSON
.
stringify
([]),
{
status
:
500
,
}));
}
if
(
request
.
url
===
'
base.json
'
)
{
next
(
request
.
respondWith
(
JSON
.
stringify
([]),
{
status
:
500
,
}));
}
};
beforeEach
(()
=>
{
Vue
.
http
.
interceptors
.
push
(
errorInterceptor
);
vm
=
mountComponent
();
});
afterEach
(()
=>
{
Vue
.
http
.
interceptors
=
_
.
without
(
Vue
.
http
.
interceptors
,
errorInterceptor
);
});
it
(
'
should render error indicator
'
,
(
done
)
=>
{
setTimeout
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.js-codequality-widget
'
).
textContent
.
trim
()).
toContain
(
'
Failed to load codeclimate report
'
);
done
();
},
0
);
});
});
});
});
spec/javascripts/vue_mr_widget/mock_data.js
View file @
876d90a2
...
...
@@ -249,6 +249,28 @@ export const headIssues = [
}
];
export
const
parsedHeadIssues
=
[
{
"
check_name
"
:
"
Rubocop/Lint/UselessAssignment
"
,
"
location
"
:
{
"
path
"
:
"
lib/six.rb
"
,
"
positions
"
:
{
"
begin
"
:
{
"
column
"
:
6
,
"
line
"
:
59
},
"
end
"
:
{
"
column
"
:
7
,
"
line
"
:
59
}
}
},
"
fingerprint
"
:
"
e879dd9bbc0953cad5037cde7ff0f627
"
,
name
:
'
Rubocop/Lint/UselessAssignment
'
,
path
:
'
lib/six.rb
'
,
}
];
export
const
baseIssues
=
[
{
"
categories
"
:
[
"
Security
"
],
...
...
@@ -274,4 +296,65 @@ export const baseIssues = [
},
"
fingerprint
"
:
"
ca2354534dee94ae60ba2f54e3857c50e5
"
,
}
]
];
export
const
parsedBaseIssues
=
[
{
"
categories
"
:
[
"
Security
"
],
"
check_name
"
:
"
Insecure Dependency
"
,
"
location
"
:
{
"
path
"
:
"
Gemfile.lock
"
,
"
lines
"
:
{
"
begin
"
:
21
,
"
end
"
:
21
}
},
"
fingerprint
"
:
"
ca2354534dee94ae60ba2f54e3857c50e5
"
,
name
:
"
Insecure Dependency
"
,
path
:
"
Gemfile.lock
"
,
line
:
21
,
urlPath
:
'
undefined/Gemfile.lock#L21
'
,
},
];
export
const
codequalityParsedIssues
=
[
{
name
:
'
Insecure Dependency
'
,
fingerprint
:
'
ca2e59451e98ae60ba2f54e3857c50e5
'
,
path
:
'
Gemfile.lock
'
,
line
:
12
,
urlPath
:
'
foo/Gemfile.lock
'
,
},
];
export
const
securityParsedIssues
=
[
{
name
:
'
Arbitrary file existence disclosure in Action Pack
'
,
path
:
'
Gemfile.lock
'
,
line
:
12
,
priority
:
'
High
'
,
urlPath
:
'
foo/Gemfile.lock
'
,
},
];
export
const
securityIssues
=
[
{
tool
:
'
bundler_audit
'
,
message
:
'
Arbitrary file existence disclosure in Action Pack
'
,
url
:
'
https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk
'
,
cve
:
'
CVE-2014-7829
'
,
file
:
'
Gemfile.lock
'
,
solution
:
'
upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8
'
,
priority
:
'
High
'
,
line
:
12
,
},
{
tool
:
'
bundler_audit
'
,
message
:
'
Possible Information Leak Vulnerability in Action View
'
,
url
:
'
https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00
'
,
cve
:
'
CVE-2016-0752
'
,
file
:
'
Gemfile.lock
'
,
solution
:
'
upgrade to >= 5.0.0.beta1.1, >= 4.2.5.1, ~> 4.2.5, >= 4.1.14.1, ~> 4.1.14, ~> 3.2.22.1
'
,
priority
:
'
Medium
'
,
}
];
spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js
View file @
876d90a2
import
MergeRequestStore
from
'
ee/vue_merge_request_widget/stores/mr_widget_store
'
;
import
mockData
,
{
headIssues
,
baseIssues
}
from
'
../mock_data
'
;
import
mockData
,
{
headIssues
,
baseIssues
,
securityIssues
,
parsedBaseIssues
,
parsedHeadIssues
,
}
from
'
../mock_data
'
;
describe
(
'
MergeRequestStore
'
,
()
=>
{
let
store
;
...
...
@@ -60,11 +66,24 @@ describe('MergeRequestStore', () => {
});
it
(
'
should return the new issues
'
,
()
=>
{
expect
(
store
.
codeclimateMetrics
.
newIssues
[
0
]).
toEqual
(
h
eadIssues
[
0
]);
expect
(
store
.
codeclimateMetrics
.
newIssues
[
0
]).
toEqual
(
parsedH
eadIssues
[
0
]);
});
it
(
'
should return the resolved issues
'
,
()
=>
{
expect
(
store
.
codeclimateMetrics
.
resolvedIssues
[
0
]).
toEqual
(
baseIssues
[
1
]);
expect
(
store
.
codeclimateMetrics
.
resolvedIssues
[
0
]).
toEqual
(
parsedBaseIssues
[
0
]);
});
});
describe
(
'
parseIssues
'
,
()
=>
{
it
(
'
should parse the received issues
'
,
()
=>
{
const
codequality
=
MergeRequestStore
.
parseIssues
(
baseIssues
,
'
path
'
)[
0
];
expect
(
codequality
.
name
).
toEqual
(
baseIssues
[
0
].
check_name
);
expect
(
codequality
.
path
).
toEqual
(
baseIssues
[
0
].
location
.
path
);
expect
(
codequality
.
line
).
toEqual
(
baseIssues
[
0
].
location
.
lines
.
begin
);
const
security
=
MergeRequestStore
.
parseIssues
(
securityIssues
,
'
path
'
)[
0
];
expect
(
security
.
name
).
toEqual
(
securityIssues
[
0
].
message
);
expect
(
security
.
path
).
toEqual
(
securityIssues
[
0
].
file
);
});
});
});
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