summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tests/unit/test_web.py39
-rw-r--r--web/src/containers/FilterToolbar.jsx7
-rw-r--r--web/src/containers/build/BuildList.jsx32
-rw-r--r--zuul/driver/sql/sqlconnection.py12
-rw-r--r--zuul/manager/__init__.py8
-rw-r--r--zuul/model.py21
-rwxr-xr-xzuul/web/__init__.py6
7 files changed, 112 insertions, 13 deletions
diff --git a/tests/unit/test_web.py b/tests/unit/test_web.py
index 14df6f96a..02a1d76d1 100644
--- a/tests/unit/test_web.py
+++ b/tests/unit/test_web.py
@@ -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()
diff --git a/web/src/containers/FilterToolbar.jsx b/web/src/containers/FilterToolbar.jsx
index 9568997cc..4cc1e3f25 100644
--- a/web/src/containers/FilterToolbar.jsx
+++ b/web/src/containers/FilterToolbar.jsx
@@ -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
}
diff --git a/web/src/containers/build/BuildList.jsx b/web/src/containers/build/BuildList.jsx
index 69f1795bf..ccb734679 100644
--- a/web/src/containers/build/BuildList.jsx
+++ b/web/src/containers/build/BuildList.jsx
@@ -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 &nbsp;</span>
+ <Switch
+ isChecked={showSkipped}
+ onChange={this.handleSkippedSwitch}
+ isReversed
+ />
+ </FlexItem> :
+ <></>
+
const sortedBuilds = this.sortedBuilds()
return (
<Flex direction={{ default: 'column' }}>
+ {skippedSwitch}
{retrySwitch}
<FlexItem>
<DataList
diff --git a/zuul/driver/sql/sqlconnection.py b/zuul/driver/sql/sqlconnection.py
index 1af50b2f7..8ab528c39 100644
--- a/zuul/driver/sql/sqlconnection.py
+++ b/zuul/driver/sql/sqlconnection.py
@@ -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
diff --git a/zuul/manager/__init__.py b/zuul/manager/__init__.py
index a1d906fe9..1e5d21eea 100644
--- a/zuul/manager/__init__.py
+++ b/zuul/manager/__init__.py
@@ -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(
diff --git a/zuul/model.py b/zuul/model.py
index 605984b56..65140a4ae 100644
--- a/zuul/model.py
+++ b/zuul/model.py
@@ -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)
diff --git a/zuul/web/__init__.py b/zuul/web/__init__.py
index 09706057b..9e684b3f6 100755
--- a/zuul/web/__init__.py
+++ b/zuul/web/__init__.py
@@ -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'] = '*'