[IMP] mergebot: staging endpoints

- use lingo `staged` and `merged` for clarity (instead of `heads` and
  `commits`)
- convert from json-rpc to regular HTTP to make easier to call
- add the staged and merged heads when returning staging data
- return more complete objects (rather than a list of ids) when
  matching stagings based on commits / heads

Fixes #1018
This commit is contained in:
Xavier Morel 2025-01-14 15:01:13 +01:00
parent 89411f968f
commit 7d5c3dcb73

View File

@ -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/<int:staging>', auth='none', type='json')
@route('/runbot_merge/stagings/<int:staging>', 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/<int:from_staging>/<int:to_staging>', 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/<int:from_staging>/<int:to_staging>', 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: