diff --git a/src/api/handlers/build.py b/src/api/handlers/build.py index 011310fb7..7d12fce24 100644 --- a/src/api/handlers/build.py +++ b/src/api/handlers/build.py @@ -19,10 +19,10 @@ @ns.route('/') @api.doc(responses={403: 'Not Authorized'}) class Builds(Resource): - @api.marshal_list_with(build_model) def get(self, project_id): ''' - Returns builds + Returns build summaries with aggregated state, dates, commit and PR info. + Accepts: from, to, sha, branch, cronjob, state, size/build_limit ''' build_from = request.args.get('from', None) @@ -30,7 +30,8 @@ def get(self, project_id): sha = request.args.get('sha', None) branch = request.args.get('branch', None) cronjob = request.args.get('cronjob', None) - size = request.args.get('size', 10) + state = request.args.get('state', None) + size = request.args.get('build_limit', request.args.get('size', 10)) if cronjob == "true": cronjob = True @@ -62,21 +63,57 @@ def get(self, project_id): if not build_from: build_from = 0 - #if build_to - build_from > 500: - # build_from = max(build_to - 500, 0) - - p = g.db.execute_many_dict(''' - SELECT b.id, b.build_number, b.restart_counter, b.is_cronjob + rows = g.db.execute_many_dict(''' + SELECT + b.id, + b.build_number, + b.restart_counter, + b.is_cronjob, + CASE + WHEN bool_or(j.state IN ('queued', 'scheduled', 'running') + AND j.restarted IS NOT TRUE) THEN 'running' + WHEN bool_or(j.state = 'killed' AND j.restarted IS NOT TRUE) THEN 'killed' + WHEN bool_or(j.state = 'error' AND j.restarted IS NOT TRUE) THEN 'error' + WHEN bool_or(j.state = 'failure' AND j.restarted IS NOT TRUE) THEN 'failure' + WHEN bool_or(j.state = 'unstable' AND j.restarted IS NOT TRUE) THEN 'unstable' + ELSE 'finished' + END AS state, + to_char(min(j.start_date), 'YYYY-MM-DD HH24:MI:SS') AS start_date, + to_char(max(j.end_date), 'YYYY-MM-DD HH24:MI:SS') AS end_date, + c.id AS commit_id, + c.branch AS commit_branch, + c.author_name AS commit_author_name, + c.tag AS commit_tag, + c.url AS commit_url, + su.filename AS source_upload_filename, + pr.title AS pull_request_title, + pr.url AS pull_request_url FROM build b - LEFT OUTER JOIN commit c - ON b.commit_id = c.id + INNER JOIN job j ON j.build_id = b.id + LEFT JOIN commit c ON b.commit_id = c.id + LEFT JOIN source_upload su ON b.source_upload_id = su.id + LEFT JOIN pull_request pr ON c.pull_request_id = pr.id WHERE b.project_id = %(pid)s AND b.build_number < %(to)s AND b.build_number >= %(from)s - AND (%(sha)s IS NULL OR c.id = %(sha)s) - AND (%(branch)s IS NULL OR c.branch = %(branch)s) + AND (%(sha)s IS NULL OR c.id = %(sha)s) + AND (%(branch)s IS NULL OR c.branch = %(branch)s) AND (%(cronjob)s IS NULL OR b.is_cronjob = %(cronjob)s) - ORDER BY build_number DESC, restart_counter DESC + GROUP BY b.id, b.build_number, b.restart_counter, b.is_cronjob, + c.id, c.branch, c.author_name, c.tag, c.url, + su.filename, pr.title, pr.url + HAVING (%(state)s IS NULL OR + CASE + WHEN bool_or(j.state IN ('queued', 'scheduled', 'running') + AND j.restarted IS NOT TRUE) THEN 'running' + WHEN bool_or(j.state = 'killed' AND j.restarted IS NOT TRUE) THEN 'killed' + WHEN bool_or(j.state = 'error' AND j.restarted IS NOT TRUE) THEN 'error' + WHEN bool_or(j.state = 'failure' AND j.restarted IS NOT TRUE) THEN 'failure' + WHEN bool_or(j.state = 'unstable' AND j.restarted IS NOT TRUE) THEN 'unstable' + ELSE 'finished' + END = %(state)s + ) + ORDER BY b.build_number DESC, b.restart_counter DESC LIMIT %(size)s ''', { 'pid': project_id, @@ -85,10 +122,41 @@ def get(self, project_id): 'sha': sha, 'branch': branch, 'cronjob': cronjob, + 'state': state, 'size': size, }) - return p + result = [] + for b in rows: + o = { + 'id': b['id'], + 'build_number': b['build_number'], + 'restart_counter': b['restart_counter'], + 'is_cronjob': b['is_cronjob'], + 'state': b['state'], + 'start_date': b['start_date'], + 'end_date': b['end_date'], + 'commit': None, + 'source_upload': None, + 'pull_request': None, + } + if b['commit_id']: + o['commit'] = { + 'id': b['commit_id'], + 'branch': b['commit_branch'], + 'author_name': b['commit_author_name'], + 'tag': b['commit_tag'], + 'url': b['commit_url'], + } + if b['source_upload_filename']: + o['source_upload'] = {'filename': b['source_upload_filename']} + if b['pull_request_title']: + o['pull_request'] = { + 'title': b['pull_request_title'], + 'url': b['pull_request_url'], + } + result.append(o) + return result @ns.route('/') @api.doc(responses={403: 'Not Authorized'}) diff --git a/src/dashboard-client/src/models/Project.js b/src/dashboard-client/src/models/Project.js index 02a3ec7b7..ce5e2de39 100644 --- a/src/dashboard-client/src/models/Project.js +++ b/src/dashboard-client/src/models/Project.js @@ -56,7 +56,7 @@ export default class Project { } loadBuilds (from, to, sha, branch, cronjob, buildLimit) { - let url = `projects/${this.id}/jobs/?from=${from}&to=${to}` + let url = `projects/${this.id}/builds/?from=${from}&to=${to}` if (sha) { url += `&sha=${sha}` @@ -75,8 +75,8 @@ export default class Project { } return NewAPIService.get(url) - .then((jobs) => { - this._addJobs(jobs) + .then((builds) => { + store.commit('addBuilds', { projectId: this.id, builds }) }) .catch((err) => { NotificationService.$emit('NOTIFICATION', new Notification(err)) @@ -86,7 +86,7 @@ export default class Project { getBuild (number, restartCounter) { const b = this._getBuild(number, restartCounter) - if (b) { + if (b && b.jobs.length > 0) { return new Promise((resolve) => { resolve(b) }) } @@ -178,9 +178,9 @@ export default class Project { } _loadJobs () { - return NewAPIService.get(`projects/${this.id}/jobs/`) - .then((response) => { - store.commit('addJobs', response) + return NewAPIService.get(`projects/${this.id}/builds/`) + .then((builds) => { + store.commit('addBuilds', { projectId: this.id, builds }) events.listenJobs(this) }) } diff --git a/src/dashboard-client/src/store.js b/src/dashboard-client/src/store.js index c0b8ba423..6ebcdb4a0 100644 --- a/src/dashboard-client/src/store.js +++ b/src/dashboard-client/src/store.js @@ -170,6 +170,40 @@ function handleJobUpdate (state, event) { project._updateState() } +function addBuilds (state, payload) { + const project = findProject(state, payload.projectId) + if (!project) return + + for (const b of payload.builds) { + let build = findBuild(project, b.id) + if (!build) { + build = new Build( + b.id, b.build_number, b.restart_counter, b.is_cronjob, + b.commit || null, b.pull_request || null, project + ) + let builds = [build] + for (let ex of project.builds) { + builds.push(ex) + } + builds = _(builds) + .chain() + .sortBy((x) => x.restartCounter) + .sortBy((x) => x.number) + .value() + .reverse() + project.builds = builds + } + build.state = b.state + build.startDate = b.start_date ? toDate(b.start_date) : null + build.endDate = b.end_date ? toDate(b.end_date) : null + if (b.commit) build.commit = b.commit + if (b.pull_request) build.pull_request = b.pull_request + } + if (project.builds.length > 0) { + project._updateState() + } +} + function addProjects (state, projects) { for (const project of projects) { let p = findProject(state, project.id) @@ -353,6 +387,7 @@ function setAdminGlobalTokens (state, tokens) { const mutations = { addProjects, addJobs, + addBuilds, setSecrets, setCronJobs, setSSHKeys,