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
b7ea36e2
Commit
b7ea36e2
authored
Mar 17, 2022
by
Jannik Lehmann
Committed by
Phil Hughes
Mar 17, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Code quality mr widget extension
parent
64d66902
Changes
8
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
410 additions
and
2 deletions
+410
-2
app/assets/javascripts/reports/codequality_report/constants.js
...ssets/javascripts/reports/codequality_report/constants.js
+14
-0
app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.js
...vue_merge_request_widget/extensions/code_quality/index.js
+123
-0
app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
...avascripts/vue_merge_request_widget/mr_widget_options.vue
+12
-1
app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
...cripts/vue_merge_request_widget/stores/mr_widget_store.js
+6
-0
ee/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
...avascripts/vue_merge_request_widget/mr_widget_options.vue
+1
-1
locale/gitlab.pot
locale/gitlab.pot
+22
-0
spec/frontend/vue_mr_widget/extentions/code_quality/index_spec.js
...ntend/vue_mr_widget/extentions/code_quality/index_spec.js
+145
-0
spec/frontend/vue_mr_widget/extentions/code_quality/mock_data.js
...ontend/vue_mr_widget/extentions/code_quality/mock_data.js
+87
-0
No files found.
app/assets/javascripts/reports/codequality_report/constants.js
View file @
b7ea36e2
...
@@ -15,3 +15,17 @@ export const SEVERITY_ICONS = {
...
@@ -15,3 +15,17 @@ export const SEVERITY_ICONS = {
blocker
:
'
severity-critical
'
,
blocker
:
'
severity-critical
'
,
unknown
:
'
severity-unknown
'
,
unknown
:
'
severity-unknown
'
,
};
};
// This is the icons mapping for the code Quality Merge-Request Widget Extension
// once the refactor_mr_widgets_extensions flag is activated the above SEVERITY_ICONS
// need be removed and this variable needs to be rename to SEVERITY_ICONS
// Rollout Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/341759
export
const
SEVERITY_ICONS_EXTENSION
=
{
info
:
'
severityInfo
'
,
minor
:
'
severityLow
'
,
major
:
'
severityMedium
'
,
critical
:
'
severityHigh
'
,
blocker
:
'
severityCritical
'
,
unknown
:
'
severityUnknown
'
,
};
app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.js
0 → 100644
View file @
b7ea36e2
import
{
n__
,
s__
,
sprintf
}
from
'
~/locale
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
EXTENSION_ICONS
}
from
'
~/vue_merge_request_widget/constants
'
;
import
{
SEVERITY_ICONS_EXTENSION
}
from
'
~/reports/codequality_report/constants
'
;
import
{
parseCodeclimateMetrics
}
from
'
~/reports/codequality_report/store/utils/codequality_parser
'
;
import
{
capitalizeFirstCharacter
}
from
'
~/lib/utils/text_utility
'
;
export
default
{
name
:
'
WidgetCodeQuality
'
,
props
:
[
'
codeQuality
'
,
'
blobPath
'
],
i18n
:
{
label
:
s__
(
'
ciReport|Code Quality
'
),
loading
:
s__
(
'
ciReport|Code Quality test metrics results are being parsed
'
),
error
:
s__
(
'
ciReport|Code Quality failed loading results
'
),
},
expandEvent
:
'
i_testing_code_quality_widget_total
'
,
computed
:
{
summary
()
{
const
{
newErrors
,
resolvedErrors
,
errorSummary
}
=
this
.
collapsedData
;
if
(
errorSummary
.
errored
>=
1
&&
errorSummary
.
resolved
>=
1
)
{
const
improvements
=
sprintf
(
n__
(
'
%{strongOpen}%{errors}%{strongClose} point
'
,
'
%{strongOpen}%{errors}%{strongClose} points
'
,
resolvedErrors
.
length
,
),
{
errors
:
resolvedErrors
.
length
,
strongOpen
:
'
<strong>
'
,
strongClose
:
'
</strong>
'
,
},
false
,
);
const
degradations
=
sprintf
(
n__
(
'
%{strongOpen}%{errors}%{strongClose} point
'
,
'
%{strongOpen}%{errors}%{strongClose} points
'
,
newErrors
.
length
,
),
{
errors
:
newErrors
.
length
,
strongOpen
:
'
<strong>
'
,
strongClose
:
'
</strong>
'
},
false
,
);
return
sprintf
(
s__
(
`ciReport|Code Quality improved on
${
improvements
}
and degraded on
${
degradations
}
.`
),
);
}
else
if
(
errorSummary
.
resolved
>=
1
)
{
const
improvements
=
n__
(
'
%d point
'
,
'
%d points
'
,
resolvedErrors
.
length
);
return
sprintf
(
s__
(
`ciReport|Code Quality improved on
${
improvements
}
.`
));
}
else
if
(
errorSummary
.
errored
>=
1
)
{
const
degradations
=
n__
(
'
%d point
'
,
'
%d points
'
,
newErrors
.
length
);
return
sprintf
(
s__
(
`ciReport|Code Quality degraded on
${
degradations
}
.`
));
}
return
s__
(
`ciReport|No changes to Code Quality.`
);
},
statusIcon
()
{
if
(
this
.
collapsedData
.
errorSummary
?.
errored
>=
1
)
{
return
EXTENSION_ICONS
.
warning
;
}
return
EXTENSION_ICONS
.
success
;
},
},
methods
:
{
fetchCollapsedData
()
{
return
Promise
.
all
([
this
.
fetchReport
(
this
.
codeQuality
)]).
then
((
values
)
=>
{
return
{
resolvedErrors
:
parseCodeclimateMetrics
(
values
[
0
].
resolved_errors
,
this
.
blobPath
.
head_path
,
),
newErrors
:
parseCodeclimateMetrics
(
values
[
0
].
new_errors
,
this
.
blobPath
.
head_path
),
existingErrors
:
parseCodeclimateMetrics
(
values
[
0
].
existing_errors
,
this
.
blobPath
.
head_path
,
),
errorSummary
:
values
[
0
].
summary
,
};
});
},
fetchFullData
()
{
const
fullData
=
[];
this
.
collapsedData
.
newErrors
.
map
((
e
)
=>
{
return
fullData
.
push
({
text
:
`
${
capitalizeFirstCharacter
(
e
.
severity
)}
-
${
e
.
description
}
`
,
subtext
:
sprintf
(
s__
(
`ciReport|in %{open_link}
${
e
.
file_path
}
:
${
e
.
line
}
%{close_link}`
),
{
open_link
:
`<a class="gl-text-decoration-underline" href="
${
e
.
urlPath
}
">`
,
close_link
:
'
</a>
'
,
},
false
,
),
icon
:
{
name
:
SEVERITY_ICONS_EXTENSION
[
e
.
severity
],
},
});
});
this
.
collapsedData
.
resolvedErrors
.
map
((
e
)
=>
{
return
fullData
.
push
({
text
:
`
${
capitalizeFirstCharacter
(
e
.
severity
)}
-
${
e
.
description
}
`
,
subtext
:
sprintf
(
s__
(
`ciReport|in %{open_link}
${
e
.
file_path
}
:
${
e
.
line
}
%{close_link}`
),
{
open_link
:
`<a class="gl-text-decoration-underline" href="
${
e
.
urlPath
}
">`
,
close_link
:
'
</a>
'
,
},
false
,
),
icon
:
{
name
:
SEVERITY_ICONS_EXTENSION
[
e
.
severity
],
},
});
});
return
Promise
.
resolve
(
fullData
);
},
fetchReport
(
endpoint
)
{
return
axios
.
get
(
endpoint
).
then
((
res
)
=>
res
.
data
);
},
},
};
app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
View file @
b7ea36e2
...
@@ -46,6 +46,7 @@ import mergeRequestQueryVariablesMixin from './mixins/merge_request_query_variab
...
@@ -46,6 +46,7 @@ import mergeRequestQueryVariablesMixin from './mixins/merge_request_query_variab
import
getStateQuery
from
'
./queries/get_state.query.graphql
'
;
import
getStateQuery
from
'
./queries/get_state.query.graphql
'
;
import
terraformExtension
from
'
./extensions/terraform
'
;
import
terraformExtension
from
'
./extensions/terraform
'
;
import
accessibilityExtension
from
'
./extensions/accessibility
'
;
import
accessibilityExtension
from
'
./extensions/accessibility
'
;
import
codeQualityExtension
from
'
./extensions/code_quality
'
;
export
default
{
export
default
{
// False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/25
// False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/25
...
@@ -241,6 +242,11 @@ export default {
...
@@ -241,6 +242,11 @@ export default {
this
.
registerTerraformPlans
();
this
.
registerTerraformPlans
();
}
}
},
},
shouldRenderCodeQuality
(
newVal
)
{
if
(
newVal
)
{
this
.
registerCodeQualityExtension
();
}
},
shouldShowAccessibilityReport
(
newVal
)
{
shouldShowAccessibilityReport
(
newVal
)
{
if
(
newVal
)
{
if
(
newVal
)
{
this
.
registerAccessibilityExtension
();
this
.
registerAccessibilityExtension
();
...
@@ -491,6 +497,11 @@ export default {
...
@@ -491,6 +497,11 @@ export default {
registerExtension
(
accessibilityExtension
);
registerExtension
(
accessibilityExtension
);
}
}
},
},
registerCodeQualityExtension
()
{
if
(
this
.
shouldRenderCodeQuality
&&
this
.
shouldShowExtension
)
{
registerExtension
(
codeQualityExtension
);
}
},
},
},
};
};
</
script
>
</
script
>
...
@@ -546,7 +557,7 @@ export default {
...
@@ -546,7 +557,7 @@ export default {
</div>
</div>
<extensions-container
:mr=
"mr"
/>
<extensions-container
:mr=
"mr"
/>
<grouped-codequality-reports-app
<grouped-codequality-reports-app
v-if=
"shouldRenderCodeQuality"
v-if=
"shouldRenderCodeQuality
&& !shouldShowExtension
"
:head-blob-path=
"mr.headBlobPath"
:head-blob-path=
"mr.headBlobPath"
:base-blob-path=
"mr.baseBlobPath"
:base-blob-path=
"mr.baseBlobPath"
:codequality-reports-path=
"mr.codequalityReportsPath"
:codequality-reports-path=
"mr.codequalityReportsPath"
...
...
app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
View file @
b7ea36e2
...
@@ -32,9 +32,15 @@ export default class MergeRequestStore {
...
@@ -32,9 +32,15 @@ export default class MergeRequestStore {
this
.
setPaths
(
data
);
this
.
setPaths
(
data
);
this
.
setData
(
data
);
this
.
setData
(
data
);
this
.
initCodeQualityReport
(
data
);
this
.
setGitpodData
(
data
);
this
.
setGitpodData
(
data
);
}
}
initCodeQualityReport
(
data
)
{
this
.
blobPath
=
data
.
blob_path
;
this
.
codeQuality
=
data
.
codequality_reports_path
;
}
setData
(
data
,
isRebased
)
{
setData
(
data
,
isRebased
)
{
this
.
initApprovals
();
this
.
initApprovals
();
...
...
ee/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
View file @
b7ea36e2
...
@@ -352,7 +352,7 @@ export default {
...
@@ -352,7 +352,7 @@ export default {
<extensions-container
:mr=
"mr"
/>
<extensions-container
:mr=
"mr"
/>
<blocking-merge-requests-report
:mr=
"mr"
/>
<blocking-merge-requests-report
:mr=
"mr"
/>
<grouped-codequality-reports-app
<grouped-codequality-reports-app
v-if=
"shouldRenderCodeQuality"
v-if=
"shouldRenderCodeQuality
&& !shouldShowExtension
"
:head-blob-path=
"mr.headBlobPath"
:head-blob-path=
"mr.headBlobPath"
:base-blob-path=
"mr.baseBlobPath"
:base-blob-path=
"mr.baseBlobPath"
:codequality-reports-path=
"mr.codequalityReportsPath"
:codequality-reports-path=
"mr.codequalityReportsPath"
...
...
locale/gitlab.pot
View file @
b7ea36e2
...
@@ -350,6 +350,11 @@ msgid_plural "%d personal projects will be removed and cannot be restored."
...
@@ -350,6 +350,11 @@ msgid_plural "%d personal projects will be removed and cannot be restored."
msgstr[0] ""
msgstr[0] ""
msgstr[1] ""
msgstr[1] ""
msgid "%d point"
msgid_plural "%d points"
msgstr[0] ""
msgstr[1] ""
msgid "%d previously merged commit"
msgid "%d previously merged commit"
msgid_plural "%d previously merged commits"
msgid_plural "%d previously merged commits"
msgstr[0] ""
msgstr[0] ""
...
@@ -981,6 +986,11 @@ msgstr ""
...
@@ -981,6 +986,11 @@ msgstr ""
msgid "%{start} to %{end}"
msgid "%{start} to %{end}"
msgstr ""
msgstr ""
msgid "%{strongOpen}%{errors}%{strongClose} point"
msgid_plural "%{strongOpen}%{errors}%{strongClose} points"
msgstr[0] ""
msgstr[1] ""
msgid "%{strongOpen}Warning:%{strongClose} SAML group links can cause GitLab to automatically remove members from groups."
msgid "%{strongOpen}Warning:%{strongClose} SAML group links can cause GitLab to automatically remove members from groups."
msgstr ""
msgstr ""
...
@@ -43479,6 +43489,15 @@ msgstr ""
...
@@ -43479,6 +43489,15 @@ msgstr ""
msgid "ciReport|Cluster Image Scanning"
msgid "ciReport|Cluster Image Scanning"
msgstr ""
msgstr ""
msgid "ciReport|Code Quality"
msgstr ""
msgid "ciReport|Code Quality failed loading results"
msgstr ""
msgid "ciReport|Code Quality test metrics results are being parsed"
msgstr ""
msgid "ciReport|Code quality degraded"
msgid "ciReport|Code quality degraded"
msgstr ""
msgstr ""
...
@@ -43586,6 +43605,9 @@ msgstr ""
...
@@ -43586,6 +43605,9 @@ msgstr ""
msgid "ciReport|New"
msgid "ciReport|New"
msgstr ""
msgstr ""
msgid "ciReport|No changes to Code Quality."
msgstr ""
msgid "ciReport|No changes to code quality"
msgid "ciReport|No changes to code quality"
msgstr ""
msgstr ""
...
...
spec/frontend/vue_mr_widget/extentions/code_quality/index_spec.js
0 → 100644
View file @
b7ea36e2
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
{
mountExtended
}
from
'
helpers/vue_test_utils_helper
'
;
import
{
trimText
}
from
'
helpers/text_helper
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
extensionsContainer
from
'
~/vue_merge_request_widget/components/extensions/container
'
;
import
{
registerExtension
}
from
'
~/vue_merge_request_widget/components/extensions
'
;
import
codeQualityExtension
from
'
~/vue_merge_request_widget/extensions/code_quality
'
;
import
httpStatusCodes
from
'
~/lib/utils/http_status
'
;
import
{
codeQualityResponseNewErrors
,
codeQualityResponseResolvedErrors
,
codeQualityResponseResolvedAndNewErrors
,
codeQualityResponseNoErrors
,
}
from
'
./mock_data
'
;
describe
(
'
Code Quality extension
'
,
()
=>
{
let
wrapper
;
let
mock
;
registerExtension
(
codeQualityExtension
);
const
endpoint
=
'
/root/repo/-/merge_requests/4/accessibility_reports.json
'
;
const
mockApi
=
(
statusCode
,
data
)
=>
{
mock
.
onGet
(
endpoint
).
reply
(
statusCode
,
data
);
};
const
findToggleCollapsedButton
=
()
=>
wrapper
.
findByTestId
(
'
toggle-button
'
);
const
findAllExtensionListItems
=
()
=>
wrapper
.
findAllByTestId
(
'
extension-list-item
'
);
const
createComponent
=
()
=>
{
wrapper
=
mountExtended
(
extensionsContainer
,
{
propsData
:
{
mr
:
{
codeQuality
:
endpoint
,
blobPath
:
{
head_path
:
'
example/path
'
,
base_path
:
'
example/path
'
,
},
},
},
});
};
beforeEach
(()
=>
{
mock
=
new
MockAdapter
(
axios
);
});
afterEach
(()
=>
{
wrapper
.
destroy
();
mock
.
restore
();
});
describe
(
'
summary
'
,
()
=>
{
it
(
'
displays loading text
'
,
()
=>
{
mockApi
(
httpStatusCodes
.
OK
,
codeQualityResponseNewErrors
);
createComponent
();
expect
(
wrapper
.
text
()).
toBe
(
'
Code Quality test metrics results are being parsed
'
);
});
it
(
'
displays failed loading text
'
,
async
()
=>
{
mockApi
(
httpStatusCodes
.
INTERNAL_SERVER_ERROR
);
createComponent
();
await
waitForPromises
();
expect
(
wrapper
.
text
()).
toBe
(
'
Code Quality failed loading results
'
);
});
it
(
'
displays quality degradation
'
,
async
()
=>
{
mockApi
(
httpStatusCodes
.
OK
,
codeQualityResponseNewErrors
);
createComponent
();
await
waitForPromises
();
expect
(
wrapper
.
text
()).
toBe
(
'
Code Quality degraded on 2 points.
'
);
});
it
(
'
displays quality improvement
'
,
async
()
=>
{
mockApi
(
httpStatusCodes
.
OK
,
codeQualityResponseResolvedErrors
);
createComponent
();
await
waitForPromises
();
expect
(
wrapper
.
text
()).
toBe
(
'
Code Quality improved on 2 points.
'
);
});
it
(
'
displays quality improvement and degradation
'
,
async
()
=>
{
mockApi
(
httpStatusCodes
.
OK
,
codeQualityResponseResolvedAndNewErrors
);
createComponent
();
await
waitForPromises
();
expect
(
wrapper
.
text
()).
toBe
(
'
Code Quality improved on 1 point and degraded on 1 point.
'
);
});
it
(
'
displays no detected errors
'
,
async
()
=>
{
mockApi
(
httpStatusCodes
.
OK
,
codeQualityResponseNoErrors
);
createComponent
();
await
waitForPromises
();
expect
(
wrapper
.
text
()).
toBe
(
'
No changes to Code Quality.
'
);
});
});
describe
(
'
expanded data
'
,
()
=>
{
beforeEach
(
async
()
=>
{
mockApi
(
httpStatusCodes
.
OK
,
codeQualityResponseResolvedAndNewErrors
);
createComponent
();
await
waitForPromises
();
findToggleCollapsedButton
().
trigger
(
'
click
'
);
await
waitForPromises
();
});
it
(
'
displays all report list items in viewport
'
,
async
()
=>
{
expect
(
findAllExtensionListItems
()).
toHaveLength
(
2
);
});
it
(
'
displays report list item formatted
'
,
()
=>
{
const
text
=
{
newError
:
trimText
(
findAllExtensionListItems
().
at
(
0
).
text
().
replace
(
/
\s
+/g
,
'
'
).
trim
()),
resolvedError
:
findAllExtensionListItems
().
at
(
1
).
text
().
replace
(
/
\s
+/g
,
'
'
).
trim
(),
};
expect
(
text
.
newError
).
toContain
(
"
Minor - Parsing error: 'return' outside of function in index.js:12
"
,
);
expect
(
text
.
resolvedError
).
toContain
(
"
Minor - Parsing error: 'return' outside of function in index.js:12
"
,
);
});
});
});
spec/frontend/vue_mr_widget/extentions/code_quality/mock_data.js
0 → 100644
View file @
b7ea36e2
export
const
codeQualityResponseNewErrors
=
{
status
:
'
failed
'
,
new_errors
:
[
{
description
:
"
Parsing error: 'return' outside of function
"
,
severity
:
'
minor
'
,
file_path
:
'
index.js
'
,
line
:
12
,
},
{
description
:
'
TODO found
'
,
severity
:
'
minor
'
,
file_path
:
'
.gitlab-ci.yml
'
,
line
:
73
,
},
],
resolved_errors
:
[],
existing_errors
:
[],
summary
:
{
total
:
2
,
resolved
:
0
,
errored
:
2
,
},
};
export
const
codeQualityResponseResolvedErrors
=
{
status
:
'
failed
'
,
new_errors
:
[],
resolved_errors
:
[
{
description
:
"
Parsing error: 'return' outside of function
"
,
severity
:
'
minor
'
,
file_path
:
'
index.js
'
,
line
:
12
,
},
{
description
:
'
TODO found
'
,
severity
:
'
minor
'
,
file_path
:
'
.gitlab-ci.yml
'
,
line
:
73
,
},
],
existing_errors
:
[],
summary
:
{
total
:
2
,
resolved
:
2
,
errored
:
0
,
},
};
export
const
codeQualityResponseResolvedAndNewErrors
=
{
status
:
'
failed
'
,
new_errors
:
[
{
description
:
"
Parsing error: 'return' outside of function
"
,
severity
:
'
minor
'
,
file_path
:
'
index.js
'
,
line
:
12
,
},
],
resolved_errors
:
[
{
description
:
"
Parsing error: 'return' outside of function
"
,
severity
:
'
minor
'
,
file_path
:
'
index.js
'
,
line
:
12
,
},
],
existing_errors
:
[],
summary
:
{
total
:
2
,
resolved
:
1
,
errored
:
1
,
},
};
export
const
codeQualityResponseNoErrors
=
{
status
:
'
failed
'
,
new_errors
:
[],
resolved_errors
:
[],
existing_errors
:
[],
summary
:
{
total
:
0
,
resolved
:
0
,
errored
:
0
,
},
};
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