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()
|
||||
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):
|
||||
# Generate some build records in the db.
|
||||
self.add_base_changes()
|
||||
|
|
|
@ -319,14 +319,21 @@ function writeFiltersToUrl(filters, location, history) {
|
|||
|
||||
function buildQueryString(filters) {
|
||||
let queryString = '&complete=true'
|
||||
let resultFilter = false
|
||||
if (filters) {
|
||||
Object.keys(filters).map((key) => {
|
||||
filters[key].forEach((value) => {
|
||||
if (value === 'result') {
|
||||
resultFilter = true
|
||||
}
|
||||
queryString += '&' + key + '=' + value
|
||||
})
|
||||
return queryString
|
||||
})
|
||||
}
|
||||
if (!resultFilter) {
|
||||
queryString += '&exclude_result=SKIPPED'
|
||||
}
|
||||
return queryString
|
||||
}
|
||||
|
||||
|
|
|
@ -57,15 +57,24 @@ class BuildList extends React.Component {
|
|||
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 = {
|
||||
visibleNonFinalBuilds: retriedJobs,
|
||||
retriedJobs: retriedJobs,
|
||||
skippedJobs: skippedJobs,
|
||||
showSkipped: false,
|
||||
}
|
||||
}
|
||||
|
||||
sortedBuilds = () => {
|
||||
const { builds } = this.props
|
||||
const { visibleNonFinalBuilds } = this.state
|
||||
const { visibleNonFinalBuilds, showSkipped } = this.state
|
||||
|
||||
return builds.sort((a, b) => {
|
||||
// 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) => {
|
||||
if (build.final || visibleNonFinalBuilds.indexOf(build.job_name) >= 0) {
|
||||
if (build.result === 'SKIPPED' && !showSkipped) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
else {
|
||||
|
@ -98,6 +110,10 @@ class BuildList extends React.Component {
|
|||
this.setState({ visibleNonFinalBuilds: (isChecked ? retriedJobs : []) })
|
||||
}
|
||||
|
||||
handleSkippedSwitch = isChecked => {
|
||||
this.setState({ showSkipped: isChecked })
|
||||
}
|
||||
|
||||
handleToggleVisibleNonFinalBuilds = (jobName) => {
|
||||
const { visibleNonFinalBuilds } = this.state
|
||||
const index = visibleNonFinalBuilds.indexOf(jobName)
|
||||
|
@ -138,7 +154,7 @@ class BuildList extends React.Component {
|
|||
|
||||
render() {
|
||||
const { tenant } = this.props
|
||||
const { visibleNonFinalBuilds, retriedJobs } = this.state
|
||||
const { visibleNonFinalBuilds, retriedJobs, skippedJobs, showSkipped } = this.state
|
||||
|
||||
let retrySwitch = retriedJobs.length > 0 ?
|
||||
<FlexItem align={{ default: 'alignRight' }}>
|
||||
|
@ -151,10 +167,22 @@ class BuildList extends React.Component {
|
|||
</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()
|
||||
|
||||
return (
|
||||
<Flex direction={{ default: 'column' }}>
|
||||
{skippedSwitch}
|
||||
{retrySwitch}
|
||||
<FlexItem>
|
||||
<DataList
|
||||
|
|
|
@ -59,6 +59,14 @@ class DatabaseSession(object):
|
|||
return query.filter(column.in_(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,
|
||||
change=None, branch=None, patchset=None, ref=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,
|
||||
result=None, provides=None, final=None, held=None,
|
||||
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
|
||||
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.nodeset, nodeset)
|
||||
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)
|
||||
if complete is True:
|
||||
q = q.filter(build_table.c.result != None) # noqa
|
||||
|
|
|
@ -1967,13 +1967,7 @@ class PipelineManager(metaclass=ABCMeta):
|
|||
build_set.jobNodeRequestComplete(request.job_name, nodeset)
|
||||
# Put a fake build through the cycle to clean it up.
|
||||
if not request.fulfilled:
|
||||
fakebuild = 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:")
|
||||
build_set.item.setNodeRequestFailure(job)
|
||||
self._resumeBuilds(build_set)
|
||||
tenant = build_set.item.pipeline.tenant
|
||||
tenant.semaphore_handler.release(
|
||||
|
|
|
@ -4929,6 +4929,10 @@ class QueueItem(zkobject.ZKObject):
|
|||
job=job, build_set=self.current_build_set,
|
||||
result='FAILURE')
|
||||
self.addBuild(fakebuild)
|
||||
self.pipeline.manager.sql.reportBuildEnd(
|
||||
fakebuild,
|
||||
tenant=self.pipeline.tenant.name,
|
||||
final=True)
|
||||
self.setResult(fakebuild)
|
||||
ret = False
|
||||
return ret
|
||||
|
@ -5262,6 +5266,10 @@ class QueueItem(zkobject.ZKObject):
|
|||
build_set=self.current_build_set,
|
||||
result='SKIPPED')
|
||||
self.addBuild(fakebuild)
|
||||
self.pipeline.manager.sql.reportBuildEnd(
|
||||
fakebuild,
|
||||
tenant=self.pipeline.tenant.name,
|
||||
final=True)
|
||||
|
||||
def setNodeRequestFailure(self, job):
|
||||
fakebuild = Build.new(
|
||||
|
@ -5273,8 +5281,11 @@ class QueueItem(zkobject.ZKObject):
|
|||
result='NODE_FAILURE',
|
||||
)
|
||||
self.addBuild(fakebuild)
|
||||
self.pipeline.manager.sql.reportBuildEnd(
|
||||
fakebuild,
|
||||
tenant=self.pipeline.tenant.name,
|
||||
final=True)
|
||||
self.setResult(fakebuild)
|
||||
return fakebuild
|
||||
|
||||
def setDequeuedNeedingChange(self, msg):
|
||||
self.updateAttributes(
|
||||
|
@ -5330,6 +5341,10 @@ class QueueItem(zkobject.ZKObject):
|
|||
job=job, build_set=self.current_build_set,
|
||||
result='SKIPPED')
|
||||
self.addBuild(fakebuild)
|
||||
self.pipeline.manager.sql.reportBuildEnd(
|
||||
fakebuild,
|
||||
tenant=self.pipeline.tenant.name,
|
||||
final=True)
|
||||
|
||||
def _setMissingJobsSkipped(self):
|
||||
for job in self.getJobs():
|
||||
|
@ -5340,6 +5355,10 @@ class QueueItem(zkobject.ZKObject):
|
|||
job=job, build_set=self.current_build_set,
|
||||
result='SKIPPED')
|
||||
self.addBuild(fakebuild)
|
||||
self.pipeline.manager.sql.reportBuildEnd(
|
||||
fakebuild,
|
||||
tenant=self.pipeline.tenant.name,
|
||||
final=True)
|
||||
|
||||
def getNodePriority(self):
|
||||
return self.pipeline.manager.getNodePriority(self)
|
||||
|
|
|
@ -1399,7 +1399,8 @@ class ZuulWebAPI(object):
|
|||
branch=None, patchset=None, ref=None, newrev=None,
|
||||
uuid=None, job_name=None, voting=None, nodeset=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()
|
||||
|
||||
if tenant not in self.zuulweb.abide.tenants.keys():
|
||||
|
@ -1423,7 +1424,8 @@ class ZuulWebAPI(object):
|
|||
branch=branch, patchset=patchset, ref=ref, newrev=newrev,
|
||||
uuid=uuid, job_name=job_name, voting=voting, nodeset=nodeset,
|
||||
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.headers['Access-Control-Allow-Origin'] = '*'
|
||||
|
|
Loading…
Reference in New Issue