Fixing buildset req (#3)

* Adding builders to state

* indentation fix

* printing buildData in commitsCard.js

* changing into functional component and mapping stateToProps

* buildset endpoint now loads only relevent data

* fixing reducer- builders were undefined

* Adding builds to PR tab

* increasing the range of unixF

* adding message to outer check
This commit is contained in:
Ayush Kumar Sinha 2019-08-15 17:20:46 +05:30 committed by Victor Perevertkin
parent 3ff7737442
commit 842c5acd06
10 changed files with 305 additions and 167 deletions

13
app.js
View File

@ -128,10 +128,10 @@ app.get('/api/pulls', (req, res) => {
//------- BUILD-SET END-POINT -------
function buildSetReq() {
function buildSetReq(str) {
//https://build.reactos.org/api/v2/buildsets?field=bsid&field=sourcestamps&order=-bsid&offset=0&limit=200
const buildSets = {
uri:
'https://build.reactos.org/api/v2/buildsets?field=bsid&field=sourcestamps&order=-bsid&offset=0&limit=200',
uri: `https://build.reactos.org/api/v2/buildsets?field=bsid&field=sourcestamps&field=submitted_at&order=-bsid${str}`,
headers: {
'User-Agent': 'Request-Promise'
},
@ -142,7 +142,12 @@ function buildSetReq() {
}
app.get('/api/buildsets', (req, res) => {
rp(buildSetReq())
let q =
'&submitted_at__le=' +
req.query.submitted_at__le +
'&submitted_at__ge=' +
req.query.submitted_at__ge;
rp(buildSetReq(q))
.then(body => {
res.json(body);
})

View File

@ -1,53 +1,61 @@
import React from 'react';
import { connect } from 'react-redux';
function Build({builderid, number, builderName, started_at, complete_at, state_string}) {
return (<React.Fragment>
<div className='col-sm-3'>
<a
target='_blank'
rel='noreferrer noopener'
href={`https://build.reactos.org/#builders/${
builderid
}/builds/${number}`}
>
{builderName}
</a>
</div>
<div className='col-sm-3'>
{state_string}
{state_string === 'build successful' ? (
<i className='fa fa-check' />
) : (
<i />
)}
</div>
<div className='col-sm-3'>started_at:{started_at}</div>
<div className='col-sm-3'>complete_at:{complete_at}</div>
</React.Fragment>);
function Build({ builderName, ...build }) {
let completedDate = new Date(build.complete_at * 1000);
let startedDate = new Date(build.started_at * 1000);
return (
<React.Fragment>
<div className='col-sm-2'>
<a
target='_blank'
rel='noreferrer noopener'
href={`https://build.reactos.org/#builders/${
build.builderid
}/builds/${build.number}`}
>
{builderName}
</a>
</div>
<div className='col-sm-3'>
{build.state_string}
{build.state_string === 'build successful' ? (
<i className='fa fa-check' />
) : (
<i />
)}
</div>
<div className='col-sm-3'>Started: {startedDate.toLocaleString()}</div>
<div className='col-sm-4'>
Completed: {build.complete_at ? completedDate.toLocaleString() : <p />}
</div>
</React.Fragment>
);
}
function renderBuild(props) {
return <Build key={props.buildid} {...props} />;
}
function BuildDetails({builds}) {
return (<React.Fragment>
{builds.length > 0 ? (
<div className='row'>{builds.map(renderBuild)}</div>
) : (
<p>
<strong>No data Exists</strong>
</p>
)}
</React.Fragment>);
function BuildDetails({ builds }) {
return (
<React.Fragment>
{builds.length > 0 ? (
<div className='row'>{builds.map(renderBuild)}</div>
) : (
<p>
<strong>No data Exists</strong>
</p>
)}
</React.Fragment>
);
}
const mapStateToProps = ({ builders }, ownProps) => {
return {
builds: ownProps.builds.map(
build => {...build, builderName: builders[build.builderid].name}
)
builds: ownProps.builds.map(build => ({
...build,
builderName: builders[build.builderid].name
}))
};
};

View File

@ -83,7 +83,13 @@ function CommitsCard(props) {
</div>
<hr />
<h5>Build Details:</h5>
<BuildDetails builds={props.builds} />
{props.builds ? (
<BuildDetails builds={props.builds} />
) : (
<div>
<strong>Loading Build Data ...</strong>
</div>
)}
</CardBody>
</UncontrolledCollapse>
</Card>

View File

@ -7,80 +7,93 @@ import Loading from './Loading';
import PullsCard from './PullsCard';
class Pulls extends React.Component {
componentDidMount() {
this.props.loadPulls();
}
renderPulls = pull => {
return (
<div className='panel-margin' key={pull.id}>
<PullsCard {...pull} />
</div>
);
};
render() {
return (
<div className='container margin'>
<h2>Latest Pulls</h2>
<PullState />
{this.props.isLoading.load ? (
<Loading text='Fetching latest PRs for you...' />
) : (
<div>
<div>{this.props.pulls.map(this.renderPulls)}</div>
{this.props.error ? (
<div className='error'>
Unexpected Error occured. Kindly Reload the page
<br />
Err:{this.props.error}
</div>
) : (
<div>
<button
type='button'
onClick={() => {
this.props.loadPulls(this.props.page.prev);
}}
className='btn btn-primary '
disabled={this.props.page.prev === null || this.props.error !== null}
>
<i className='fa fa-caret-left' aria-hidden='true' />
Previous Page{' '}
</button>{' '}
<button
type='button'
onClick={() => {
this.props.loadPulls(this.props.page.next);
}}
className='btn btn-primary'
disabled={this.props.page.next === null || this.props.error !== null}
>
Next Page{' '}
<i className='fa fa-caret-right' aria-hidden='true' />
</button>
<footer className='blockquote-footer'>
Page {this.props.page.next - 1}
</footer>
<div className='footer-blockquote' />
</div>
)}
</div>
)}
</div>
);
}
componentDidMount() {
this.props.loadPulls();
}
renderPulls = pull => {
return (
<div className='panel-margin' key={pull.id}>
<PullsCard {...pull} builds={this.props.build[pull.number]} />
</div>
);
};
render() {
return (
<div className='container margin'>
<h2>Latest Pulls</h2>
<PullState />
{this.props.isLoading.load ? (
<Loading text='Fetching latest PRs for you...' />
) : (
<div>
<div>{this.props.pulls.map(this.renderPulls)}</div>
{this.props.error ? (
<div className='error'>
Unexpected Error occured. Kindly Reload the page
<br />
Err:{this.props.error}
</div>
) : (
<div>
<button
type='button'
onClick={() => {
this.props.loadPulls(this.props.page.prev);
}}
className='btn btn-primary '
disabled={
this.props.page.prev === null || this.props.error !== null
}
>
<i className='fa fa-caret-left' aria-hidden='true' />
Previous Page{' '}
</button>{' '}
<button
type='button'
onClick={() => {
this.props.loadPulls(this.props.page.next);
}}
className='btn btn-primary'
disabled={
this.props.page.next === null || this.props.error !== null
}
>
Next Page{' '}
<i className='fa fa-caret-right' aria-hidden='true' />
</button>
<footer className='blockquote-footer'>
Page {this.props.page.next - 1}
</footer>
<div className='footer-blockquote' />
</div>
)}
</div>
)}
</div>
);
}
}
const mapStateToProps = ({ pulls, page, isLoading, error }) => ({
pulls,
page,
isLoading,
error
const mapStateToProps = ({
pulls,
builders,
page,
isLoading,
error,
build
}) => ({
pulls,
builders,
page,
isLoading,
error,
build
});
const mapDispatchToProps = dispatch => ({
loadPulls: next => dispatch(loadPulls(next))
loadPulls: next => dispatch(loadPulls(next))
});
export default connect(
mapStateToProps,
mapDispatchToProps
mapStateToProps,
mapDispatchToProps
)(Pulls);

View File

@ -1,50 +1,59 @@
import React from 'react';
import { UncontrolledCollapse, CardBody, Card, CardHeader } from 'reactstrap';
import BuildDetails from './BuildDetails';
function PullsCard(props) {
let tog = 'toggler' + props.id;
let createdDate = new Date(props.created_at);
let closedDate = new Date(props.closed_at);
let mergedDate = new Date(props.merged_at);
return (
<Card>
<CardHeader className='new' type='button' id={tog}>
<div className='row'>
<div className='col-sm'>{props.number}</div>
<div className='col-sm'>{props.state}</div>
<div className='col-sm'>{props.user.login}</div>
</div>
</CardHeader>
<UncontrolledCollapse toggler={tog}>
<CardBody className='indent'>
<p>
<strong>Pull number: </strong>{' '}
<a target='_blank' rel='noreferrer noopener' href={props.html_url}>
{props.number}
</a>
</p>
<p>
<strong>Title:</strong> {props.title}
</p>
<p>
<strong>Body:</strong> {props.body}
</p>
<div className='row'>
<div className='col-sm'>
<strong>Created at: </strong>
{createdDate.toLocaleString()}
</div>
<div className='col-sm'>
<strong>Closed at: </strong>
{props.closed_at !== null ? closedDate.toLocaleString() : null}
</div>
<div className='col-sm'>
<strong>Merged at: </strong>
{props.merged_at !== null ? mergedDate.toLocaleString() : null}
</div>
</div>
</CardBody>
</UncontrolledCollapse>
</Card>
);
let tog = 'toggler' + props.id;
let createdDate = new Date(props.created_at);
let closedDate = new Date(props.closed_at);
let mergedDate = new Date(props.merged_at);
return (
<Card>
<CardHeader className='new' type='button' id={tog}>
<div className='row'>
<div className='col-sm'>{props.number}</div>
<div className='col-sm'>{props.state}</div>
<div className='col-sm'>{props.user.login}</div>
</div>
</CardHeader>
<UncontrolledCollapse toggler={tog}>
<CardBody className='indent'>
<p>
<strong>Pull number: </strong>{' '}
<a target='_blank' rel='noreferrer noopener' href={props.html_url}>
{props.number}
</a>
</p>
<p>
<strong>Title:</strong> {props.title}
</p>
<p>
<strong>Body:</strong> {props.body}
</p>
<div className='row'>
<div className='col-sm'>
<strong>Created at: </strong>
{createdDate.toLocaleString()}
</div>
<div className='col-sm'>
<strong>Closed at: </strong>
{props.closed_at !== null ? closedDate.toLocaleString() : null}
</div>
<div className='col-sm'>
<strong>Merged at: </strong>
{props.merged_at !== null ? mergedDate.toLocaleString() : null}
</div>
</div>
<hr />
<h5>Build Details:</h5>
{props.builds ? (
<BuildDetails builds={props.builds} />
) : (
<div>Loading Pulls data...</div>
)}
</CardBody>
</UncontrolledCollapse>
</Card>
);
}
export default PullsCard;

View File

@ -7,13 +7,15 @@ export const fetchCommits = async (sha, page) => {
return data;
};
export const fetchBuildSets = async () => {
const response = await fetch('/api/buildsets');
const data = await response.json();
if (response.status >= 400) {
throw new Error(data.errors);
export const fetchBuildSets = async str => {
if (str) {
const response = await fetch(`/api/buildsets?${str}`);
const data = await response.json();
if (response.status >= 400) {
throw new Error(data.errors);
}
return data.buildsets;
}
return data.buildsets;
};
export const fetchBuildReq = async str => {

View File

@ -4,7 +4,7 @@ const builderReducer = (state = {}, action) => {
if (action.type === BUILDERS.LOAD_SUCCESS) {
const builders = {};
for (const builder of action.builders) {
builders[action.builders.builderid] = builder;
builders[builder.builderid] = builder;
}
return builders;
}

View File

@ -7,6 +7,21 @@ import { setBuildSetsError, setBuilds } from '../actions';
* filtering data on BuildBot side.
* see BuildBot API: http://docs.buildbot.net/latest/developer/rest.html#filtering
*/
/* The function convertIsoToUnixTime takes two parameters (committer.data)
* first[0] and last commit[9] of the page and converts it to unix time format
* and is sent along with fetchBuildSets, and loads only 20-30 datasets into
* the memory,which is easy to process.
* +5000 is done to normalize the diffrence between committ date and build triggered.
* see BuildBot API: http://docs.buildbot.net/latest/developer/rest.html#filtering
*/
function convertIsoToUnixTime(isoF, isoL) {
let unixF = Date.parse(isoF.commit.committer.date) / 1000 + 5000;
let unixL = Date.parse(isoL.commit.committer.date) / 1000;
return '&submitted_at__le=' + unixF + '&submitted_at__ge=' + unixL;
}
function getBuildQString(builds) {
return builds
.map(build => 'buildrequestid__contains=' + build.buildrequestid)
@ -36,7 +51,10 @@ function getBuildReqQString(commits, buildData) {
function* handleBuildsLoad() {
try {
const commits = yield select(state => state.commits);
const buildSetsRaw = yield call(fetchBuildSets);
const buildSetsRaw = yield call(
fetchBuildSets,
convertIsoToUnixTime(commits[0], commits[9])
);
if (buildSetsRaw.length === 0) {
yield put(setBuildSetsError('Nothing returned'));
return;

View File

@ -5,13 +5,14 @@ import branchesSaga from './branchesSaga';
import pullsSaga from './pullsSaga';
import buildersSaga from './buildersSaga';
import buildSetSaga from './buildSetSaga';
import pullsBuildSaga from './pullsBuildSaga';
export default function* rootSaga() {
yield all([
commitsSaga(),
branchesSaga(),
pullsSaga(),
buildersSaga(),
buildSetSaga()
buildSetSaga(),
pullsBuildSaga()
]);
}

View File

@ -0,0 +1,76 @@
import { takeEvery, call, put, select } from 'redux-saga/effects';
import { PULLS } from '../constants';
import { fetchBuildSets, fetchBuildReq, fetchBuilds } from '../api';
import { setBuildSetsError, setBuilds } from '../actions';
function convertIsoToUnixTime(isoF, isoL) {
let unixF = Date.parse(isoF.created_at) / 1000 + 30000;
let unixL = Date.parse(isoL.created_at) / 1000;
return '&submitted_at__le=' + unixF + '&submitted_at__ge=' + unixL;
}
function getBuildQString(builds) {
return builds
.map(build => 'buildrequestid__contains=' + build.buildrequestid)
.join('&');
}
function getBuildReqQString(pulls, buildSetsRaw) {
var a = pulls
.flatMap(pull =>
buildSetsRaw.filter(
build =>
build.sourcestamps[0].branch === `refs/pull/${pull.number}/head` ||
build.sourcestamps[0].branch === `refs/pull/${pull.number}/merge`
)
)
.map(bd => 'buildsetid__contains=' + bd.bsid)
.join('&');
return a;
}
function* handlePullsBuildLoad() {
try {
const pulls = yield select(state => state.pulls);
const buildSetsRaw = yield call(
fetchBuildSets,
convertIsoToUnixTime(pulls[0], pulls[9])
);
if (buildSetsRaw.length === 0) {
yield put(setBuildSetsError('Nothing returned'));
return;
}
const buildReqsRaw = yield call(
fetchBuildReq,
getBuildReqQString(pulls, buildSetsRaw)
);
const buildsRaw = yield call(fetchBuilds, getBuildQString(buildReqsRaw));
const buildsByPR = {};
for (let { number } of pulls) {
const buildSetIds = buildSetsRaw
.filter(
bs =>
bs.sourcestamps[0].branch === `refs/pull/${number}/head` ||
bs.sourcestamps[0].branch === `refs/pull/${number}/merge`
)
.map(bs => bs.bsid);
const buildReqIds = buildReqsRaw
.filter(br => buildSetIds.includes(br.buildsetid))
.map(br => br.buildrequestid);
const builds = buildsRaw.filter(b =>
buildReqIds.includes(b.buildrequestid)
);
buildsByPR[number] = builds;
}
yield put(setBuilds(buildsByPR));
} catch (error) {
yield put(setBuildSetsError(error.toString()));
}
}
export default function* watchPullsBuildLoad() {
yield takeEvery(PULLS.LOAD_SUCCESS, handlePullsBuildLoad);
}