UI: add pagination to builds, buildsets search
Allow the user to see more than 50 results, and paginate the results. Depends-On: I5a64e16260399062ae341ca6026893d80391c668 Change-Id: I83dff8de93ff015773dd6d83ea406295ad5c48d9
This commit is contained in:
parent
7f4475fcce
commit
38241ad29a
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
REST API calls to the builds and buildsets endpoints now return pagination
|
||||
information (total results and offset). This is an API-breaking change.
|
||||
Implement results pagination in the web UI (buildsets and builds pages).
|
|
@ -100,8 +100,9 @@ class TestMysqlDatabase(BaseTestCase):
|
|||
db.createBuildSet(**buildset_args)
|
||||
|
||||
# Verify that worked using the driver-external interface
|
||||
self.assertEqual(len(self.connection.getBuildsets()), 1)
|
||||
self.assertEqual(self.connection.getBuildsets()[0].uuid, buildset_uuid)
|
||||
results = self.connection.getBuildsets()
|
||||
self.assertEqual(results['total'], 1)
|
||||
self.assertEqual(results['buildsets'][0].uuid, buildset_uuid)
|
||||
|
||||
# Update the buildset using the internal interface
|
||||
with self.connection.getSession() as db:
|
||||
|
|
|
@ -1271,11 +1271,12 @@ class TestBuildInfo(BaseTestWeb):
|
|||
self.waitUntilSettled()
|
||||
|
||||
builds = self.get_url("api/tenant/tenant-one/builds").json()
|
||||
self.assertEqual(len(builds), 6)
|
||||
self.assertEqual(builds['total'], 6)
|
||||
self.assertEqual(len(builds['builds']), 6)
|
||||
|
||||
uuid = builds[0]['uuid']
|
||||
uuid = builds['builds'][0]['uuid']
|
||||
build = self.get_url("api/tenant/tenant-one/build/%s" % uuid).json()
|
||||
self.assertEqual(build['job_name'], builds[0]['job_name'])
|
||||
self.assertEqual(build['job_name'], builds['builds'][0]['job_name'])
|
||||
|
||||
resp = self.get_url("api/tenant/tenant-one/build/1234")
|
||||
self.assertEqual(404, resp.status_code)
|
||||
|
@ -1283,8 +1284,9 @@ class TestBuildInfo(BaseTestWeb):
|
|||
builds_query = self.get_url("api/tenant/tenant-one/builds?"
|
||||
"project=org/project&"
|
||||
"project=org/project1").json()
|
||||
self.assertEqual(len(builds_query), 6)
|
||||
self.assertEqual(builds_query[0]['nodeset'], 'test-nodeset')
|
||||
self.assertEqual(builds_query['total'], 6)
|
||||
self.assertEqual(len(builds_query['builds']), 6)
|
||||
self.assertEqual(builds_query['builds'][0]['nodeset'], 'test-nodeset')
|
||||
|
||||
resp = self.get_url("api/tenant/non-tenant/builds")
|
||||
self.assertEqual(404, resp.status_code)
|
||||
|
@ -1330,8 +1332,10 @@ class TestBuildInfo(BaseTestWeb):
|
|||
self.waitUntilSettled()
|
||||
|
||||
buildsets = self.get_url("api/tenant/tenant-one/buildsets").json()
|
||||
self.assertEqual(2, len(buildsets))
|
||||
project_bs = [x for x in buildsets if x["project"] == "org/project"][0]
|
||||
self.assertEqual(2, buildsets['total'])
|
||||
self.assertEqual(2, len(buildsets['buildsets']))
|
||||
project_bs = [x for x in buildsets['buildsets']
|
||||
if x["project"] == "org/project"][0]
|
||||
|
||||
buildset = self.get_url(
|
||||
"api/tenant/tenant-one/buildset/%s" % project_bs['uuid']).json()
|
||||
|
@ -1370,8 +1374,9 @@ class TestBuildInfo(BaseTestWeb):
|
|||
self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
|
||||
self.waitUntilSettled()
|
||||
builds = self.get_url("api/tenant/tenant-one/builds").json()
|
||||
self.assertTrue(builds['total'] >= 1)
|
||||
self.assertIn('Unable to find playbook',
|
||||
builds[0]['error_detail'])
|
||||
builds['builds'][0]['error_detail'])
|
||||
|
||||
|
||||
class TestArtifacts(BaseTestWeb, AnsibleZuulTestCase):
|
||||
|
@ -1388,11 +1393,12 @@ class TestArtifacts(BaseTestWeb, AnsibleZuulTestCase):
|
|||
build_query = self.get_url("api/tenant/tenant-one/builds?"
|
||||
"project=org/project&"
|
||||
"job_name=project-test1").json()
|
||||
self.assertEqual(len(build_query), 1)
|
||||
self.assertEqual(len(build_query[0]['artifacts']), 3)
|
||||
arts = build_query[0]['artifacts']
|
||||
self.assertEqual(build_query['total'], 1)
|
||||
self.assertEqual(len(build_query['builds']), 1)
|
||||
self.assertEqual(len(build_query['builds'][0]['artifacts']), 3)
|
||||
arts = build_query['builds'][0]['artifacts']
|
||||
arts.sort(key=lambda x: x['name'])
|
||||
self.assertEqual(build_query[0]['artifacts'], [
|
||||
self.assertEqual(build_query['builds'][0]['artifacts'], [
|
||||
{'url': 'http://example.com/docs',
|
||||
'name': 'docs'},
|
||||
{'url': 'http://logs.example.com/build/relative/docs',
|
||||
|
@ -1409,7 +1415,8 @@ class TestArtifacts(BaseTestWeb, AnsibleZuulTestCase):
|
|||
self.waitUntilSettled()
|
||||
|
||||
buildsets = self.get_url("api/tenant/tenant-one/buildsets").json()
|
||||
project_bs = [x for x in buildsets if x["project"] == "org/project"][0]
|
||||
project_bs = [x for x in buildsets['buildsets']
|
||||
if x["project"] == "org/project"][0]
|
||||
buildset = self.get_url(
|
||||
"api/tenant/tenant-one/buildset/%s" % project_bs['uuid']).json()
|
||||
self.assertEqual(3, len(buildset["builds"]))
|
||||
|
@ -2408,8 +2415,9 @@ class TestHeldAttributeInBuildInfo(BaseTestWeb):
|
|||
held_builds_resp.text)
|
||||
all_builds = all_builds_resp.json()
|
||||
held_builds = held_builds_resp.json()
|
||||
self.assertEqual(len(held_builds), 1, all_builds)
|
||||
held_build = held_builds[0]
|
||||
self.assertEqual(held_builds['total'], 1, all_builds)
|
||||
self.assertEqual(len(held_builds['builds']), 1, all_builds)
|
||||
held_build = held_builds['builds'][0]
|
||||
self.assertEqual('project-test2', held_build['job_name'], held_build)
|
||||
self.assertEqual(True, held_build['held'], held_build)
|
||||
|
||||
|
|
|
@ -125,9 +125,18 @@ paths:
|
|||
application/json:
|
||||
schema:
|
||||
description: The list of builds
|
||||
items:
|
||||
$ref: '#/components/schemas/build'
|
||||
type: array
|
||||
properties:
|
||||
total:
|
||||
type: integer
|
||||
description: the total amount of builds matching the search query
|
||||
offset:
|
||||
type: integer
|
||||
description: how many results were omitted for pagination
|
||||
builds:
|
||||
items:
|
||||
$ref: '#/components/schemas/build'
|
||||
type: array
|
||||
type: object
|
||||
description: Returns the list of builds
|
||||
'404':
|
||||
description: Tenant not found
|
||||
|
@ -205,9 +214,18 @@ paths:
|
|||
application/json:
|
||||
schema:
|
||||
description: The list of buildsets
|
||||
items:
|
||||
$ref: '#/components/schemas/buildset'
|
||||
type: array
|
||||
properties:
|
||||
total:
|
||||
type: integer
|
||||
description: the total amount of buildsets matching the search query
|
||||
offset:
|
||||
type: integer
|
||||
description: how many results were omitted for pagination
|
||||
buildsets:
|
||||
items:
|
||||
$ref: '#/components/schemas/buildset'
|
||||
type: array
|
||||
type: object
|
||||
description: Returns the list of builds
|
||||
'404':
|
||||
description: Tenant not found
|
||||
|
|
|
@ -234,7 +234,7 @@ FilterToolbar.propTypes = {
|
|||
|
||||
function getFiltersFromUrl(location, filterCategories) {
|
||||
const urlParams = new URLSearchParams(location.search)
|
||||
const filters = filterCategories.reduce((filterDict, item) => {
|
||||
const _filters = filterCategories.reduce((filterDict, item) => {
|
||||
// Initialize each filter category with an empty list
|
||||
filterDict[item.key] = []
|
||||
|
||||
|
@ -257,6 +257,11 @@ function getFiltersFromUrl(location, filterCategories) {
|
|||
})
|
||||
return filterDict
|
||||
}, {})
|
||||
const pagination_options = {
|
||||
skip: urlParams.getAll('skip') ? urlParams.getAll('skip') : [0,],
|
||||
limit: urlParams.getAll('limit') ? urlParams.getAll('limit') : [50,],
|
||||
}
|
||||
const filters = { ..._filters, ...pagination_options }
|
||||
return filters
|
||||
}
|
||||
|
||||
|
|
|
@ -139,13 +139,13 @@ function BuildTable({
|
|||
.format('YYYY-MM-DD HH:mm:ss'),
|
||||
},
|
||||
{
|
||||
title: (
|
||||
<BuildResult
|
||||
result={build.result}
|
||||
link={`${tenant.linkPrefix}/build/${build.uuid}`}
|
||||
colored={build.voting}
|
||||
/>
|
||||
),
|
||||
title: (
|
||||
<BuildResult
|
||||
result={build.result}
|
||||
link={`${tenant.linkPrefix}/build/${build.uuid}`}
|
||||
colored={build.voting}
|
||||
/>
|
||||
),
|
||||
},
|
||||
],
|
||||
}
|
||||
|
@ -183,7 +183,7 @@ function BuildTable({
|
|||
// fetcihng row.
|
||||
columns[0].dataLabel = ''
|
||||
} else {
|
||||
rows = builds.map((build) => createBuildRow(build))
|
||||
rows = builds.builds.map((build) => createBuildRow(build))
|
||||
// This list of actions will be applied to each row in the table. For
|
||||
// row-specific actions we must evaluate the individual row data provided to
|
||||
// the onClick handler.
|
||||
|
@ -234,7 +234,7 @@ function BuildTable({
|
|||
}
|
||||
|
||||
BuildTable.propTypes = {
|
||||
builds: PropTypes.array.isRequired,
|
||||
builds: PropTypes.object.isRequired,
|
||||
fetching: PropTypes.bool.isRequired,
|
||||
onClearFilters: PropTypes.func.isRequired,
|
||||
tenant: PropTypes.object.isRequired,
|
||||
|
|
|
@ -101,13 +101,13 @@ function BuildsetTable({
|
|||
title: changeOrRefLink && changeOrRefLink,
|
||||
},
|
||||
{
|
||||
title: (
|
||||
<BuildResult
|
||||
result={buildset.result}
|
||||
link={`${tenant.linkPrefix}/buildset/${buildset.uuid}`}
|
||||
>
|
||||
</BuildResult>
|
||||
),
|
||||
title: (
|
||||
<BuildResult
|
||||
result={buildset.result}
|
||||
link={`${tenant.linkPrefix}/buildset/${buildset.uuid}`}
|
||||
>
|
||||
</BuildResult>
|
||||
),
|
||||
},
|
||||
],
|
||||
}
|
||||
|
@ -145,7 +145,7 @@ function BuildsetTable({
|
|||
// the fetching row.
|
||||
columns[0].dataLabel = ''
|
||||
} else {
|
||||
rows = buildsets.map((buildset) => createBuildsetRow(buildset))
|
||||
rows = buildsets.buildsets.map((buildset) => createBuildsetRow(buildset))
|
||||
// This list of actions will be applied to each row in the table. For
|
||||
// row-specific actions we must evaluate the individual row data provided to
|
||||
// the onClick handler.
|
||||
|
@ -196,7 +196,7 @@ function BuildsetTable({
|
|||
}
|
||||
|
||||
BuildsetTable.propTypes = {
|
||||
buildsets: PropTypes.array.isRequired,
|
||||
buildsets: PropTypes.object.isRequired,
|
||||
fetching: PropTypes.bool.isRequired,
|
||||
onClearFilters: PropTypes.func.isRequired,
|
||||
tenant: PropTypes.object.isRequired,
|
||||
|
|
|
@ -16,7 +16,7 @@ import * as React from 'react'
|
|||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import 'moment-duration-format'
|
||||
import { PageSection, PageSectionVariants } from '@patternfly/react-core'
|
||||
import { PageSection, PageSectionVariants, Pagination } from '@patternfly/react-core'
|
||||
|
||||
import { fetchBuilds } from '../api'
|
||||
import {
|
||||
|
@ -117,13 +117,27 @@ class BuildsPage extends React.Component {
|
|||
},
|
||||
]
|
||||
|
||||
const _filters = getFiltersFromUrl(props.location, this.filterCategories)
|
||||
const perPage = _filters.limit[0]
|
||||
? parseInt(_filters.limit[0])
|
||||
: 50
|
||||
const currentPage = _filters.skip[0]
|
||||
? Math.floor(parseInt(_filters.skip[0] / perPage)) + 1
|
||||
: 1
|
||||
|
||||
this.state = {
|
||||
builds: [],
|
||||
builds: {
|
||||
builds: [],
|
||||
offset: null,
|
||||
total: null,
|
||||
},
|
||||
fetching: false,
|
||||
filters: getFiltersFromUrl(props.location, this.filterCategories),
|
||||
filters: _filters,
|
||||
projectsFetched: false,
|
||||
pipelinesFetched: false,
|
||||
jobsFetched: false,
|
||||
resultsPerPage: perPage,
|
||||
currentPage: currentPage,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -187,6 +201,7 @@ class BuildsPage extends React.Component {
|
|||
this.setState({
|
||||
builds: response.data,
|
||||
fetching: false,
|
||||
currentPage: Math.floor(response.data.offset / this.state.resultsPerPage) + 1,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -231,9 +246,25 @@ class BuildsPage extends React.Component {
|
|||
this.handleFilterChange(filters)
|
||||
}
|
||||
|
||||
handlePerPageSelect = (event, perPage) => {
|
||||
const { filters } = this.state
|
||||
this.setState({ resultsPerPage: perPage })
|
||||
const newFilters = { ...filters, limit: [perPage,] }
|
||||
this.handleFilterChange(newFilters)
|
||||
}
|
||||
|
||||
handleSetPage = (event, pageNumber) => {
|
||||
const { filters, resultsPerPage } = this.state
|
||||
this.setState({ currentPage: pageNumber })
|
||||
let offset = resultsPerPage * (pageNumber - 1)
|
||||
const newFilters = { ...filters, skip: [offset,] }
|
||||
this.handleFilterChange(newFilters)
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
const { history } = this.props
|
||||
const { builds, fetching, filters } = this.state
|
||||
const { builds, fetching, filters, resultsPerPage, currentPage } = this.state
|
||||
return (
|
||||
<PageSection variant={PageSectionVariants.light}>
|
||||
<FilterToolbar
|
||||
|
@ -241,6 +272,14 @@ class BuildsPage extends React.Component {
|
|||
onFilterChange={this.handleFilterChange}
|
||||
filters={filters}
|
||||
/>
|
||||
<Pagination
|
||||
itemCount={builds.total}
|
||||
perPage={resultsPerPage}
|
||||
page={currentPage}
|
||||
widgetId="pagination-menu"
|
||||
onPerPageSelect={this.handlePerPageSelect}
|
||||
onSetPage={this.handleSetPage}
|
||||
/>
|
||||
<BuildTable
|
||||
builds={builds}
|
||||
fetching={fetching}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
import * as React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import { PageSection, PageSectionVariants } from '@patternfly/react-core'
|
||||
import { PageSection, PageSectionVariants, Pagination } from '@patternfly/react-core'
|
||||
|
||||
import { fetchBuildsets } from '../api'
|
||||
import {
|
||||
|
@ -82,12 +82,26 @@ class BuildsetsPage extends React.Component {
|
|||
},
|
||||
]
|
||||
|
||||
const _filters = getFiltersFromUrl(props.location, this.filterCategories)
|
||||
const perPage = _filters.limit[0]
|
||||
? parseInt(_filters.limit[0])
|
||||
: 50
|
||||
const currentPage = _filters.skip[0]
|
||||
? Math.floor(parseInt(_filters.skip[0] / perPage)) + 1
|
||||
: 1
|
||||
|
||||
this.state = {
|
||||
buildsets: [],
|
||||
buildsets: {
|
||||
buildsets: [],
|
||||
offset: null,
|
||||
total: null,
|
||||
},
|
||||
fetching: false,
|
||||
filters: getFiltersFromUrl(props.location, this.filterCategories),
|
||||
filters: _filters,
|
||||
projectsFetched: false,
|
||||
pipelinesFetched: false,
|
||||
resultsPerPage: perPage,
|
||||
currentPage: currentPage,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,6 +149,7 @@ class BuildsetsPage extends React.Component {
|
|||
this.setState({
|
||||
buildsets: response.data,
|
||||
fetching: false,
|
||||
currentPage: Math.floor(response.data.offset / this.state.resultsPerPage) + 1,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
@ -177,9 +192,24 @@ class BuildsetsPage extends React.Component {
|
|||
this.handleFilterChange(filters)
|
||||
}
|
||||
|
||||
handlePerPageSelect = (event, perPage) => {
|
||||
const { filters } = this.state
|
||||
this.setState({ resultsPerPage: perPage })
|
||||
const newFilters = { ...filters, limit: [perPage,] }
|
||||
this.handleFilterChange(newFilters)
|
||||
}
|
||||
|
||||
handleSetPage = (event, pageNumber) => {
|
||||
const { filters, resultsPerPage } = this.state
|
||||
this.setState({ currentPage: pageNumber })
|
||||
const offset = resultsPerPage * (pageNumber - 1)
|
||||
const newFilters = { ...filters, skip: [offset,] }
|
||||
this.handleFilterChange(newFilters)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { history } = this.props
|
||||
const { buildsets, fetching, filters } = this.state
|
||||
const { buildsets, fetching, filters, resultsPerPage, currentPage } = this.state
|
||||
return (
|
||||
<PageSection variant={PageSectionVariants.light}>
|
||||
<FilterToolbar
|
||||
|
@ -187,6 +217,14 @@ class BuildsetsPage extends React.Component {
|
|||
onFilterChange={this.handleFilterChange}
|
||||
filters={filters}
|
||||
/>
|
||||
<Pagination
|
||||
itemCount={buildsets.total}
|
||||
perPage={resultsPerPage}
|
||||
page={currentPage}
|
||||
widgetId="pagination-menu"
|
||||
onPerPageSelect={this.handlePerPageSelect}
|
||||
onSetPage={this.handleSetPage}
|
||||
/>
|
||||
<BuildsetTable
|
||||
buildsets={buildsets}
|
||||
fetching={fetching}
|
||||
|
|
|
@ -111,14 +111,31 @@ class DatabaseSession(object):
|
|||
q = self.listFilter(q, provides_table.c.name, provides)
|
||||
q = self.listFilter(q, build_table.c.held, held)
|
||||
|
||||
try:
|
||||
total = q.count()
|
||||
except sqlalchemy.orm.exc.NoResultFound:
|
||||
return {
|
||||
'total': 0,
|
||||
'offset': offset,
|
||||
'builds': []
|
||||
}
|
||||
|
||||
q = q.order_by(build_table.c.id.desc()).\
|
||||
limit(limit).\
|
||||
offset(offset)
|
||||
|
||||
try:
|
||||
return q.all()
|
||||
return {
|
||||
'total': total,
|
||||
'offset': offset,
|
||||
'builds': q.all()
|
||||
}
|
||||
except sqlalchemy.orm.exc.NoResultFound:
|
||||
return []
|
||||
return {
|
||||
'total': 0,
|
||||
'offset': offset,
|
||||
'builds': []
|
||||
}
|
||||
|
||||
def getBuild(self, tenant, uuid):
|
||||
build_table = self.connection.zuul_build_table
|
||||
|
@ -177,14 +194,31 @@ class DatabaseSession(object):
|
|||
elif complete is False:
|
||||
q = q.filter(buildset_table.c.result == None) # noqa
|
||||
|
||||
try:
|
||||
total = q.count()
|
||||
except sqlalchemy.orm.exc.NoResultFound:
|
||||
return {
|
||||
'total': 0,
|
||||
'offset': offset,
|
||||
'buildsets': []
|
||||
}
|
||||
|
||||
q = q.order_by(buildset_table.c.id.desc()).\
|
||||
limit(limit).\
|
||||
offset(offset)
|
||||
|
||||
try:
|
||||
return q.all()
|
||||
return {
|
||||
'total': total,
|
||||
'offset': offset,
|
||||
'buildsets': q.all()
|
||||
}
|
||||
except sqlalchemy.orm.exc.NoResultFound:
|
||||
return []
|
||||
return {
|
||||
'total': 0,
|
||||
'offset': offset,
|
||||
'buildsets': []
|
||||
}
|
||||
|
||||
def getBuildset(self, tenant, uuid):
|
||||
"""Get one buildset with its builds"""
|
||||
|
|
|
@ -2747,7 +2747,7 @@ class QueueItem(object):
|
|||
if requirements_tuple not in self._cached_sql_results:
|
||||
conn = self.pipeline.manager.sched.connections.getSqlConnection()
|
||||
if conn:
|
||||
builds = conn.getBuilds(
|
||||
_builds = conn.getBuilds(
|
||||
tenant=self.pipeline.tenant.name,
|
||||
project=self.change.project.name,
|
||||
pipeline=self.pipeline.name,
|
||||
|
@ -2755,6 +2755,7 @@ class QueueItem(object):
|
|||
branch=self.change.branch,
|
||||
patchset=self.change.patchset,
|
||||
provides=requirements_tuple)
|
||||
builds = _builds['builds']
|
||||
else:
|
||||
builds = []
|
||||
# Just look at the most recent buildset.
|
||||
|
|
|
@ -1004,7 +1004,12 @@ class ZuulWebAPI(object):
|
|||
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
return [self.buildToDict(b, b.buildset) for b in builds]
|
||||
return {
|
||||
'total': builds['total'],
|
||||
'offset': builds['offset'],
|
||||
'builds': [self.buildToDict(b, b.buildset)
|
||||
for b in builds['builds']]
|
||||
}
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.save_params()
|
||||
|
@ -1013,9 +1018,11 @@ class ZuulWebAPI(object):
|
|||
connection = self._get_connection()
|
||||
|
||||
data = connection.getBuilds(tenant=tenant, uuid=uuid, limit=1)
|
||||
if not data:
|
||||
if data['total'] == 0:
|
||||
raise cherrypy.HTTPError(404, "Build not found")
|
||||
data = self.buildToDict(data[0], data[0].buildset)
|
||||
build = data['builds'][0]
|
||||
data = self.buildToDict(build,
|
||||
build.buildset)
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
return data
|
||||
|
@ -1049,10 +1056,10 @@ class ZuulWebAPI(object):
|
|||
buildsets = connection.getBuildsets(
|
||||
tenant=tenant, project=project, pipeline=pipeline,
|
||||
branch=branch, complete=True, limit=1)
|
||||
if not buildsets:
|
||||
if buildsets['total'] == 0:
|
||||
raise cherrypy.HTTPError(404, 'No buildset found')
|
||||
|
||||
if buildsets[0].result == 'SUCCESS':
|
||||
if buildsets['buildsets'][0].result == 'SUCCESS':
|
||||
file = 'passing.svg'
|
||||
else:
|
||||
file = 'failing.svg'
|
||||
|
@ -1083,7 +1090,12 @@ class ZuulWebAPI(object):
|
|||
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
return [self.buildsetToDict(b) for b in buildsets]
|
||||
return {
|
||||
'total': buildsets['total'],
|
||||
'offset': buildsets['offset'],
|
||||
'buildsets': [self.buildsetToDict(b)
|
||||
for b in buildsets['buildsets']]
|
||||
}
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.save_params()
|
||||
|
|
Loading…
Reference in New Issue