mirror of
https://github.com/reactos/developer-web-interface.git
synced 2024-11-26 21:30:35 +00:00
Reworked tests/build status representation
Added links for downloading bootcd/livecd Added a hack for distinguishing GCC/GCC8 builds
This commit is contained in:
parent
8d35e26504
commit
30e4e23ac6
@ -1,46 +1,50 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
function Build({ builderName, ...build }) {
|
||||
let completedDate = new Date(build.complete_at * 1000);
|
||||
let startedDate = new Date(build.started_at * 1000);
|
||||
import { JOB_STATUS } from '../redux/constants'
|
||||
import { statusElement } from './utils'
|
||||
|
||||
|
||||
function bootcdUrl(suffix) {
|
||||
return `https://iso.reactos.org/bootcd/reactos-bootcd-${suffix}.7z`
|
||||
}
|
||||
|
||||
function livecdUrl(suffix) {
|
||||
return `https://iso.reactos.org/livecd/reactos-livecd-${suffix}.7z`
|
||||
}
|
||||
|
||||
function Build({buildId, builderId, number, builderName, status, statusText, isoSuffix}) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className='col-sm-2'>
|
||||
<div className="row" key={buildId}>
|
||||
<div className='col-sm-1'>
|
||||
{statusElement(status, statusText)}
|
||||
</div>
|
||||
<div className='col-sm-5'>
|
||||
<a
|
||||
target='_blank'
|
||||
rel='noreferrer noopener'
|
||||
href={`https://build.reactos.org/#builders/${
|
||||
build.builderid
|
||||
}/builds/${build.number}`}
|
||||
href={`https://build.reactos.org/#builders/${builderId}/builds/${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>
|
||||
<div className='col-sm-6'>
|
||||
{status === JOB_STATUS.SUCCESS &&
|
||||
<React.Fragment>
|
||||
<a href={bootcdUrl(isoSuffix)}><i className="fa fa-download" />{" bootcd"}</a>
|
||||
{" "}
|
||||
<a href={livecdUrl(isoSuffix)}><i className="fa fa-download" />{" livecd"}</a>
|
||||
</React.Fragment>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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>
|
||||
builds.map(Build)
|
||||
) : (
|
||||
<p>
|
||||
<strong>No data Exists</strong>
|
||||
@ -54,7 +58,7 @@ const mapStateToProps = ({ builders }, ownProps) => {
|
||||
return {
|
||||
builds: ownProps.builds.map(build => ({
|
||||
...build,
|
||||
builderName: builders[build.builderid] && builders[build.builderid].name
|
||||
builderName: builders[build.builderId] && builders[build.builderId].name
|
||||
}))
|
||||
};
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { loadCommits, loadBuildSets } from '../redux/actions';
|
||||
import { loadCommits, loadBuilds } from '../redux/actions';
|
||||
import Branches from './Branches';
|
||||
import './styles/Commit.css';
|
||||
import CommitsCard from './CommitsCard';
|
||||
@ -10,24 +10,26 @@ import Loading from './Loading';
|
||||
class Commits extends React.PureComponent {
|
||||
componentDidMount() {
|
||||
this.props.loadCommits(this.props.branch);
|
||||
this.props.loadBuildSets();
|
||||
this.props.loadBuilds();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.branch !== prevProps.branch) {
|
||||
this.props.loadCommits(this.props.branch)
|
||||
this.props.loadBuildSets()
|
||||
this.props.loadBuilds()
|
||||
}
|
||||
}
|
||||
|
||||
renderCommits = commit => {
|
||||
const tests = this.props.tests[commit.sha]
|
||||
|
||||
return (
|
||||
<CommitsCard
|
||||
key={commit.sha}
|
||||
{...commit}
|
||||
builds={this.props.build[commit.sha]}
|
||||
tests={this.props.testData[commit.sha]}
|
||||
previousTests={extractCommitsParentTestsCount(this.props.testData, commit)}
|
||||
builds={this.props.builds[commit.sha]}
|
||||
tests={tests ? Object.values(tests) : []}
|
||||
previousTests={extractCommitsParentTestsCount(this.props.tests, commit)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -109,16 +111,16 @@ const mapStateToProps = ({
|
||||
builders,
|
||||
error,
|
||||
page,
|
||||
build,
|
||||
testData
|
||||
builds,
|
||||
tests
|
||||
}) => ({
|
||||
isLoading,
|
||||
commits,
|
||||
builders,
|
||||
error,
|
||||
page,
|
||||
build,
|
||||
testData
|
||||
builds,
|
||||
tests
|
||||
});
|
||||
|
||||
/**
|
||||
@ -137,7 +139,7 @@ function extractCommitsParentTestsCount(testData, commit) {
|
||||
|
||||
let result = {};
|
||||
|
||||
for (let test of tests) {
|
||||
for (let test of Object.values(tests)) {
|
||||
result[test.source] = test.count
|
||||
}
|
||||
|
||||
@ -145,7 +147,7 @@ function extractCommitsParentTestsCount(testData, commit) {
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
loadBuildSets: () => dispatch(loadBuildSets()),
|
||||
loadBuilds: () => dispatch(loadBuilds()),
|
||||
loadCommits: (branch, next) => dispatch(loadCommits(branch, next))
|
||||
});
|
||||
|
||||
|
@ -2,6 +2,8 @@ import React from 'react';
|
||||
import { UncontrolledCollapse, CardBody, Card, CardHeader } from 'reactstrap';
|
||||
import BuildDetails from './BuildDetails';
|
||||
import TestDetails from './TestDetails';
|
||||
import { JOB_STATUS } from '../redux/constants'
|
||||
import { statusElement } from './utils'
|
||||
|
||||
function firstLineTrimmed(str) {
|
||||
const newStr = str.split('\n', 1)[0]
|
||||
@ -13,6 +15,21 @@ function firstLineTrimmed(str) {
|
||||
}
|
||||
}
|
||||
|
||||
function getTotalStatus(jobs) {
|
||||
if (!jobs.length) return null
|
||||
|
||||
let ret = JOB_STATUS.SUCCESS
|
||||
|
||||
for (let job of jobs) {
|
||||
if (job.status === JOB_STATUS.ONGOING)
|
||||
return JOB_STATUS.ONGOING
|
||||
else if (job.status === JOB_STATUS.FAILURE)
|
||||
ret = JOB_STATUS.FAILURE
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
function CommitsCard({sha, ...props}) {
|
||||
let tog = 'toggler' + sha;
|
||||
let committerDate = new Date(props.commit.committer.date);
|
||||
@ -23,11 +40,22 @@ function CommitsCard({sha, ...props}) {
|
||||
<Card className="mb-1">
|
||||
<CardHeader className='new' type='button' id={tog}>
|
||||
<div className='row'>
|
||||
<div className='col-sm-2'><a href={`https://github.com/reactos/reactos/commit/${sha}`}>{sha.substring(0, 7)}</a></div>
|
||||
<div className='col-sm-8'>
|
||||
{firstLineTrimmed(props.commit.message)}
|
||||
<div className='col-sm-9'>
|
||||
<a className="text-monospace" href={`https://github.com/reactos/reactos/commit/${sha}`}>{sha.substring(0, 7)}</a>
|
||||
{" "}{firstLineTrimmed(props.commit.message)}
|
||||
</div>
|
||||
<div className='col-sm-2'>{props.author.login}</div>
|
||||
<div className="col-sm-1">
|
||||
{props.builds &&
|
||||
props.builds.length > 0
|
||||
? statusElement(getTotalStatus(props.builds), "Build status")
|
||||
: <span title="Loading results"><i className="fa fa-refresh fa-spin" /></span> }
|
||||
{" "}
|
||||
{props.tests &&
|
||||
props.tests.length > 0
|
||||
? statusElement(getTotalStatus(props.tests), "Test status")
|
||||
: <span title="Loading results"><i className="fa fa-refresh fa-spin" /></span> }
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<UncontrolledCollapse toggler={tog}>
|
||||
@ -57,17 +85,12 @@ function CommitsCard({sha, ...props}) {
|
||||
>
|
||||
{props.commit.author.name}
|
||||
</a>
|
||||
{` <${props.commit.author.email}>`}
|
||||
</div>
|
||||
<div className='col-sm'>
|
||||
<strong>Author Date: </strong>
|
||||
{authorDate.toLocaleString()}
|
||||
</div>
|
||||
<div className='col-sm'>
|
||||
<strong>Author Email: </strong>
|
||||
<a href={`mailto:${props.commit.author.email}`} target='_top'>
|
||||
{props.commit.author.email}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<div className='col-sm'>
|
||||
@ -79,19 +102,16 @@ function CommitsCard({sha, ...props}) {
|
||||
>
|
||||
{props.commit.committer.name}
|
||||
</a>
|
||||
{` <${props.commit.committer.email}>`}
|
||||
</div>
|
||||
<div className='col-sm'>
|
||||
<strong>Committer Date: </strong>
|
||||
{committerDate.toLocaleString()}
|
||||
</div>
|
||||
<div className='col-sm'>
|
||||
<strong>Committer Email: </strong>
|
||||
<a href={`mailto:${props.commit.committer.email}`} target='_top'>
|
||||
{props.commit.committer.email}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div className="row">
|
||||
<div className="col-md-5">
|
||||
<h5>Build Details:</h5>
|
||||
{props.builds ? (
|
||||
<BuildDetails builds={props.builds} />
|
||||
@ -100,7 +120,8 @@ function CommitsCard({sha, ...props}) {
|
||||
<strong>Loading Builds...</strong>
|
||||
</div>
|
||||
)}
|
||||
<hr />
|
||||
</div>
|
||||
<div className="col-md-7">
|
||||
<h5>Test Details:</h5>
|
||||
{props.tests ? (
|
||||
<TestDetails tests={props.tests} previousTests={props.previousTests} />
|
||||
@ -109,6 +130,8 @@ function CommitsCard({sha, ...props}) {
|
||||
<strong>No data Exists</strong>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardBody>
|
||||
</UncontrolledCollapse>
|
||||
</Card>
|
||||
|
@ -18,12 +18,14 @@ class Pulls extends React.PureComponent {
|
||||
}
|
||||
|
||||
renderPulls = pull => {
|
||||
const tests = this.props.tests[pull.merge_commit_sha]
|
||||
|
||||
return (
|
||||
<PullsCard
|
||||
key={pull.id}
|
||||
{...pull}
|
||||
builds={this.props.build[pull.number]}
|
||||
tests={this.props.testData[pull.merge_commit_sha]}
|
||||
builds={this.props.builds[pull.number]}
|
||||
tests={tests ? Object.values(tests) : []}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -112,16 +114,16 @@ const mapStateToProps = ({
|
||||
page,
|
||||
isLoading,
|
||||
error,
|
||||
build,
|
||||
testData
|
||||
builds,
|
||||
tests
|
||||
}) => ({
|
||||
pulls,
|
||||
builders,
|
||||
page,
|
||||
isLoading,
|
||||
error,
|
||||
build,
|
||||
testData
|
||||
builds,
|
||||
tests
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
@ -1,4 +1,7 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux'
|
||||
import { statusElement } from './utils'
|
||||
|
||||
|
||||
function renderCountChange(test, previousTestCount) {
|
||||
if (!previousTestCount) {
|
||||
@ -13,38 +16,63 @@ function renderCountChange(test, previousTestCount) {
|
||||
}
|
||||
}
|
||||
|
||||
const trStyle = {fontSize: "0.85rem"}
|
||||
|
||||
function renderTest(test, previousTests) {
|
||||
return (
|
||||
<React.Fragment key={test.id}>
|
||||
<div className='col-sm-2'>Test id: {test.id}</div>
|
||||
<div className='col-sm-4'>
|
||||
<tr key={test.buildBotId} style={trStyle}>
|
||||
<td>
|
||||
{statusElement(test.status, test.statusText)}
|
||||
</td>
|
||||
<td>
|
||||
{test.testerName}
|
||||
{/* Hack: to be removed when we will properly populate build properties */}
|
||||
{test.parentBuild && test.parentBuild.builderid === 10 ? " (GCC8)" : " (GCC)"}
|
||||
</td>
|
||||
<td>
|
||||
{test.testManData
|
||||
? <span title="Tests count: failures/total">{`${test.testManData.failures}/${test.testManData.count}`}</span>
|
||||
: ""}
|
||||
</td>
|
||||
<td>
|
||||
<a
|
||||
target='_blank'
|
||||
rel='noreferrer noopener'
|
||||
href={`https://reactos.org/testman/compare.php?ids=${test.id}`}
|
||||
href={`https://build.reactos.org/#builders/${test.builderId}/builds/${test.number}`}
|
||||
>
|
||||
{test.source}
|
||||
{`bbot: ${test.number}`}
|
||||
</a>
|
||||
</div>
|
||||
<div className='col-sm-3'>
|
||||
{" "}
|
||||
{test.testManData && <a
|
||||
target='_blank'
|
||||
rel='noreferrer noopener'
|
||||
href={`https://reactos.org/testman/compare.php?ids=${test.testManData.id}`}
|
||||
>
|
||||
{`tm: ${test.testManData.id}`}
|
||||
</a>}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
Count: {test.count}
|
||||
{/* previousTests[test.source] returns undefined when PR State === closed */}
|
||||
{ previousTests[test.source] returns undefined when PR State === closed }
|
||||
{previousTests
|
||||
? renderCountChange(test, previousTests[test.source])
|
||||
: null}
|
||||
</div>
|
||||
<div className='col-sm-3'>Failures: {test.failures}</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
<div className='col-sm-3'>Failures: {test.failures}</div>
|
||||
*/
|
||||
|
||||
function TestDetails(props) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{props.tests.length > 0 ? (
|
||||
<div className='row'>
|
||||
<table className="table table-sm table-striped">
|
||||
<tbody>
|
||||
{props.tests.map(test => renderTest(test, props.previousTests))}
|
||||
</div>
|
||||
</tbody>
|
||||
</table>
|
||||
) : (
|
||||
<p>
|
||||
<strong>No data Exists</strong>
|
||||
@ -54,4 +82,13 @@ function TestDetails(props) {
|
||||
);
|
||||
}
|
||||
|
||||
export default TestDetails;
|
||||
function mapStateToProps({ builders }, ownProps) {
|
||||
return {
|
||||
tests: ownProps.tests.map(t => ({
|
||||
...t,
|
||||
testerName: builders[t.builderId] && builders[t.builderId].name
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(TestDetails);
|
||||
|
16
client/src/components/utils.js
Normal file
16
client/src/components/utils.js
Normal file
@ -0,0 +1,16 @@
|
||||
import React from 'react'
|
||||
import { JOB_STATUS } from '../redux/constants'
|
||||
|
||||
|
||||
export function statusElement(status, statusText) {
|
||||
switch(status) {
|
||||
case JOB_STATUS.SUCCESS:
|
||||
return <span className="text-success" title={statusText}><i className="fa fa-check" /></span>
|
||||
case JOB_STATUS.ONGOING:
|
||||
return <span className="text-warning" title={statusText}><i className="fa fa-hourglass" /></span>
|
||||
case JOB_STATUS.FAILURE:
|
||||
return <span className="text-danger" title={statusText}><i className="fa fa-times" /></span>
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ import {
|
||||
BRANCHES,
|
||||
PULLS,
|
||||
BUILD_DATA,
|
||||
TEST_DATA,
|
||||
BUILDERS,
|
||||
TESTMAN_DATA
|
||||
} from '../constants';
|
||||
@ -23,25 +24,46 @@ export const setCommitsError = error => ({
|
||||
error
|
||||
});
|
||||
|
||||
export const loadBuildSets = () => ({
|
||||
// builds
|
||||
|
||||
export const loadBuilds = () => ({
|
||||
type: BUILD_DATA.LOAD
|
||||
});
|
||||
export const setBuilds = build => ({
|
||||
|
||||
export const setBuilds = builds => ({
|
||||
type: BUILD_DATA.LOAD_SUCCESS,
|
||||
build
|
||||
builds
|
||||
});
|
||||
|
||||
export const setBuildSetsError = error => ({
|
||||
export const setBuildsError = error => ({
|
||||
type: BUILD_DATA.LOAD_FAIL,
|
||||
error
|
||||
});
|
||||
|
||||
// tests
|
||||
|
||||
export const loadTests = () => ({
|
||||
type: TEST_DATA.LOAD
|
||||
});
|
||||
|
||||
export const setTests = tests => ({
|
||||
type: TEST_DATA.LOAD_SUCCESS,
|
||||
tests
|
||||
});
|
||||
|
||||
export const setTestsError = error => ({
|
||||
type: TEST_DATA.LOAD_FAIL,
|
||||
error
|
||||
});
|
||||
|
||||
// testman data
|
||||
|
||||
export const loadTestman = () => ({
|
||||
type: TESTMAN_DATA.LOAD
|
||||
});
|
||||
export const setTestman = tests => ({
|
||||
export const setTestman = testmanTests => ({
|
||||
type: TESTMAN_DATA.LOAD_SUCCESS,
|
||||
tests
|
||||
testmanTests
|
||||
});
|
||||
|
||||
export const setTestmanError = error => ({
|
||||
|
@ -4,6 +4,17 @@ export const PULL_STATE = {
|
||||
ALL: 'all',
|
||||
}
|
||||
|
||||
export const JOB_STATUS = {
|
||||
SUCCESS: 0,
|
||||
FAILURE: 1,
|
||||
ONGOING: 2
|
||||
}
|
||||
|
||||
export const BUILDER_TYPE = {
|
||||
BUILDER: 0,
|
||||
TESTER: 1
|
||||
}
|
||||
|
||||
export const COMMITS = {
|
||||
LOAD: 'COMMITS_LOAD',
|
||||
LOAD_SUCCESS: 'COMMITS_LOAD_SUCCESS',
|
||||
@ -28,6 +39,12 @@ export const BUILD_DATA = {
|
||||
LOAD_FAIL: 'BUILD_DATA_LOAD_FAIL'
|
||||
};
|
||||
|
||||
export const TEST_DATA = {
|
||||
LOAD: 'TEST_DATA_LOAD',
|
||||
LOAD_SUCCESS: 'TEST_DATA_LOAD_SUCCESS',
|
||||
LOAD_FAIL: 'TEST_DATA_LOAD_FAIL'
|
||||
}
|
||||
|
||||
export const TESTMAN_DATA = {
|
||||
LOAD: 'TESTMAN_DATA_LOAD',
|
||||
LOAD_SUCCESS: 'TESTMAN_DATA_LOAD_SUCCESS',
|
||||
|
@ -1,10 +0,0 @@
|
||||
//handling builds endpoint
|
||||
import { BUILD_DATA } from '../constants';
|
||||
const buildStatusReducer = (state = {}, action) => {
|
||||
if (action.type === BUILD_DATA.LOAD_SUCCESS) {
|
||||
return { ...action.build };
|
||||
}
|
||||
return state;
|
||||
};
|
||||
|
||||
export default buildStatusReducer;
|
@ -1,10 +1,32 @@
|
||||
import { BUILDERS } from '../constants';
|
||||
import { BUILDERS, BUILDER_TYPE } from '../constants';
|
||||
|
||||
/*
|
||||
Builder object, derived from buildbot
|
||||
{
|
||||
"builders": [
|
||||
{
|
||||
"builderid": 1,
|
||||
"description": null,
|
||||
"masterids": [1],
|
||||
"name": "Build MSVC_x86",
|
||||
"tags": []
|
||||
},
|
||||
<...>
|
||||
]
|
||||
}
|
||||
*/
|
||||
|
||||
const builderReducer = (state = {}, action) => {
|
||||
if (action.type === BUILDERS.LOAD_SUCCESS) {
|
||||
const builders = {};
|
||||
|
||||
for (const builder of action.builders) {
|
||||
builders[builder.builderid] = builder;
|
||||
builders[builder.builderid] = {
|
||||
builderid: builder.builderid,
|
||||
type: builder.name.startsWith("Build") ? BUILDER_TYPE.BUILDER : BUILDER_TYPE.TESTER,
|
||||
name: builder.name.substring(builder.name.indexOf(" ")).trim(),
|
||||
full_name: builder.name
|
||||
}
|
||||
}
|
||||
return builders;
|
||||
}
|
||||
|
35
client/src/redux/reducers/builds.js
Normal file
35
client/src/redux/reducers/builds.js
Normal file
@ -0,0 +1,35 @@
|
||||
import { BUILD_DATA, JOB_STATUS } from '../constants';
|
||||
|
||||
|
||||
export default function buildsReducer(state = {}, action) {
|
||||
if (action.type === BUILD_DATA.LOAD_SUCCESS) {
|
||||
const buildsBySha = {}
|
||||
|
||||
for (let [sha, builds] of Object.entries(action.builds)) {
|
||||
buildsBySha[sha] = builds.map(b => {
|
||||
let isoSuffix, status
|
||||
|
||||
if (b.properties && b.properties.suffix && b.properties.suffix[0]) {
|
||||
isoSuffix = b.properties.suffix[0]
|
||||
}
|
||||
|
||||
if (!b.complete) status = JOB_STATUS.ONGOING
|
||||
else if (b.state_string.includes("success")) status = JOB_STATUS.SUCCESS
|
||||
else status = JOB_STATUS.FAILURE
|
||||
|
||||
return {
|
||||
builderId: b.builderid,
|
||||
buildId: b.buildid,
|
||||
number: b.number,
|
||||
statusText: b.state_string,
|
||||
status,
|
||||
isoSuffix
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return buildsBySha
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
@ -7,8 +7,8 @@ import branchReducer from './branchReducer';
|
||||
import builderReducer from './builderReducer';
|
||||
import pullsReducer from './pullsReducer';
|
||||
import pageReducer from './pageReducer';
|
||||
import buildStatusReducer from './buildStatusReducer';
|
||||
import testmanReducer from './testmanReducer';
|
||||
import builds from './builds';
|
||||
import tests from './tests'
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
isLoading: loadingReducer,
|
||||
@ -18,8 +18,8 @@ const rootReducer = combineReducers({
|
||||
builders: builderReducer,
|
||||
pulls: pullsReducer,
|
||||
page: pageReducer,
|
||||
build: buildStatusReducer,
|
||||
testData: testmanReducer
|
||||
builds,
|
||||
tests
|
||||
});
|
||||
|
||||
export default rootReducer;
|
||||
|
@ -1,20 +0,0 @@
|
||||
import { TESTMAN_DATA } from '../constants';
|
||||
const testmanReducer = (state = {}, action) => {
|
||||
if (action.type === TESTMAN_DATA.LOAD_SUCCESS) {
|
||||
let cleanedTestmanData = {};
|
||||
for (const [sha, tests] of Object.entries(action.tests)) {
|
||||
cleanedTestmanData[sha] = tests.map(t => {
|
||||
let reducedKeyVal = {};
|
||||
for (const [k, v] of Object.entries(t)) {
|
||||
reducedKeyVal[k] = v._text;
|
||||
}
|
||||
return reducedKeyVal;
|
||||
})
|
||||
}
|
||||
return cleanedTestmanData;
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
export default testmanReducer;
|
53
client/src/redux/reducers/tests.js
Normal file
53
client/src/redux/reducers/tests.js
Normal file
@ -0,0 +1,53 @@
|
||||
import { TEST_DATA, TESTMAN_DATA, JOB_STATUS } from '../constants';
|
||||
|
||||
|
||||
export default function testsReducer(state = {}, action) {
|
||||
if (action.type === TEST_DATA.LOAD_SUCCESS) {
|
||||
const newState = {}
|
||||
|
||||
for (let [sha, tests] of Object.entries(action.tests)) {
|
||||
const testsOfSha = {}
|
||||
|
||||
for (let test of tests) {
|
||||
let status
|
||||
|
||||
if (!test.complete) status = JOB_STATUS.ONGOING
|
||||
else if (test.state_string.includes("success")) status = JOB_STATUS.SUCCESS
|
||||
else status = JOB_STATUS.FAILURE
|
||||
|
||||
testsOfSha[test.buildid] = {
|
||||
builderId: test.builderid,
|
||||
buildBotId: test.buildid,
|
||||
number: test.number,
|
||||
statusText: test.state_string,
|
||||
status,
|
||||
testManData: null,
|
||||
parentBuild: test.parentBuild
|
||||
}
|
||||
}
|
||||
|
||||
newState[sha] = testsOfSha
|
||||
}
|
||||
return newState
|
||||
}
|
||||
else if (action.type === TESTMAN_DATA.LOAD_SUCCESS) {
|
||||
const newState = {...state}
|
||||
|
||||
for (let [sha, tests] of Object.entries(action.testmanTests)) {
|
||||
for (let t of tests) {
|
||||
if (newState[sha] && newState[sha][t.buildBotId]) {
|
||||
newState[sha][t.buildBotId].testManData = {
|
||||
id: parseInt(t.id._text),
|
||||
platform: t.platform._text,
|
||||
count: parseInt(t.count._text),
|
||||
failures: parseInt(t.failures._text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newState
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { takeEvery, call, put, select } from 'redux-saga/effects';
|
||||
import { COMMITS } from '../constants';
|
||||
import { COMMITS, BUILDER_TYPE } from '../constants';
|
||||
import { fetchBuildSets, fetchBuildReq, fetchBuilds } from '../api';
|
||||
import { setBuildSetsError, setBuilds } from '../actions';
|
||||
import { setBuildsError, setBuilds, setTests } from '../actions';
|
||||
|
||||
/* These functions are used to build an HTTP query string for
|
||||
* filtering data on BuildBot side.
|
||||
@ -23,7 +23,8 @@ function convertIsoToUnixTime(isoF, isoL) {
|
||||
}
|
||||
|
||||
function getBuildQString(builds) {
|
||||
return builds
|
||||
// besides buildRequestId filtering, we need to get a suffix property from a build
|
||||
return "property=suffix&" + builds
|
||||
.map(build => 'buildrequestid__contains=' + build.buildrequestid)
|
||||
.join('&');
|
||||
}
|
||||
@ -51,12 +52,14 @@ function getBuildReqQString(commits, buildData) {
|
||||
function* handleBuildsLoad() {
|
||||
try {
|
||||
const commits = yield select(state => state.commits);
|
||||
const builders = yield select(state => state.builders)
|
||||
|
||||
const buildSetsRaw = yield call(
|
||||
fetchBuildSets,
|
||||
convertIsoToUnixTime(commits[0], commits[9])
|
||||
);
|
||||
if (buildSetsRaw.length === 0) {
|
||||
yield put(setBuildSetsError('Nothing returned'));
|
||||
yield put(setBuildsError('Nothing returned'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -66,23 +69,38 @@ function* handleBuildsLoad() {
|
||||
);
|
||||
const buildsRaw = yield call(fetchBuilds, getBuildQString(buildReqsRaw));
|
||||
|
||||
// populate builds with buildSetId
|
||||
for (let b of buildsRaw) {
|
||||
b.bsid = buildReqsRaw.find(br => br.buildrequestid === b.buildrequestid).buildsetid
|
||||
}
|
||||
|
||||
const buildsBySha = {};
|
||||
const testsBySha = {}
|
||||
|
||||
for (let { sha } of commits) {
|
||||
const buildSetIds = buildSetsRaw
|
||||
.filter(bs => bs.sourcestamps[0].revision === sha)
|
||||
.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)
|
||||
);
|
||||
buildsBySha[sha] = builds;
|
||||
|
||||
// here we separate "test" builds from "build" builds
|
||||
// because they are processed by different reducers
|
||||
buildsBySha[sha] = buildsRaw.filter(b =>
|
||||
builders[b.builderid].type === BUILDER_TYPE.BUILDER && buildSetIds.includes(b.bsid));
|
||||
|
||||
testsBySha[sha] = buildsRaw.filter(b =>
|
||||
builders[b.builderid].type === BUILDER_TYPE.TESTER && buildSetIds.includes(b.bsid));
|
||||
|
||||
// populate the parent build data
|
||||
for (let t of testsBySha[sha]) {
|
||||
const parentBuildId = buildSetsRaw.find(bs => bs.bsid === t.bsid).parent_buildid
|
||||
t.parentBuild = buildsBySha[sha].find(b => b.buildid === parentBuildId)
|
||||
}
|
||||
}
|
||||
|
||||
yield put(setBuilds(buildsBySha));
|
||||
yield put(setTests(testsBySha));
|
||||
} catch (error) {
|
||||
yield put(setBuildSetsError(error.toString()));
|
||||
yield put(setBuildsError(error.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,16 +1,40 @@
|
||||
import { takeEvery, call, select, put } from 'redux-saga/effects';
|
||||
import { COMMITS } from '../constants';
|
||||
import { TEST_DATA } from '../constants';
|
||||
import { fetchTests } from '../api';
|
||||
import { setTestman, setTestmanError } from '../actions';
|
||||
|
||||
function* handleTestmanLoad() {
|
||||
try {
|
||||
const commits = yield select(state => state.commits);
|
||||
const builders = yield select(state => state.builders)
|
||||
const tests = yield select(state => state.tests)
|
||||
|
||||
let shas = [];
|
||||
if (commits.length > 0) {
|
||||
shas = [commits[0].sha, ...commits.map(commit => commit.parents[0].sha)];
|
||||
}
|
||||
const testResults = yield call(fetchTests, shas[shas.length - 1], shas[0], 1);
|
||||
|
||||
// connecting testman entry with buildbot build by testerName + buildNumber
|
||||
for (let t of testResults) {
|
||||
const buildNumber = parseInt(t.comment._text.match(/Build\s(\d+)/i)[1])
|
||||
const testerName = t.source._text.match(/Build\s.*\son\s(Test.*)/i)[1]
|
||||
const builder = Object.values(builders).find(b => b.full_name === testerName)
|
||||
const builderId = builder.builderid
|
||||
|
||||
for (let shaTests of Object.values(tests)) {
|
||||
for (let test of Object.values(shaTests)) {
|
||||
if (test.number === buildNumber && test.builderId === builderId)
|
||||
{
|
||||
t.buildBotId = test.buildBotId
|
||||
t.testerName = builder.name
|
||||
break
|
||||
}
|
||||
}
|
||||
if (t.buildBotId) break
|
||||
}
|
||||
}
|
||||
|
||||
const testBySha = {};
|
||||
for (let sha of shas) {
|
||||
testBySha[sha] = testResults.filter(test =>
|
||||
@ -24,5 +48,5 @@ function* handleTestmanLoad() {
|
||||
}
|
||||
|
||||
export default function* watchTestmanLoad() {
|
||||
yield takeEvery(COMMITS.LOAD_SUCCESS, handleTestmanLoad);
|
||||
yield takeEvery(TEST_DATA.LOAD_SUCCESS, handleTestmanLoad);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { takeEvery, call, put, select } from 'redux-saga/effects';
|
||||
import { PULLS } from '../constants';
|
||||
import { fetchBuildSets, fetchBuildReq, fetchBuilds } from '../api';
|
||||
import { setBuildSetsError, setBuilds } from '../actions';
|
||||
import { setBuildsError, setBuilds } from '../actions';
|
||||
|
||||
function convertIsoToUnixTime(isoF, isoL) {
|
||||
let unixF = Date.parse(isoF.created_at) / 1000 + 30000;
|
||||
@ -38,7 +38,7 @@ function* handlePullsBuildLoad() {
|
||||
convertIsoToUnixTime(pulls[0], pulls[9])
|
||||
);
|
||||
if (buildSetsRaw.length === 0) {
|
||||
yield put(setBuildSetsError('Nothing returned'));
|
||||
yield put(setBuildsError('Nothing returned'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -69,7 +69,7 @@ function* handlePullsBuildLoad() {
|
||||
}
|
||||
yield put(setBuilds(buildsByPR));
|
||||
} catch (error) {
|
||||
yield put(setBuildSetsError(error.toString()));
|
||||
yield put(setBuildsError(error.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ const rp = require('request-promise');
|
||||
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&field=submitted_at&order=-bsid${str}`,
|
||||
uri: `https://build.reactos.org/api/v2/buildsets?field=parent_buildid&field=bsid&field=sourcestamps&field=submitted_at&order=-bsid${str}`,
|
||||
headers: {
|
||||
'User-Agent': 'Request-Promise'
|
||||
},
|
||||
@ -76,7 +76,7 @@ function builds(str) {
|
||||
router.get('/builds', (req, res) => {
|
||||
let f = req.query.buildrequestid__contains;
|
||||
let queryStr = f.join('&buildrequestid__contains=');
|
||||
queryStr = 'buildrequestid__contains=' + queryStr;
|
||||
queryStr = 'property=suffix&buildrequestid__contains=' + queryStr;
|
||||
rp(builds(queryStr))
|
||||
.then(body => {
|
||||
res.json(body);
|
||||
|
Loading…
Reference in New Issue
Block a user