Commit 32d16fcb authored by Filipa Lacerda's avatar Filipa Lacerda

Update frontend code after review

parent c3fd1491
import { n__ } from '~/locale';
import CEWidgetOptions from '~/vue_merge_request_widget/mr_widget_options'; import CEWidgetOptions from '~/vue_merge_request_widget/mr_widget_options';
import WidgetApprovals from './components/approvals/mr_widget_approvals'; import WidgetApprovals from './components/approvals/mr_widget_approvals';
import GeoSecondaryNode from './components/states/mr_widget_secondary_geo_node'; import GeoSecondaryNode from './components/states/mr_widget_secondary_geo_node';
...@@ -33,47 +34,49 @@ export default { ...@@ -33,47 +34,49 @@ export default {
}, },
codequalityText() { codequalityText() {
const { newIssues, resolvedIssues } = this.mr.codeclimateMetrics; const { newIssues, resolvedIssues } = this.mr.codeclimateMetrics;
let newIssuesText; const text = [];
let resolvedIssuesText;
let text = [];
if (!newIssues.length && !resolvedIssues.length) { if (!newIssues.length && !resolvedIssues.length) {
text.push('No changes to code quality'); text.push('No changes to code quality');
} else if (newIssues.length || resolvedIssues.length) { } else if (newIssues.length || resolvedIssues.length) {
if (newIssues.length) { text.push('Code quality');
newIssuesText = ` degraded on ${newIssues.length} ${this.pointsText(newIssues)}`;
}
if (resolvedIssues.length) { if (resolvedIssues.length) {
resolvedIssuesText = ` improved on ${resolvedIssues.length} ${this.pointsText(resolvedIssues)}`; text.push(n__(
} ' improved on %d point',
' improved on %d points',
const connector = (newIssues.length > 0 && resolvedIssues.length > 0) ? ' and' : null; resolvedIssues.length,
));
text = ['Code quality'];
if (resolvedIssuesText) {
text.push(resolvedIssuesText);
} }
if (connector) { if (newIssues.length > 0 && resolvedIssues.length > 0) {
text.push(connector); text.push(' and');
} }
if (newIssuesText) { if (newIssues.length) {
text.push(newIssuesText); text.push(n__(
' degraded on %d point',
' degraded on %d points',
newIssues.length,
));
} }
} }
return text.join(''); return text.join('');
}, },
securityText() { securityText() {
const { securityReport } = this.mr; if (this.mr.securityReport.length) {
if (securityReport.length) { return n__(
return `${securityReport.length} security ${this.pluralizeVulnerability(securityReport.length)} detected`; '%d security vulnerability detected',
'%d security vulnerabilities detected',
this.mr.securityReport.length,
);
} }
return 'No security vulnerabilities detected'; return 'No security vulnerabilities detected';
}, },
codequalityStatus() { codequalityStatus() {
if (this.isLoadingCodequality) { if (this.isLoadingCodequality) {
return 'loading'; return 'loading';
...@@ -82,6 +85,7 @@ export default { ...@@ -82,6 +85,7 @@ export default {
} }
return 'success'; return 'success';
}, },
securityStatus() { securityStatus() {
if (this.isLoadingSecurity) { if (this.isLoadingSecurity) {
return 'loading'; return 'loading';
...@@ -92,9 +96,6 @@ export default { ...@@ -92,9 +96,6 @@ export default {
}, },
}, },
methods: { methods: {
pluralizeVulnerability(length) {
return length === 1 ? 'vulnerability' : 'vulnerabilities';
},
fetchCodeQuality() { fetchCodeQuality() {
const { head_path, head_blob_path, base_path, base_blob_path } = this.mr.codeclimate; const { head_path, head_blob_path, base_path, base_blob_path } = this.mr.codeclimate;
...@@ -115,11 +116,12 @@ export default { ...@@ -115,11 +116,12 @@ export default {
}, },
fetchSecurity() { fetchSecurity() {
const { path, blob_path } = this.mr.security.sast;
this.isLoadingSecurity = true; this.isLoadingSecurity = true;
this.service.fetchReport(this.mr.security.sast) this.service.fetchReport(path)
.then((data) => { .then((data) => {
this.mr.setSecurityReport(data); this.mr.setSecurityReport(data, blob_path);
this.isLoadingSecurity = false; this.isLoadingSecurity = false;
}) })
.catch(() => { .catch(() => {
...@@ -127,10 +129,6 @@ export default { ...@@ -127,10 +129,6 @@ export default {
this.loadingSecurityFailed = true; this.loadingSecurityFailed = true;
}); });
}, },
pointsText(issues) {
return gl.text.pluralize('point', issues.length);
},
}, },
created() { created() {
if (this.shouldRenderCodeQuality) { if (this.shouldRenderCodeQuality) {
...@@ -160,9 +158,9 @@ export default { ...@@ -160,9 +158,9 @@ export default {
v-if="shouldRenderCodeQuality" v-if="shouldRenderCodeQuality"
type="codequality" type="codequality"
:status="codequalityStatus" :status="codequalityStatus"
loadingText="Loading codeclimate report" loading-text="Loading codeclimate report"
errorText="Failed to load codeclimate report" error-text="Failed to load codeclimate report"
:successText="codequalityText" :success-text="codequalityText"
:unresolvedIssues="mr.codeclimateMetrics.newIssues" :unresolvedIssues="mr.codeclimateMetrics.newIssues"
:resolvedIssues="mr.codeclimateMetrics.resolvedIssues" :resolvedIssues="mr.codeclimateMetrics.resolvedIssues"
/> />
...@@ -170,9 +168,9 @@ export default { ...@@ -170,9 +168,9 @@ export default {
v-if="shouldRenderSecurityReport" v-if="shouldRenderSecurityReport"
type="security" type="security"
:status="securityStatus" :status="securityStatus"
loadingText="Loading security report" loading-text="Loading security report"
errorText="Failed to load security report" error-text="Failed to load security report"
:successText="securityText" :success-text="securityText"
:unresolvedIssues="mr.securityReport" :unresolvedIssues="mr.securityReport"
/> />
<div class="mr-widget-section"> <div class="mr-widget-section">
......
...@@ -62,10 +62,10 @@ export default class MergeRequestStore extends CEMergeRequestStore { ...@@ -62,10 +62,10 @@ export default class MergeRequestStore extends CEMergeRequestStore {
this.securityReport = []; this.securityReport = [];
} }
setSecurityReport(issues) { setSecurityReport(issues, path) {
this.securityReport = MergeRequestStore.parseIssues(issues); this.securityReport = MergeRequestStore.parseIssues(issues, path);
} }
// TODO: get changes from codequality MR
compareCodeclimateMetrics(headIssues, baseIssues, headBlobPath, baseBlobPath) { compareCodeclimateMetrics(headIssues, baseIssues, headBlobPath, baseBlobPath) {
const parsedHeadIssues = MergeRequestStore.parseIssues(headIssues, headBlobPath); const parsedHeadIssues = MergeRequestStore.parseIssues(headIssues, headBlobPath);
const parsedBaseIssues = MergeRequestStore.parseIssues(baseIssues, baseBlobPath); const parsedBaseIssues = MergeRequestStore.parseIssues(baseIssues, baseBlobPath);
...@@ -113,19 +113,20 @@ export default class MergeRequestStore extends CEMergeRequestStore { ...@@ -113,19 +113,20 @@ export default class MergeRequestStore extends CEMergeRequestStore {
if (issue.location.lines && issue.location.lines.begin) { if (issue.location.lines && issue.location.lines.begin) {
parsedIssue.line = issue.location.lines.begin; parsedIssue.line = issue.location.lines.begin;
parsedIssue.urlPath = parseCodeQualityUrl += `#L${issue.location.lines.begin}`; parseCodeQualityUrl += `#L${issue.location.lines.begin}`;
}
} else {
// security
let parsedSecurityUrl;
if (issue.file) {
parsedSecurityUrl = `${path}/${issue.file}`;
parsedIssue.path = issue.file;
} }
parsedIssue.urlPath = parseCodeQualityUrl;
// security
} else if (issue.file) {
let parsedSecurityUrl = `${path}/${issue.file}`;
parsedIssue.path = issue.file;
if (issue.line) { if (issue.line) {
parsedIssue.urlPath = parsedSecurityUrl += `#L${issue.line}`; parsedSecurityUrl += `#L${issue.line}`;
} }
parsedIssue.urlPath = parsedSecurityUrl;
} }
return parsedIssue; return parsedIssue;
......
...@@ -28,7 +28,7 @@ describe('Merge Request collapsible section', () => { ...@@ -28,7 +28,7 @@ describe('Merge Request collapsible section', () => {
}); });
}); });
describe('with successful request', () => { describe('with success status', () => {
it('should render provided data', () => { it('should render provided data', () => {
vm = mountComponent(MRWidgetCodeQuality, { vm = mountComponent(MRWidgetCodeQuality, {
type: 'codequality', type: 'codequality',
...@@ -49,7 +49,7 @@ describe('Merge Request collapsible section', () => { ...@@ -49,7 +49,7 @@ describe('Merge Request collapsible section', () => {
}); });
describe('toggleCollapsed', () => { describe('toggleCollapsed', () => {
it('toggles issues', () => { it('toggles issues', (done) => {
vm = mountComponent(MRWidgetCodeQuality, { vm = mountComponent(MRWidgetCodeQuality, {
type: 'codequality', type: 'codequality',
status: 'success', status: 'success',
...@@ -63,8 +63,8 @@ describe('Merge Request collapsible section', () => { ...@@ -63,8 +63,8 @@ describe('Merge Request collapsible section', () => {
Vue.nextTick(() => { Vue.nextTick(() => {
expect( expect(
vm.$el.querySelector('.code-quality-container').geAttribute('style'), vm.$el.querySelector('.code-quality-container').getAttribute('style'),
).toEqual(null); ).toEqual('');
expect( expect(
vm.$el.querySelector('button').textContent.trim(), vm.$el.querySelector('button').textContent.trim(),
).toEqual('Collapse'); ).toEqual('Collapse');
...@@ -73,11 +73,13 @@ describe('Merge Request collapsible section', () => { ...@@ -73,11 +73,13 @@ describe('Merge Request collapsible section', () => {
Vue.nextTick(() => { Vue.nextTick(() => {
expect( expect(
vm.$el.querySelector('.code-quality-container').geAttribute('style'), vm.$el.querySelector('.code-quality-container').getAttribute('style'),
).toEqual('display: none;'); ).toEqual('display: none;');
expect( expect(
vm.$el.querySelector('button').textContent.trim(), vm.$el.querySelector('button').textContent.trim(),
).toEqual('Expand'); ).toEqual('Expand');
done();
}); });
}); });
}); });
......
...@@ -32,8 +32,8 @@ describe('merge request report issues', () => { ...@@ -32,8 +32,8 @@ describe('merge request report issues', () => {
it('should render "Fixed" keyword', () => { 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).toContain('Fixed');
expect( expect(
vm.$el.querySelector('.mr-widget-code-quality-list li').textContent.trim().replace(/\s+/g, ''), vm.$el.querySelector('.mr-widget-code-quality-list li').textContent.replace(/\s+/g, ' ').trim(),
).toEqual('Fixed:InsecureDependencyinGemfile.lock:12'); ).toEqual('Fixed: Insecure Dependency in Gemfile.lock:12');
}); });
}); });
......
...@@ -221,95 +221,91 @@ export default { ...@@ -221,95 +221,91 @@ export default {
export const headIssues = [ export const headIssues = [
{ {
"check_name": "Rubocop/Lint/UselessAssignment", check_name: 'Rubocop/Lint/UselessAssignment',
"location": { location: {
"path": "lib/six.rb", path: 'lib/six.rb',
"lines": { lines: {
"begin": 6, begin: 6,
"end": 7, end: 7,
} }
}, },
"fingerprint": "e879dd9bbc0953cad5037cde7ff0f627", fingerprint: 'e879dd9bbc0953cad5037cde7ff0f627',
}, },
{ {
"categories": ["Security"], categories: ['Security'],
"check_name": "Insecure Dependency", check_name: 'Insecure Dependency',
"location": { location: {
"path": "Gemfile.lock", path: 'Gemfile.lock',
"lines": { lines: {
"begin": 22, begin: 22,
"end": 22 end: 22
} }
}, },
"fingerprint": "ca2e59451e98ae60ba2f54e3857c50e5", fingerprint: 'ca2e59451e98ae60ba2f54e3857c50e5',
} }
]; ];
export const parsedHeadIssues = [ export const parsedHeadIssues = [
{ {
"check_name": "Rubocop/Lint/UselessAssignment", check_name: 'Rubocop/Lint/UselessAssignment',
"location": { location: {
"path": "lib/six.rb", path: 'lib/six.rb',
"positions": { lines: {
"begin": { begin: 6,
"column": 6, end: 7
"line": 59 },
},
"end": {
"column": 7,
"line": 59
}
}
}, },
"fingerprint": "e879dd9bbc0953cad5037cde7ff0f627", fingerprint: 'e879dd9bbc0953cad5037cde7ff0f627',
name: 'Rubocop/Lint/UselessAssignment', name: 'Rubocop/Lint/UselessAssignment',
path: 'lib/six.rb', path: 'lib/six.rb',
} urlPath: 'headPath/lib/six.rb#L6',
line: 6,
},
]; ];
export const baseIssues = [ export const baseIssues = [
{ {
"categories": ["Security"], categories: ['Security'],
"check_name": "Insecure Dependency", check_name: 'Insecure Dependency',
"location": { location: {
"path": "Gemfile.lock", path: 'Gemfile.lock',
"lines": { lines: {
"begin": 22, begin: 22,
"end": 22 end: 22
} }
}, },
"fingerprint": "ca2e59451e98ae60ba2f54e3857c50e5", fingerprint: 'ca2e59451e98ae60ba2f54e3857c50e5',
}, },
{ {
"categories": ["Security"], categories: ['Security'],
"check_name": "Insecure Dependency", check_name: 'Insecure Dependency',
"location": { location: {
"path": "Gemfile.lock", path: 'Gemfile.lock',
"lines": { lines: {
"begin": 21, begin: 21,
"end": 21 end: 21
} }
}, },
"fingerprint": "ca2354534dee94ae60ba2f54e3857c50e5", fingerprint: 'ca2354534dee94ae60ba2f54e3857c50e5',
} }
]; ];
export const parsedBaseIssues = [ export const parsedBaseIssues = [
{ {
"categories": ["Security"], categories: ['Security'],
"check_name": "Insecure Dependency", check_name: 'Insecure Dependency',
"location": { location: {
"path": "Gemfile.lock", path: 'Gemfile.lock',
"lines": { lines: {
"begin": 21, begin: 21,
"end": 21 end: 21,
} },
}, },
"fingerprint": "ca2354534dee94ae60ba2f54e3857c50e5", fingerprint: "ca2354534dee94ae60ba2f54e3857c50e5",
name: "Insecure Dependency", name: "Insecure Dependency",
path: "Gemfile.lock", path: "Gemfile.lock",
line: 21, line: 21,
urlPath: 'undefined/Gemfile.lock#L21', urlPath: 'basePath/Gemfile.lock#L21',
}, },
]; ];
...@@ -352,5 +348,33 @@ export const securityIssues = [ ...@@ -352,5 +348,33 @@ export const securityIssues = [
file: 'Gemfile.lock', 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', 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', priority: 'Medium',
} },
];
export const parsedSecurityIssuesStore = [
{
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,
name: 'Arbitrary file existence disclosure in Action Pack',
path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock#L12'
},
{
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',
name: 'Possible Information Leak Vulnerability in Action View',
path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock',
},
]; ];
...@@ -5,6 +5,7 @@ import mockData, { ...@@ -5,6 +5,7 @@ import mockData, {
securityIssues, securityIssues,
parsedBaseIssues, parsedBaseIssues,
parsedHeadIssues, parsedHeadIssues,
parsedSecurityIssuesStore,
} from '../mock_data'; } from '../mock_data';
describe('MergeRequestStore', () => { describe('MergeRequestStore', () => {
...@@ -74,6 +75,14 @@ describe('MergeRequestStore', () => { ...@@ -74,6 +75,14 @@ describe('MergeRequestStore', () => {
}); });
}); });
describe('setSecurityReport', () => {
it('should set security issues', () => {
store.setSecurityReport(securityIssues, 'path');
expect(store.securityReport).toEqual(parsedSecurityIssuesStore);
});
});
describe('parseIssues', () => { describe('parseIssues', () => {
it('should parse the received issues', () => { it('should parse the received issues', () => {
const codequality = MergeRequestStore.parseIssues(baseIssues, 'path')[0]; const codequality = MergeRequestStore.parseIssues(baseIssues, 'path')[0];
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment