Refactor: Split up some modules + rename as .jsx files

This commit is contained in:
Armen Zambrano G 2018-03-26 10:59:16 -04:00 коммит произвёл Armen Zambrano
Родитель 3a4ce2c9e7
Коммит 71a5aa8aa0
19 изменённых файлов: 274 добавлений и 254 удалений

Просмотреть файл

@ -17,7 +17,6 @@ module.exports = {
eslint: {
rules: {
"no-console": "off",
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
"react/prop-types": "off",
}
}

Просмотреть файл

@ -20,6 +20,9 @@
"lint-staged": {
"*.js": [
"neutrino lint"
],
"*.jsx": [
"neutrino lint"
]
},
"dependencies": {

Просмотреть файл

@ -1,89 +0,0 @@
import React, { Component } from 'react';
import { Route } from 'react-router-dom';
import ChangesetsViewerContainer from './summaryviewer';
import DiffViewerContainer from './diffviewer';
import FileViewerContainer from './fileviewer';
import settings from '../settings';
import clearLocalCache from '../utils/localCache';
import '../style.css';
const { REPO, GITHUB_RIBBON } = settings;
const GitHubRibbon = () => (
<div className="github-ribbon">
<a href={`${REPO}`}>
<img src={`${GITHUB_RIBBON}`} alt="Fork me on GitHub" title="Fork me on GitHub" />
</a>
</div>
);
const AppDisclaimer = () => (
<div className="app-disclaimer">
<div>
<p>NOTE: This app is in beta state.</p>
<p>There are some core issues with regards to coverage collection. These are
explained in the project&apos;s&nbsp;
<a href={`${REPO}/blob/master/README.md#disclaimers`}>readme</a>.
</p>
</div>
<div>
Project information: <a href={REPO}>Frontend repository</a>&nbsp;
<a href={`${REPO}/issues?q=is%3Aissue+is%3Aopen+label%3Abug`}>Known issues</a>
</div>
</div>
);
// Main component
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
repoName: 'mozilla-central',
};
}
render() {
const { repoName } = this.state;
return (
<div className="app">
<Route
exact
path="/"
render={() => (
<div className="changesets-viewer">
<GitHubRibbon />
<AppDisclaimer />
<ChangesetsViewerContainer
repoName={repoName}
/>
</div>
)}
/>
<Route
path="/changeset/:id"
render={({ match }) => (
<DiffViewerContainer
changeset={match.params.id}
repoName={repoName}
/>
)}
/>
<Route
path="/file"
component={FileViewerContainer}
/>
<Route
path="/clear-cache"
render={() => {
if (clearLocalCache()) {
return (<p>The local database has been cleared.</p>);
}
return (<p>Failed to clear the local DB.</p>);
}}
/>
</div>
);
}
}

52
src/components/app.jsx Normal file
Просмотреть файл

@ -0,0 +1,52 @@
import React from 'react';
import { Route } from 'react-router-dom';
import ChangesetsViewerContainer from '../containers/summaryViewer';
import DiffViewerContainer from '../containers/diffViewer';
import FileViewerContainer from '../containers/fileViewer';
import settings from '../settings';
import clearLocalCache from '../utils/localCache';
import '../style.css';
import AppDisclaimer from './disclaimer';
import GitHubRibbon from './githubRibbon';
// Main component
export default () => (
<div className="app">
<Route
exact
path="/"
render={() => (
<div className="changesets-viewer">
<GitHubRibbon />
<AppDisclaimer />
<ChangesetsViewerContainer
repoName={settings.FIREFOX_REPO}
/>
</div>
)}
/>
<Route
path="/changeset/:id"
render={({ match }) => (
<DiffViewerContainer
changeset={match.params.id}
repoName={settings.FIREFOX_REPO}
/>
)}
/>
<Route
path="/file"
component={FileViewerContainer}
/>
<Route
path="/clear-cache"
render={() => {
if (clearLocalCache()) {
return (<p>The local database has been cleared.</p>);
}
return (<p>Failed to clear the local DB.</p>);
}}
/>
</div>
);

Просмотреть файл

@ -0,0 +1,14 @@
import bzIcon from '../static/bugzilla.png';
const ChangesetDescription = ({ description, bzUrl }) => (
<div className="changeset-description">
{description.substring(0, 40).padEnd(40)}
{(bzUrl) ?
<a href={bzUrl} target="_blank">
<img className="bzIcon" src={bzIcon} alt="bugzilla icon" />
</a>
: undefined}
</div>
);
export default ChangesetDescription;

Просмотреть файл

@ -0,0 +1,40 @@
import ChangesetDescription from './changesetDescription';
import eIcon from '../static/noun_205162_cc.png';
import dummyIcon from '../static/dummyIcon16x16.png';
const ChangesetInfo = ({ changeset }) => {
const {
authorInfo, desc, hidden, bzUrl, node, summary, summaryClassName,
} = changeset;
const hgUrl = changeset.coverage.hgRev;
const handleClick = (e) => {
if (e.target.tagName.toLowerCase() === 'td') {
window.open(`/#/changeset/${node}`, '_blank');
} else {
e.stopPropagation();
}
};
return (
<tr className={(hidden) ? 'hidden-changeset' : 'changeset'} onClick={e => handleClick(e)}>
<td className="changeset-author">
{(authorInfo.email) ?
<a href={`mailto: ${authorInfo.email}`}>
<img className="eIcon" src={eIcon} alt="email icon" />
</a> : <img className="icon-substitute" src={dummyIcon} alt="placeholder icon" />
}
<span className="changeset-eIcon-align">{authorInfo.name.substring(0, 60)}</span>
</td>
<td className="changeset-hg">
{(hgUrl) ?
<a href={hgUrl} target="_blank">{node.substring(0, 12)}</a>
: <span>{node.substring(0, 12)}</span>}
</td>
<td>
<ChangesetDescription description={desc} bzUrl={bzUrl} />
</td>
<td className={`changeset-summary ${summaryClassName}`}>{summary}</td>
</tr>
);
};
export default ChangesetInfo;

Просмотреть файл

@ -1,97 +1,7 @@
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import { orderBy } from 'lodash';
import { csetWithCcovData } from '../utils/data';
import hash from '../utils/hash';
import { getDiff } from '../utils/hg';
import settings from '../settings';
const parse = require('parse-diff');
/* DiffViewer loads a raw diff from Mozilla's hg-web and code coverage from
* shiptit-uplift to show a diff with code coverage information for added
* lines.
*/
export default class DiffViewerContainer extends Component {
constructor(props) {
super(props);
this.state = {
appError: undefined,
csetMeta: {
coverage: undefined,
},
parsedDiff: [],
};
}
componentDidMount() {
const { changeset } = this.props;
Promise.all([this.fetchSetCoverageData(changeset), this.fetchSetDiff(changeset)]);
}
async fetchSetCoverageData(changeset) {
try {
const csetMeta = await csetWithCcovData({ node: changeset });
if (csetMeta.summary === settings.STRINGS.PENDING) {
this.setState({
appError: 'The coverage data is still pending. Try again later.',
});
}
this.setState({ csetMeta });
} catch (error) {
console.error(error);
this.setState({
appError: 'There was an error fetching the code coverage data.',
});
}
}
async fetchSetDiff(changeset) {
try {
const text = await (await getDiff(changeset)).text();
this.setState({ parsedDiff: parse(text) });
} catch (e) {
if ((e instanceof TypeError) && (e.message === 'Failed to fetch')) {
this.setState({
appError: 'We\'ve had a network issue. Please try again.',
});
} else {
this.setState({
appError: 'We did not manage to parse the diff correctly.',
});
// Since we're not checking for e.message we should raise it
// so it shows up on sentry.io
throw e;
}
}
}
render() {
const { appError, csetMeta, parsedDiff } = this.state;
return (
<DiffViewer
{...csetMeta}
appError={appError}
parsedDiff={parsedDiff}
/>
);
}
}
// Adds a new percent property to each file in parsedDiff that represents
// the proportion of uncovered lines.
// This directly modifies each object in the parsedDiff array.
const sortByPercent = (parsedDiff, coverage) => {
parsedDiff.forEach((p) => {
const cov = p;
cov.percent = (coverage.diffs[p.from]) ? coverage.diffs[p.from].percent : 0;
});
const sortedDiffs = orderBy(parsedDiff, ({ percent }) => percent || 0, ['desc']);
return sortedDiffs;
};
const DiffViewer = ({
appError, coverage, parsedDiff, summary,
}) => (
@ -106,7 +16,7 @@ const DiffViewer = ({
coverage={coverage}
summary={summary}
/>,
{sortByPercent(parsedDiff, coverage).map((diffBlock) => {
{parsedDiff.map((diffBlock) => {
// We only push down the subset of code coverage data
// applicable to a file
const path = (diffBlock.to === '/dev/null') ? diffBlock.from : diffBlock.to;
@ -261,3 +171,5 @@ const DiffLine = ({ change, fileDiffs, id }) => {
</tr>
);
};
export default DiffViewer;

Просмотреть файл

@ -0,0 +1,20 @@
import settings from '../settings';
const { REPO } = settings;
export default () => (
<div className="app-disclaimer">
<div>
<p>NOTE: This app is in beta state.</p>
<p>There are some core issues with regards to coverage collection. These are
explained in the project&apos;s&nbsp;
<a href={`${REPO}/blob/master/README.md#disclaimers`}>readme</a>.
</p>
</div>
<div>
Project information: <a href={REPO}>Frontend repository</a>&nbsp;
<a href={`${REPO}/issues?q=is%3Aissue+is%3Aopen+label%3Abug`}>Known issues</a>
</div>
</div>
);

Просмотреть файл

@ -1,5 +1,5 @@
// This file contains coverage information for a particular revision of a source file
import React, { Component } from 'react';
import { Component } from 'react';
import getPercentCovColor from '../utils/color';
import { TRIANGULAR_BULLET } from '../utils/symbol';

Просмотреть файл

@ -0,0 +1,11 @@
import settings from '../settings';
const { REPO, GITHUB_RIBBON } = settings;
export default () => (
<div className="github-ribbon">
<a href={`${REPO}`}>
<img src={`${GITHUB_RIBBON}`} alt="Fork me on GitHub" title="Fork me on GitHub" />
</a>
</div>
);

Просмотреть файл

@ -0,0 +1,22 @@
import ChangesetInfo from './changesetInfo';
const ChangesetsViewer = ({ changesets }) => (
<table className="changeset-viewer">
<tbody>
<tr>
<th>Author</th>
<th>Changeset</th>
<th>Description</th>
<th>Coverage summary</th>
</tr>
{Object.keys(changesets).map(node => (
<ChangesetInfo
key={node}
changeset={changesets[node]}
/>
))}
</tbody>
</table>
);
export default ChangesetsViewer;

Просмотреть файл

@ -0,0 +1,94 @@
import React, { Component } from 'react';
import { orderBy } from 'lodash';
import DiffViewer from '../components/diffViewer';
import { csetWithCcovData } from '../utils/data';
import { getDiff } from '../utils/hg';
import settings from '../settings';
const parse = require('parse-diff');
// Adds a new percent property to each file in parsedDiff that represents
// the proportion of uncovered lines.
// This directly modifies each object in the parsedDiff array.
const sortByPercent = (parsedDiff, coverage) => {
parsedDiff.forEach((p) => {
const cov = p;
cov.percent = (coverage.diffs[p.from]) ? coverage.diffs[p.from].percent : 0;
});
const sortedDiffs = orderBy(parsedDiff, ({ percent }) => percent || 0, ['desc']);
return sortedDiffs;
};
/* DiffViewer loads a raw diff from Mozilla's hg-web and code coverage from
* shiptit-uplift to show a diff with code coverage information for added
* lines.
*/
export default class DiffViewerContainer extends Component {
constructor(props) {
super(props);
this.state = {
appError: undefined,
csetMeta: {
coverage: undefined,
},
parsedDiff: [],
};
}
componentDidMount() {
const { changeset } = this.props;
Promise.all([this.fetchSetCoverageData(changeset), this.fetchSetDiff(changeset)]);
}
async fetchSetCoverageData(changeset) {
try {
const csetMeta = await csetWithCcovData({ node: changeset });
if (csetMeta.summary === settings.STRINGS.PENDING) {
this.setState({
appError: 'The coverage data is still pending. Try again later.',
});
}
this.setState({ csetMeta });
} catch (error) {
console.error(error);
this.setState({
appError: 'There was an error fetching the code coverage data.',
});
}
}
async fetchSetDiff(changeset) {
try {
const text = await (await getDiff(changeset)).text();
this.setState({ parsedDiff: parse(text) });
} catch (e) {
if ((e instanceof TypeError) && (e.message === 'Failed to fetch')) {
this.setState({
appError: 'We\'ve had a network issue. Please try again.',
});
} else {
this.setState({
appError: 'We did not manage to parse the diff correctly.',
});
// Since we're not checking for e.message we should raise it
// so it shows up on sentry.io
throw e;
}
}
}
render() {
const { appError, csetMeta, parsedDiff } = this.state;
const sortedDiff = sortByPercent(parsedDiff, csetMeta.coverage);
return (
<DiffViewer
{...csetMeta}
appError={appError}
parsedDiff={sortedDiff}
/>
);
}
}

Просмотреть файл

@ -2,7 +2,7 @@ import React, { Component } from 'react';
import * as queryString from 'query-string';
import { fileRevisionCoverageSummary, fileRevisionWithActiveData, rawFile } from '../utils/data';
import { TestsSideViewer, CoveragePercentageViewer } from './fileviewercov';
import { TestsSideViewer, CoveragePercentageViewer } from '../components/fileViewer';
import { HORIZONTAL_ELLIPSIS, HEAVY_CHECKMARK } from '../utils/symbol';
import hash from '../utils/hash';

Просмотреть файл

@ -1,74 +1,12 @@
import React, { Component } from 'react';
import ReactInterval from 'react-interval';
import SummaryViewer from '../components/summaryViewer';
import settings from '../settings';
import { arrayToMap, csetWithCcovData, mapToArray } from '../utils/data';
import getChangesets from '../utils/hg';
import bzIcon from '../static/bugzilla.png';
import eIcon from '../static/noun_205162_cc.png';
import dummyIcon from '../static/dummyIcon16x16.png';
const { INTERNAL_ERROR, LOADING, PENDING } = settings.STRINGS;
const { REPO } = settings;
const ChangesetInfo = ({ changeset }) => {
const {
authorInfo, desc, hidden, bzUrl, node, summary, summaryClassName,
} = changeset;
const hgUrl = changeset.coverage.hgRev;
const handleClick = (e) => {
if (e.target.tagName.toLowerCase() === 'td') {
window.open(`/#/changeset/${node}`, '_blank');
} else {
e.stopPropagation();
}
};
// XXX: For desc display only the first line
return (
<tr className={(hidden) ? 'hidden-changeset' : 'changeset'} onClick={e => handleClick(e)}>
<td className="changeset-author">
{(authorInfo.email) ?
<a href={`mailto: ${authorInfo.email}`}>
<img className="eIcon" src={eIcon} alt="email icon" />
</a> : <img className="icon-substitute" src={dummyIcon} alt="placeholder icon" />
}
<span className="changeset-eIcon-align">{authorInfo.name.substring(0, 60)}</span>
</td>
<td className="changeset-hg">
{(hgUrl) ?
<a href={hgUrl} target="_blank">{node.substring(0, 12)}</a>
: <span>{node.substring(0, 12)}</span>}
</td>
<td className="changeset-description">
{(bzUrl) ?
<a href={bzUrl} target="_blank"><img className="bzIcon" src={bzIcon} alt="bugzilla icon" /></a>
: <img className="icon-substitute" src={dummyIcon} alt="placeholder icon" /> }
{desc.substring(0, 40).padEnd(40)}
</td>
<td className={`changeset-summary ${summaryClassName}`}>{summary}</td>
</tr>
);
};
const ChangesetsViewer = ({ changesets }) => (
<table className="changeset-viewer">
<tbody>
<tr>
<th>Author</th>
<th>Changeset</th>
<th>Description</th>
<th>Coverage summary</th>
</tr>
{changesets.map(cset => (
<ChangesetInfo
key={cset.node}
changeset={cset}
/>
))}
</tbody>
</table>
);
const { LOADING, INTERNAL_ERROR, PENDING } = settings.STRINGS;
const PollingStatus = ({ pollingEnabled }) => (
(pollingEnabled) ? (
@ -183,12 +121,12 @@ export default class ChangesetsViewerContainer extends Component {
</div>
)}
{viewableCsets.length > 0 &&
<ChangesetsViewer changesets={viewableCsets} />
<SummaryViewer changesets={viewableCsets} />
}
{(!pollingEnabled && Object.keys(changesets).length > 0) &&
<p style={{ textAlign: 'center', fontWeight: 'bold' }}>
<span>There is currently no coverage data to show. Please </span>
<a href={`${REPO}/issues/new`} target="_blank">file an issue</a>.
<a href={`${settings.REPO}/issues/new`} target="_blank">file an issue</a>.
</p>
}
{(Object.keys(changesets).length === 0) &&

Просмотреть файл

@ -7,13 +7,14 @@ import App from './components/app';
Raven.config('https://60b05bc4ef794a6c9e30e86e6a316083@sentry.io/300397').install();
const root = document.getElementById('root');
const load = () => render((
const load = () => render(
<AppContainer>
<HashRouter>
<App />
</HashRouter>
</AppContainer>
), root);
</AppContainer>,
root,
);
// This is needed for Hot Module Replacement
if (module.hot) {

Просмотреть файл

@ -17,6 +17,7 @@ export default {
className: 'high-coverage',
},
},
FIREFOX_REPO: 'mozilla-central',
GITHUB_RIBBON: 'https://s3.amazonaws.com/github/ribbons/forkme_right_green_007200.png',
MIN_REVISION_LENGTH: 5,
REPO: 'https://github.com/mozilla/firefox-code-coverage-frontend',

Просмотреть файл

@ -384,3 +384,7 @@ span.tests {
right: 0;
border: 0;
}
.bzIcon {
padding: 0 0.3em 0 0.3em;
}

Просмотреть файл

@ -154,7 +154,6 @@ export const csetWithCcovData = async (cset) => {
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,

Просмотреть файл

@ -50,7 +50,6 @@ const initializedChangeset = (cset, id, hidden) => ({
hidden,
bzUrl: bzUrl(cset.desc),
authorInfo: authorInfo(cset.author),
linkify: false,
...cset,
});