refactor: DiffViewer to use same common code to generate code coverage status
This commit is contained in:
Родитель
83b50612ad
Коммит
e8e5b5aad4
|
@ -1,9 +1,9 @@
|
|||
import React, { Component } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import * as FetchAPI from '../utils/fetch_data';
|
||||
import { csetWithCcovData } from '../utils/data';
|
||||
import hash from '../utils/hash';
|
||||
import { DiffMeta, CoverageMeta } from './diffviewermeta';
|
||||
import * as FetchAPI from '../utils/fetch_data';
|
||||
|
||||
const parse = require('parse-diff');
|
||||
|
||||
|
@ -16,60 +16,63 @@ export default class DiffViewerContainer extends Component {
|
|||
super(props);
|
||||
this.state = {
|
||||
appError: undefined,
|
||||
coverage: undefined,
|
||||
csetMeta: {
|
||||
coverage: undefined,
|
||||
},
|
||||
parsedDiff: [],
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
async componentDidMount() {
|
||||
const { changeset } = this.props;
|
||||
await this.fetchCsetData(changeset);
|
||||
}
|
||||
|
||||
FetchAPI.getDiff(changeset)
|
||||
.then(response =>
|
||||
response.text())
|
||||
.then(text =>
|
||||
this.setState({ parsedDiff: parse(text) }))
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
this.setState({
|
||||
appError: 'We did not manage to parse the diff correctly.',
|
||||
});
|
||||
async fetchCsetData(changeset) {
|
||||
try {
|
||||
this.setState({ csetMeta: await csetWithCcovData({ node: changeset }) });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
this.setState({
|
||||
appError: 'There was an error fetching the code coverage data.',
|
||||
});
|
||||
}
|
||||
|
||||
FetchAPI.getChangesetCoverage(changeset)
|
||||
.then(response =>
|
||||
response.text())
|
||||
.then(text =>
|
||||
this.setState({ coverage: JSON.parse(text) }))
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
this.setState({
|
||||
appError: 'There was an error fetching the code coverage data.',
|
||||
});
|
||||
try {
|
||||
const text = await (await FetchAPI.getDiff(changeset)).text();
|
||||
this.setState({ parsedDiff: parse(text) });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
this.setState({
|
||||
appError: 'We did not manage to parse the diff correctly.',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { changeset } = this.props;
|
||||
const { appError, coverage, parsedDiff } = this.state;
|
||||
const { appError, csetMeta, parsedDiff } = this.state;
|
||||
return (
|
||||
<DiffViewer
|
||||
{...csetMeta}
|
||||
appError={appError}
|
||||
changeset={changeset}
|
||||
coverage={coverage}
|
||||
parsedDiff={parsedDiff}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const DiffViewer = ({ appError, changeset, coverage, parsedDiff }) => (
|
||||
<div className="page_body codecoverage-diffviewer">
|
||||
<Link className="return-home" to="/">Return to main page</Link>
|
||||
<DiffMeta changeset={changeset} />
|
||||
<CoverageMeta coverage={coverage} parsedDiff={parsedDiff} />
|
||||
const DiffViewer = ({ appError, coverage, node, parsedDiff, summary }) => (
|
||||
<div className="codecoverage-diffviewer">
|
||||
<div className="return-home"><Link to="/">Return to main page</Link></div>
|
||||
{(coverage) &&
|
||||
<CoverageMeta
|
||||
{...coverage.parentMeta(coverage)}
|
||||
{...coverage.diffMeta(node)}
|
||||
coverage={coverage}
|
||||
node={node}
|
||||
summary={summary}
|
||||
/>}
|
||||
<span className="error_message">{appError}</span>
|
||||
<br />
|
||||
{parsedDiff.map(diffBlock =>
|
||||
// We only push down the subset of code coverage data
|
||||
// applicable to a file
|
||||
|
@ -83,6 +86,30 @@ const DiffViewer = ({ appError, changeset, coverage, parsedDiff }) => (
|
|||
</div>
|
||||
);
|
||||
|
||||
const CoverageMeta = ({ ccovBackend, codecov, coverage, gh, hgRev, pushlog, summary }) => (
|
||||
<div className="coverage-meta">
|
||||
<div className="coverage-meta-row">
|
||||
<span className="meta parent-meta-subtitle">Parent meta</span>
|
||||
<span className="meta">
|
||||
{`Current coverage: ${coverage.overall_cur.substring(0, 4)}%`}
|
||||
</span>
|
||||
<span className="meta meta-right">
|
||||
<a href={pushlog} target="_blank">Push log</a>
|
||||
<a href={gh} target="_blank">GitHub</a>
|
||||
<a href={codecov} target="_blank">Codecov</a>
|
||||
</span>
|
||||
</div>
|
||||
<div className="coverage-meta-row">
|
||||
<span className="meta parent-meta-subtitle">Changeset meta</span>
|
||||
<span className="meta">{summary}</span>
|
||||
<span className="meta meta-right">
|
||||
<a href={hgRev} target="_blank">Hg diff</a>
|
||||
<a href={ccovBackend} target="_blank">Coverage backend</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
/* A DiffLine contains all diff changes for a specific file */
|
||||
const DiffFile = ({ coverage, diffBlock }) => {
|
||||
// We try to see if the file modified shows up in the code
|
||||
|
|
|
@ -1,144 +0,0 @@
|
|||
import React from 'react';
|
||||
|
||||
import * as FetchAPI from '../utils/fetch_data';
|
||||
|
||||
export const coverageSummary = (coverage) => {
|
||||
const s = {
|
||||
addedLines: 0,
|
||||
coveredLines: 0,
|
||||
};
|
||||
if (coverage.diffs.length > 0) {
|
||||
coverage.diffs.forEach((diff) => {
|
||||
diff.changes.forEach((change) => {
|
||||
if (change.coverage === 'Y') {
|
||||
s.coveredLines += 1;
|
||||
}
|
||||
if (change.coverage !== '?') {
|
||||
s.addedLines += 1;
|
||||
}
|
||||
});
|
||||
s.percentage = (s.addedLines === 0) ?
|
||||
undefined :
|
||||
100 * (s.coveredLines / s.addedLines);
|
||||
});
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
export const DiffMeta = ({ changeset }) => {
|
||||
const hgRev = `${FetchAPI.hgHost}/mozilla-central/rev/${changeset}`;
|
||||
const shipitUrl = `${FetchAPI.ccovBackend}/coverage/changeset/${changeset}`;
|
||||
|
||||
return (
|
||||
<div className="diff-meta">
|
||||
<span><b>Meta for diff ({changeset})</b></span>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<a href={hgRev} target="_blank">Hg diff</a> -
|
||||
<a href={shipitUrl} target="_blank">Shipit backend</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const determineCoverageMeta = (coverage, parsedDiff) => {
|
||||
if (parsedDiff.length > 0) {
|
||||
// addedLines: The total number of new executable lines
|
||||
const addedLines = (coverage.diffs.length !== 0) ?
|
||||
coverage.diffs.reduce((sum, file) => (
|
||||
('changes' in file) ?
|
||||
sum + file.changes.reduce((acumm, lineCov) => (
|
||||
(lineCov.coverage === 'Y' || lineCov.coverage === 'N') ? acumm + 1 : acumm), 0) :
|
||||
sum), 0) : 0;
|
||||
// coveredLines: The total number of new covered lines
|
||||
const coveredLines = (coverage.diffs.length !== 0) ?
|
||||
coverage.diffs.reduce((sum, file) => (
|
||||
('changes' in file) ?
|
||||
sum + file.changes.reduce((acumm, lineCov) => (
|
||||
(lineCov.coverage === 'Y') ? acumm + 1 : acumm), 0) :
|
||||
sum), 0) : 0;
|
||||
|
||||
let netGain = ((addedLines !== 0) ?
|
||||
(coveredLines / addedLines) * 100 : 0);
|
||||
netGain = netGain.toPrecision(3);
|
||||
|
||||
return {
|
||||
addedLines,
|
||||
coveredLines,
|
||||
netGain,
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const NetCoverageContainer = ({ coverage, parsedDiff }) => {
|
||||
const status = determineCoverageMeta(coverage, parsedDiff);
|
||||
return (status) ? (
|
||||
<NetCoverage {...status} />
|
||||
) : null;
|
||||
};
|
||||
|
||||
const NetCoverage = ({ addedLines, coveredLines, netGain }) => (
|
||||
<div className="net-coverage-meta">
|
||||
<span className="net-lines-coverage-change">
|
||||
New lines coverage change: {netGain}%
|
||||
</span>
|
||||
{`Added lines: ${addedLines} / Covered lines: ${coveredLines}`}
|
||||
</div>
|
||||
);
|
||||
|
||||
const ParentMeta = ({ coverage }) => {
|
||||
const pushlog = `https://hg.mozilla.org/mozilla-central/pushloghtml?changeset=${coverage.build_changeset}`;
|
||||
const codecov = `https://codecov.io/gh/marco-c/gecko-dev/commit/${coverage.git_build_changeset}`;
|
||||
const gh = `https://github.com/mozilla/gecko-dev/commit/${coverage.git_build_changeset}`;
|
||||
|
||||
return (
|
||||
<div className="parent-meta">
|
||||
<div>
|
||||
<b>Meta for parent code coverage build ({coverage.build_changeset})</b>
|
||||
</div>
|
||||
<div>
|
||||
{`Current coverage: ${coverage.overall_cur.substring(0, 4)}%`} -
|
||||
<a href={pushlog} target="_blank">Push log</a> -
|
||||
<a href={gh} target="_blank">GitHub</a> -
|
||||
<a href={codecov} target="_blank">Codecov</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const CoverageMeta = ({ coverage, parsedDiff }) => {
|
||||
let errorMessage;
|
||||
if (!coverage || coverage.error) {
|
||||
if (!coverage) {
|
||||
errorMessage = "We're waiting for coverage data from the backend.";
|
||||
} else if (coverage.error) {
|
||||
errorMessage = coverage.error;
|
||||
}
|
||||
return (
|
||||
<div className="error_message">{errorMessage}</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (coverage.diffs.length === 0) {
|
||||
errorMessage = `There is no code coverage for this diff
|
||||
or no new lines are being added to this diff.`;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ParentMeta coverage={coverage} />
|
||||
{(parsedDiff.length > 0 && coverage) &&
|
||||
<NetCoverageContainer
|
||||
coverage={coverage}
|
||||
parsedDiff={parsedDiff}
|
||||
/>}
|
||||
<div className="error_message">{errorMessage}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -2,13 +2,9 @@ import { Link } from 'react-router-dom';
|
|||
import React, { Component } from 'react';
|
||||
import ReactInterval from 'react-interval';
|
||||
|
||||
// XXX: Create module that fetches ccov data + diff data
|
||||
import { coverageSummary } from './diffviewermeta';
|
||||
import * as FetchAPI from '../utils/fetch_data';
|
||||
import { arrayToMap, mapToArray } from '../utils/data';
|
||||
import SETTINGS from '../settings';
|
||||
|
||||
const PENDING = 'Pending';
|
||||
import { PENDING } from '../settings';
|
||||
import { arrayToMap, csetWithCcovData, mapToArray } from '../utils/data';
|
||||
|
||||
const ChangesetInfo = ({ changeset }) => {
|
||||
const { author, desc, hidden, linkify, node, summary, summaryClassName } = changeset;
|
||||
|
@ -56,68 +52,6 @@ const PollingStatus = ({ pollingEnabled }) => (
|
|||
</div>) : (null)
|
||||
);
|
||||
|
||||
const coverageSummaryText = (coverage) => {
|
||||
const { coverageThresholds } = SETTINGS;
|
||||
const { low, medium, high } = coverageThresholds;
|
||||
const s = coverageSummary(coverage);
|
||||
const result = { className: 'no-change', text: 'No changes' };
|
||||
if (typeof s.percentage !== 'undefined') {
|
||||
const perc = parseInt(s.percentage, 10);
|
||||
if (perc < low.threshold) {
|
||||
result.className = low.className;
|
||||
} else if (perc < medium.threshold) {
|
||||
result.className = medium.className;
|
||||
} else {
|
||||
result.className = high.className;
|
||||
}
|
||||
result.text = `${perc}% - ${s.coveredLines} lines covered out of ${s.addedLines} added`;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const csetWithCcovData = async (cset) => {
|
||||
if (!cset.node) {
|
||||
throw Error(`No node for cset: ${cset}`);
|
||||
}
|
||||
const newCset = Object.assign({}, cset);
|
||||
// XXX: fetch does not support timeouts. I would like to add a 5 second
|
||||
// timeout rather than wait Heroku's default 30 second timeout. Specially
|
||||
// since we're doing sequential fetches.
|
||||
// XXX: Wrap fetch() in a Promise; add a setTimeout and call reject() if
|
||||
// it goes off, otherwise resolve with the result of the fetch()
|
||||
try {
|
||||
const res = await FetchAPI.getChangesetCoverage(cset.node);
|
||||
if (res.status === 202) {
|
||||
// This is the only case when we poll again
|
||||
newCset.summary = PENDING;
|
||||
} else if (res.status === 200) {
|
||||
const ccSum = await res.json();
|
||||
|
||||
// XXX: Document in which cases we would not have overall_cur
|
||||
if (ccSum.overall_cur) {
|
||||
// We have coverage data, thus, adding links to the coverage diff viewer
|
||||
// and unhiding the csets
|
||||
newCset.linkify = true;
|
||||
newCset.hidden = false;
|
||||
const result = coverageSummaryText(ccSum);
|
||||
newCset.summary = result.text;
|
||||
newCset.summaryClassName = result.className;
|
||||
} else {
|
||||
console.error(`No overall_cur: ${ccSum}`);
|
||||
}
|
||||
} else if (res.status === 500) {
|
||||
newCset.summary = res.statusText;
|
||||
} else {
|
||||
console.log(`Unexpected HTTP code (${res.status}) for ${newCset}`);
|
||||
}
|
||||
return newCset;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
console.log(`Failed to fetch data for ${cset.node}`);
|
||||
return cset;
|
||||
}
|
||||
};
|
||||
|
||||
// Return list of changesets
|
||||
const pushesToCsets = async (pushes, hiddenDefault) => {
|
||||
const ignore = ({ desc, author }) => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const SETTINGS = {
|
||||
export const SETTINGS = {
|
||||
coverageThresholds: {
|
||||
low: {
|
||||
threshold: 20,
|
||||
|
@ -14,4 +14,4 @@ const SETTINGS = {
|
|||
},
|
||||
};
|
||||
|
||||
export default SETTINGS;
|
||||
export const PENDING = 'Pending';
|
||||
|
|
|
@ -19,8 +19,32 @@ a {
|
|||
/* XXX: The lines inside of pre overrun if the window is very little */
|
||||
pre { margin: 0; }
|
||||
.codecoverage-diffviewer table,th,tr,td {
|
||||
border-collapse: collapse;
|
||||
padding: 0px;
|
||||
border-collapse: collapse;
|
||||
padding: 0px;
|
||||
}
|
||||
.return-home {
|
||||
margin: 0em 0em 1em;
|
||||
}
|
||||
.coverage-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0em 0em 1em;
|
||||
min-width: 530px;
|
||||
max-width: 600px;
|
||||
}
|
||||
.coverage-meta-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.parent-meta-subtitle {
|
||||
font-weight: 400;
|
||||
text-align: left;
|
||||
}
|
||||
.meta {
|
||||
flex-direction: row;
|
||||
}
|
||||
.meta-right {
|
||||
text-align: right;
|
||||
}
|
||||
.diff-block {
|
||||
border: 1px solid #eaeaea;
|
||||
|
@ -34,6 +58,7 @@ pre { margin: 0; }
|
|||
}
|
||||
.diff-file {
|
||||
margin: 0em 0em 1em;
|
||||
min-width: 530px;
|
||||
width: 100%;
|
||||
}
|
||||
.file-path {
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import { PENDING, SETTINGS } from '../settings';
|
||||
import * as FetchAPI from '../utils/fetch_data';
|
||||
|
||||
export const arrayToMap = (csets) => {
|
||||
const newCsets = {};
|
||||
csets.forEach((cset) => {
|
||||
|
@ -9,3 +12,100 @@ export const arrayToMap = (csets) => {
|
|||
export const mapToArray = csets => (
|
||||
Object.keys(csets).map(node => csets[node])
|
||||
);
|
||||
|
||||
const coverageSummary = (coverage) => {
|
||||
const s = {
|
||||
addedLines: 0,
|
||||
coveredLines: 0,
|
||||
};
|
||||
if (coverage.diffs.length > 0) {
|
||||
coverage.diffs.forEach((diff) => {
|
||||
diff.changes.forEach((change) => {
|
||||
if (change.coverage === 'Y') {
|
||||
s.coveredLines += 1;
|
||||
}
|
||||
if (change.coverage !== '?') {
|
||||
s.addedLines += 1;
|
||||
}
|
||||
});
|
||||
s.percentage = (s.addedLines === 0) ?
|
||||
undefined :
|
||||
100 * (s.coveredLines / s.addedLines);
|
||||
});
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
export const coverageSummaryText = (coverage) => {
|
||||
const { coverageThresholds } = SETTINGS;
|
||||
const { low, medium, high } = coverageThresholds;
|
||||
const s = coverageSummary(coverage);
|
||||
const result = { className: 'no-change', text: 'No changes' };
|
||||
if (typeof s.percentage !== 'undefined') {
|
||||
const perc = parseInt(s.percentage, 10);
|
||||
if (perc < low.threshold) {
|
||||
result.className = low.className;
|
||||
} else if (perc < medium.threshold) {
|
||||
result.className = medium.className;
|
||||
} else {
|
||||
result.className = high.className;
|
||||
}
|
||||
result.text = `${perc}% - ${s.coveredLines} lines covered out of ${s.addedLines} added`;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
export const csetWithCcovData = async (cset) => {
|
||||
if (!cset.node) {
|
||||
throw Error(`No node for cset: ${cset}`);
|
||||
}
|
||||
const newCset = Object.assign({}, cset);
|
||||
// XXX: fetch does not support timeouts. I would like to add a 5 second
|
||||
// timeout rather than wait Heroku's default 30 second timeout. Specially
|
||||
// since we're doing sequential fetches.
|
||||
// XXX: Wrap fetch() in a Promise; add a setTimeout and call reject() if
|
||||
// it goes off, otherwise resolve with the result of the fetch()
|
||||
try {
|
||||
const res = await FetchAPI.getChangesetCoverage(cset.node);
|
||||
if (res.status === 202) {
|
||||
// This is the only case when we poll again
|
||||
newCset.summary = PENDING;
|
||||
} else if (res.status === 200) {
|
||||
const coverageData = await res.json();
|
||||
|
||||
// XXX: Document in which cases we would not have overall_cur
|
||||
if (coverageData.overall_cur) {
|
||||
// We have coverage data, thus, adding links to the coverage diff viewer
|
||||
// and unhiding the csets
|
||||
newCset.linkify = true;
|
||||
newCset.hidden = false;
|
||||
newCset.coverage = {
|
||||
...coverageData,
|
||||
diffMeta: node => ({
|
||||
hgRev: `${FetchAPI.hgHost}/mozilla-central/rev/${node}`,
|
||||
ccovBackend: `${FetchAPI.ccovBackend}/coverage/changeset/${node}`,
|
||||
}),
|
||||
parentMeta: coverage => ({
|
||||
pushlog: `https://hg.mozilla.org/mozilla-central/pushloghtml?changeset=${coverage.build_changeset}`,
|
||||
codecov: `https://codecov.io/gh/marco-c/gecko-dev/commit/${coverage.git_build_changeset}`,
|
||||
gh: `https://github.com/mozilla/gecko-dev/commit/${coverage.git_build_changeset}`,
|
||||
}),
|
||||
};
|
||||
const result = coverageSummaryText(coverageData);
|
||||
newCset.summary = result.text;
|
||||
newCset.summaryClassName = result.className;
|
||||
} else {
|
||||
console.error(`No overall_cur: ${coverageData}`);
|
||||
}
|
||||
} else if (res.status === 500) {
|
||||
newCset.summary = res.statusText;
|
||||
} else {
|
||||
console.log(`Unexpected HTTP code (${res.status}) for ${newCset}`);
|
||||
}
|
||||
return newCset;
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
console.log(`Failed to fetch data for ${cset.node}`);
|
||||
return cset;
|
||||
}
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче