diff --git a/runbot_merge/controllers/__init__.py b/runbot_merge/controllers/__init__.py index a2f6c196..c37b3d3f 100644 --- a/runbot_merge/controllers/__init__.py +++ b/runbot_merge/controllers/__init__.py @@ -8,6 +8,7 @@ from datetime import datetime, timedelta from typing import Callable import sentry_sdk +from werkzeug.exceptions import NotFound, UnprocessableEntity from odoo.api import Environment from odoo.http import Controller, request, route, Response @@ -18,40 +19,56 @@ from .. import utils, github _logger = logging.getLogger(__name__) +def staging_dict(staging): + return { + 'id': staging.id, + 'pull_requests': staging.batch_ids.prs.mapped(lambda p: { + 'name': p.display_name, + 'repository': p.repository.name, + 'number': p.number, + }), + 'merged': staging.commit_ids.mapped('sha'), + 'staged': staging.head_ids.mapped('sha'), + } + class MergebotController(Controller): - @route('/runbot_merge/stagings', auth='none', type='json') - def stagings_for_commits(self, commits=None, heads=None): - Stagings = request.env(user=1)['runbot_merge.stagings'].sudo() - if commits: - stagings = Stagings.for_commits(*commits) - elif heads: - stagings = Stagings.for_heads(*heads) + @route('/runbot_merge/stagings', auth='none', type='http', methods=['GET']) + def stagings_for_commits(self, **kw): + commits = request.httprequest.args.getlist('merged') + heads = request.httprequest.args.getlist('staged') + if bool(commits) ^ bool(heads): + Stagings = request.env(user=1)['runbot_merge.stagings'].sudo() + if commits: + stagings = Stagings.for_commits(*commits) + else: + stagings = Stagings.for_heads(*heads) + + # can pass `?state=` to get stagings in any state + if state := request.httprequest.args.get('state', 'success'): + stagings = stagings.filtered(lambda s: s.state == state) + return request.make_json_response(stagings.mapped(staging_dict)) else: - raise ValueError('Must receive one of "commits" or "heads" kwarg') + raise UnprocessableEntity("Must receive either `merged` or `staged` query parameters (can receive multiple of either)") - return stagings.ids - - @route('/runbot_merge/stagings/', auth='none', type='json') + @route('/runbot_merge/stagings/', auth='none', type='http', methods=['GET']) def prs_for_staging(self, staging): - staging = request.env(user=1)['runbot_merge.stagings'].browse(staging) - return [ - batch.prs.mapped(lambda p: { - 'name': p.display_name, - 'repository': p.repository.name, - 'number': p.number, - }) - for batch in staging.sudo().batch_ids - ] + staging = request.env(user=1)['runbot_merge.stagings'].browse(staging).sudo() + if not staging.exists: + raise NotFound() - @route('/runbot_merge/stagings//', auth='none', type='json') - def prs_for_stagings(self, from_staging, to_staging, include_from=True, include_to=True): + return request.make_json_response(staging_dict(staging)) + + @route('/runbot_merge/stagings//', auth='none', type='http', methods=['GET']) + def prs_for_stagings(self, from_staging, to_staging, include_from='1', include_to='1'): Stagings = request.env(user=1, context={"active_test": False})['runbot_merge.stagings'] from_staging = Stagings.browse(from_staging) to_staging = Stagings.browse(to_staging) + if not (from_staging.exists() and to_staging.exists()): + raise NotFound() if from_staging.target != to_staging.target: - raise ValueError(f"Stagings must have the same target branch, found {from_staging.target.name} and {to_staging.target.name}") + raise UnprocessableEntity(f"Stagings must have the same target branch, found {from_staging.target.name} and {to_staging.target.name}") if from_staging.id >= to_staging.id: - raise ValueError("first staging must be older than second staging") + raise UnprocessableEntity("first staging must be older than second staging") stagings = Stagings.search([ ('target', '=', to_staging.target.id), @@ -60,20 +77,7 @@ class MergebotController(Controller): ('id', '<=' if include_to else '<', to_staging.id), ], order="id asc") - return [ - { - 'staging': staging.id, - 'prs': [ - batch.prs.mapped(lambda p: { - 'name': p.display_name, - 'repository': p.repository.name, - 'number': p.number, - }) - for batch in staging.batch_ids - ] - } - for staging in stagings - ] + return request.make_json_response([staging_dict(staging) for staging in stagings]) @route('/runbot_merge/hooks', auth='none', type='http', csrf=False, methods=['POST']) def index(self) -> Response: