Merge "Include skipped builds in database and web ui"
This commit is contained in:
commit
b70d8de85b
|
@ -1783,6 +1783,45 @@ class TestBuildInfo(BaseTestWeb):
|
||||||
"idx_min=%i" % idx_max).json()
|
"idx_min=%i" % idx_max).json()
|
||||||
self.assertEqual(len(builds_query), 1, builds_query)
|
self.assertEqual(len(builds_query), 1, builds_query)
|
||||||
|
|
||||||
|
def test_web_list_skipped_builds(self):
|
||||||
|
# Test the exclude_result filter
|
||||||
|
# Generate some build records in the db.
|
||||||
|
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
|
||||||
|
self.executor_server.failJob('project-merge', A)
|
||||||
|
A.addApproval('Code-Review', 2)
|
||||||
|
self.fake_gerrit.addEvent(A.addApproval('Approved', 1))
|
||||||
|
self.waitUntilSettled()
|
||||||
|
self.executor_server.hold_jobs_in_build = False
|
||||||
|
self.executor_server.release()
|
||||||
|
self.waitUntilSettled()
|
||||||
|
|
||||||
|
builds = self.get_url("api/tenant/tenant-one/builds").json()
|
||||||
|
builds.sort(key=lambda x: x['job_name'])
|
||||||
|
self.assertEqual(len(builds), 3)
|
||||||
|
self.assertEqual(builds[0]['job_name'], 'project-merge')
|
||||||
|
self.assertEqual(builds[1]['job_name'], 'project-test1')
|
||||||
|
self.assertEqual(builds[2]['job_name'], 'project-test2')
|
||||||
|
self.assertEqual(builds[0]['result'], 'FAILURE')
|
||||||
|
self.assertEqual(builds[1]['result'], 'SKIPPED')
|
||||||
|
self.assertEqual(builds[2]['result'], 'SKIPPED')
|
||||||
|
|
||||||
|
builds = self.get_url("api/tenant/tenant-one/builds?"
|
||||||
|
"exclude_result=SKIPPED").json()
|
||||||
|
self.assertEqual(len(builds), 1)
|
||||||
|
self.assertEqual(builds[0]['job_name'], 'project-merge')
|
||||||
|
self.assertEqual(builds[0]['result'], 'FAILURE')
|
||||||
|
|
||||||
|
builds = self.get_url("api/tenant/tenant-one/builds?"
|
||||||
|
"result=SKIPPED&result=FAILURE").json()
|
||||||
|
builds.sort(key=lambda x: x['job_name'])
|
||||||
|
self.assertEqual(len(builds), 3)
|
||||||
|
self.assertEqual(builds[0]['job_name'], 'project-merge')
|
||||||
|
self.assertEqual(builds[1]['job_name'], 'project-test1')
|
||||||
|
self.assertEqual(builds[2]['job_name'], 'project-test2')
|
||||||
|
self.assertEqual(builds[0]['result'], 'FAILURE')
|
||||||
|
self.assertEqual(builds[1]['result'], 'SKIPPED')
|
||||||
|
self.assertEqual(builds[2]['result'], 'SKIPPED')
|
||||||
|
|
||||||
def test_web_badge(self):
|
def test_web_badge(self):
|
||||||
# Generate some build records in the db.
|
# Generate some build records in the db.
|
||||||
self.add_base_changes()
|
self.add_base_changes()
|
||||||
|
|
|
@ -319,14 +319,21 @@ function writeFiltersToUrl(filters, location, history) {
|
||||||
|
|
||||||
function buildQueryString(filters) {
|
function buildQueryString(filters) {
|
||||||
let queryString = '&complete=true'
|
let queryString = '&complete=true'
|
||||||
|
let resultFilter = false
|
||||||
if (filters) {
|
if (filters) {
|
||||||
Object.keys(filters).map((key) => {
|
Object.keys(filters).map((key) => {
|
||||||
filters[key].forEach((value) => {
|
filters[key].forEach((value) => {
|
||||||
|
if (value === 'result') {
|
||||||
|
resultFilter = true
|
||||||
|
}
|
||||||
queryString += '&' + key + '=' + value
|
queryString += '&' + key + '=' + value
|
||||||
})
|
})
|
||||||
return queryString
|
return queryString
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if (!resultFilter) {
|
||||||
|
queryString += '&exclude_result=SKIPPED'
|
||||||
|
}
|
||||||
return queryString
|
return queryString
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,15 +57,24 @@ class BuildList extends React.Component {
|
||||||
return self.indexOf(build) === idx
|
return self.indexOf(build) === idx
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let skippedJobs = builds.filter((build) => {
|
||||||
|
return build.result === 'SKIPPED'
|
||||||
|
}).map((build) => (build.job_name)
|
||||||
|
).filter((build, idx, self) => {
|
||||||
|
return self.indexOf(build) === idx
|
||||||
|
})
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
visibleNonFinalBuilds: retriedJobs,
|
visibleNonFinalBuilds: retriedJobs,
|
||||||
retriedJobs: retriedJobs,
|
retriedJobs: retriedJobs,
|
||||||
|
skippedJobs: skippedJobs,
|
||||||
|
showSkipped: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sortedBuilds = () => {
|
sortedBuilds = () => {
|
||||||
const { builds } = this.props
|
const { builds } = this.props
|
||||||
const { visibleNonFinalBuilds } = this.state
|
const { visibleNonFinalBuilds, showSkipped } = this.state
|
||||||
|
|
||||||
return builds.sort((a, b) => {
|
return builds.sort((a, b) => {
|
||||||
// Group builds by job name, then order by decreasing start time; this will ensure retries are together
|
// Group builds by job name, then order by decreasing start time; this will ensure retries are together
|
||||||
|
@ -85,6 +94,9 @@ class BuildList extends React.Component {
|
||||||
}
|
}
|
||||||
}).filter((build) => {
|
}).filter((build) => {
|
||||||
if (build.final || visibleNonFinalBuilds.indexOf(build.job_name) >= 0) {
|
if (build.final || visibleNonFinalBuilds.indexOf(build.job_name) >= 0) {
|
||||||
|
if (build.result === 'SKIPPED' && !showSkipped) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -98,6 +110,10 @@ class BuildList extends React.Component {
|
||||||
this.setState({ visibleNonFinalBuilds: (isChecked ? retriedJobs : []) })
|
this.setState({ visibleNonFinalBuilds: (isChecked ? retriedJobs : []) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleSkippedSwitch = isChecked => {
|
||||||
|
this.setState({ showSkipped: isChecked })
|
||||||
|
}
|
||||||
|
|
||||||
handleToggleVisibleNonFinalBuilds = (jobName) => {
|
handleToggleVisibleNonFinalBuilds = (jobName) => {
|
||||||
const { visibleNonFinalBuilds } = this.state
|
const { visibleNonFinalBuilds } = this.state
|
||||||
const index = visibleNonFinalBuilds.indexOf(jobName)
|
const index = visibleNonFinalBuilds.indexOf(jobName)
|
||||||
|
@ -138,7 +154,7 @@ class BuildList extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { tenant } = this.props
|
const { tenant } = this.props
|
||||||
const { visibleNonFinalBuilds, retriedJobs } = this.state
|
const { visibleNonFinalBuilds, retriedJobs, skippedJobs, showSkipped } = this.state
|
||||||
|
|
||||||
let retrySwitch = retriedJobs.length > 0 ?
|
let retrySwitch = retriedJobs.length > 0 ?
|
||||||
<FlexItem align={{ default: 'alignRight' }}>
|
<FlexItem align={{ default: 'alignRight' }}>
|
||||||
|
@ -151,10 +167,22 @@ class BuildList extends React.Component {
|
||||||
</FlexItem> :
|
</FlexItem> :
|
||||||
<></>
|
<></>
|
||||||
|
|
||||||
|
let skippedSwitch = skippedJobs.length > 0 ?
|
||||||
|
<FlexItem align={{ default: 'alignRight' }}>
|
||||||
|
<span>Show skipped jobs </span>
|
||||||
|
<Switch
|
||||||
|
isChecked={showSkipped}
|
||||||
|
onChange={this.handleSkippedSwitch}
|
||||||
|
isReversed
|
||||||
|
/>
|
||||||
|
</FlexItem> :
|
||||||
|
<></>
|
||||||
|
|
||||||
const sortedBuilds = this.sortedBuilds()
|
const sortedBuilds = this.sortedBuilds()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex direction={{ default: 'column' }}>
|
<Flex direction={{ default: 'column' }}>
|
||||||
|
{skippedSwitch}
|
||||||
{retrySwitch}
|
{retrySwitch}
|
||||||
<FlexItem>
|
<FlexItem>
|
||||||
<DataList
|
<DataList
|
||||||
|
|
|
@ -59,6 +59,14 @@ class DatabaseSession(object):
|
||||||
return query.filter(column.in_(value))
|
return query.filter(column.in_(value))
|
||||||
return query.filter(column == value)
|
return query.filter(column == value)
|
||||||
|
|
||||||
|
def exListFilter(self, query, column, value):
|
||||||
|
# Exclude values in list
|
||||||
|
if value is None:
|
||||||
|
return query
|
||||||
|
if isinstance(value, list) or isinstance(value, tuple):
|
||||||
|
return query.filter(column.not_in(value))
|
||||||
|
return query.filter(column != value)
|
||||||
|
|
||||||
def getBuilds(self, tenant=None, project=None, pipeline=None,
|
def getBuilds(self, tenant=None, project=None, pipeline=None,
|
||||||
change=None, branch=None, patchset=None, ref=None,
|
change=None, branch=None, patchset=None, ref=None,
|
||||||
newrev=None, event_id=None, event_timestamp=None,
|
newrev=None, event_id=None, event_timestamp=None,
|
||||||
|
@ -66,7 +74,8 @@ class DatabaseSession(object):
|
||||||
uuid=None, job_name=None, voting=None, nodeset=None,
|
uuid=None, job_name=None, voting=None, nodeset=None,
|
||||||
result=None, provides=None, final=None, held=None,
|
result=None, provides=None, final=None, held=None,
|
||||||
complete=None, sort_by_buildset=False, limit=50,
|
complete=None, sort_by_buildset=False, limit=50,
|
||||||
offset=0, idx_min=None, idx_max=None):
|
offset=0, idx_min=None, idx_max=None,
|
||||||
|
exclude_result=None):
|
||||||
|
|
||||||
build_table = self.connection.zuul_build_table
|
build_table = self.connection.zuul_build_table
|
||||||
buildset_table = self.connection.zuul_buildset_table
|
buildset_table = self.connection.zuul_buildset_table
|
||||||
|
@ -114,6 +123,7 @@ class DatabaseSession(object):
|
||||||
q = self.listFilter(q, build_table.c.voting, voting)
|
q = self.listFilter(q, build_table.c.voting, voting)
|
||||||
q = self.listFilter(q, build_table.c.nodeset, nodeset)
|
q = self.listFilter(q, build_table.c.nodeset, nodeset)
|
||||||
q = self.listFilter(q, build_table.c.result, result)
|
q = self.listFilter(q, build_table.c.result, result)
|
||||||
|
q = self.exListFilter(q, build_table.c.result, exclude_result)
|
||||||
q = self.listFilter(q, build_table.c.final, final)
|
q = self.listFilter(q, build_table.c.final, final)
|
||||||
if complete is True:
|
if complete is True:
|
||||||
q = q.filter(build_table.c.result != None) # noqa
|
q = q.filter(build_table.c.result != None) # noqa
|
||||||
|
|
|
@ -1967,13 +1967,7 @@ class PipelineManager(metaclass=ABCMeta):
|
||||||
build_set.jobNodeRequestComplete(request.job_name, nodeset)
|
build_set.jobNodeRequestComplete(request.job_name, nodeset)
|
||||||
# Put a fake build through the cycle to clean it up.
|
# Put a fake build through the cycle to clean it up.
|
||||||
if not request.fulfilled:
|
if not request.fulfilled:
|
||||||
fakebuild = build_set.item.setNodeRequestFailure(job)
|
build_set.item.setNodeRequestFailure(job)
|
||||||
try:
|
|
||||||
self.sql.reportBuildEnd(
|
|
||||||
fakebuild, tenant=build_set.item.pipeline.tenant.name,
|
|
||||||
final=True)
|
|
||||||
except Exception:
|
|
||||||
log.exception("Error reporting build completion to DB:")
|
|
||||||
self._resumeBuilds(build_set)
|
self._resumeBuilds(build_set)
|
||||||
tenant = build_set.item.pipeline.tenant
|
tenant = build_set.item.pipeline.tenant
|
||||||
tenant.semaphore_handler.release(
|
tenant.semaphore_handler.release(
|
||||||
|
|
|
@ -4929,6 +4929,10 @@ class QueueItem(zkobject.ZKObject):
|
||||||
job=job, build_set=self.current_build_set,
|
job=job, build_set=self.current_build_set,
|
||||||
result='FAILURE')
|
result='FAILURE')
|
||||||
self.addBuild(fakebuild)
|
self.addBuild(fakebuild)
|
||||||
|
self.pipeline.manager.sql.reportBuildEnd(
|
||||||
|
fakebuild,
|
||||||
|
tenant=self.pipeline.tenant.name,
|
||||||
|
final=True)
|
||||||
self.setResult(fakebuild)
|
self.setResult(fakebuild)
|
||||||
ret = False
|
ret = False
|
||||||
return ret
|
return ret
|
||||||
|
@ -5262,6 +5266,10 @@ class QueueItem(zkobject.ZKObject):
|
||||||
build_set=self.current_build_set,
|
build_set=self.current_build_set,
|
||||||
result='SKIPPED')
|
result='SKIPPED')
|
||||||
self.addBuild(fakebuild)
|
self.addBuild(fakebuild)
|
||||||
|
self.pipeline.manager.sql.reportBuildEnd(
|
||||||
|
fakebuild,
|
||||||
|
tenant=self.pipeline.tenant.name,
|
||||||
|
final=True)
|
||||||
|
|
||||||
def setNodeRequestFailure(self, job):
|
def setNodeRequestFailure(self, job):
|
||||||
fakebuild = Build.new(
|
fakebuild = Build.new(
|
||||||
|
@ -5273,8 +5281,11 @@ class QueueItem(zkobject.ZKObject):
|
||||||
result='NODE_FAILURE',
|
result='NODE_FAILURE',
|
||||||
)
|
)
|
||||||
self.addBuild(fakebuild)
|
self.addBuild(fakebuild)
|
||||||
|
self.pipeline.manager.sql.reportBuildEnd(
|
||||||
|
fakebuild,
|
||||||
|
tenant=self.pipeline.tenant.name,
|
||||||
|
final=True)
|
||||||
self.setResult(fakebuild)
|
self.setResult(fakebuild)
|
||||||
return fakebuild
|
|
||||||
|
|
||||||
def setDequeuedNeedingChange(self, msg):
|
def setDequeuedNeedingChange(self, msg):
|
||||||
self.updateAttributes(
|
self.updateAttributes(
|
||||||
|
@ -5330,6 +5341,10 @@ class QueueItem(zkobject.ZKObject):
|
||||||
job=job, build_set=self.current_build_set,
|
job=job, build_set=self.current_build_set,
|
||||||
result='SKIPPED')
|
result='SKIPPED')
|
||||||
self.addBuild(fakebuild)
|
self.addBuild(fakebuild)
|
||||||
|
self.pipeline.manager.sql.reportBuildEnd(
|
||||||
|
fakebuild,
|
||||||
|
tenant=self.pipeline.tenant.name,
|
||||||
|
final=True)
|
||||||
|
|
||||||
def _setMissingJobsSkipped(self):
|
def _setMissingJobsSkipped(self):
|
||||||
for job in self.getJobs():
|
for job in self.getJobs():
|
||||||
|
@ -5340,6 +5355,10 @@ class QueueItem(zkobject.ZKObject):
|
||||||
job=job, build_set=self.current_build_set,
|
job=job, build_set=self.current_build_set,
|
||||||
result='SKIPPED')
|
result='SKIPPED')
|
||||||
self.addBuild(fakebuild)
|
self.addBuild(fakebuild)
|
||||||
|
self.pipeline.manager.sql.reportBuildEnd(
|
||||||
|
fakebuild,
|
||||||
|
tenant=self.pipeline.tenant.name,
|
||||||
|
final=True)
|
||||||
|
|
||||||
def getNodePriority(self):
|
def getNodePriority(self):
|
||||||
return self.pipeline.manager.getNodePriority(self)
|
return self.pipeline.manager.getNodePriority(self)
|
||||||
|
|
|
@ -1399,7 +1399,8 @@ class ZuulWebAPI(object):
|
||||||
branch=None, patchset=None, ref=None, newrev=None,
|
branch=None, patchset=None, ref=None, newrev=None,
|
||||||
uuid=None, job_name=None, voting=None, nodeset=None,
|
uuid=None, job_name=None, voting=None, nodeset=None,
|
||||||
result=None, final=None, held=None, complete=None,
|
result=None, final=None, held=None, complete=None,
|
||||||
limit=50, skip=0, idx_min=None, idx_max=None):
|
limit=50, skip=0, idx_min=None, idx_max=None,
|
||||||
|
exclude_result=None):
|
||||||
connection = self._get_connection()
|
connection = self._get_connection()
|
||||||
|
|
||||||
if tenant not in self.zuulweb.abide.tenants.keys():
|
if tenant not in self.zuulweb.abide.tenants.keys():
|
||||||
|
@ -1423,7 +1424,8 @@ class ZuulWebAPI(object):
|
||||||
branch=branch, patchset=patchset, ref=ref, newrev=newrev,
|
branch=branch, patchset=patchset, ref=ref, newrev=newrev,
|
||||||
uuid=uuid, job_name=job_name, voting=voting, nodeset=nodeset,
|
uuid=uuid, job_name=job_name, voting=voting, nodeset=nodeset,
|
||||||
result=result, final=final, held=held, complete=complete,
|
result=result, final=final, held=held, complete=complete,
|
||||||
limit=limit, offset=skip, idx_min=_idx_min, idx_max=_idx_max)
|
limit=limit, offset=skip, idx_min=_idx_min, idx_max=_idx_max,
|
||||||
|
exclude_result=exclude_result)
|
||||||
|
|
||||||
resp = cherrypy.response
|
resp = cherrypy.response
|
||||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||||
|
|
Loading…
Reference in New Issue