feat(web): filter GitLab merge requests by source branch

This commit is contained in:
2026-08-16 16:23:36 +00:00
parent 6868a58359
commit 267db3fb9d
6 changed files with 73 additions and 4 deletions
@@ -74,7 +74,7 @@ Nothing in the client or repo layers assumes the token came from a PAT.
- Issue list: `GET /projects/:id/issues?state=opened&scope=all&per_page=50&page=N&search=<query>`.
- Issue detail: `GET /projects/:id/issues/:issue_iid`.
- Issue notes: `GET /projects/:id/issues/:issue_iid/notes?per_page=100` (system notes are skipped; each note links as `{issue_web_url}#note_{id}`).
- MR list: `GET /projects/:id/merge_requests?state=opened&scope=all&per_page=50&page=N&search=<query>`.
- MR list: `GET /projects/:id/merge_requests?state=opened&scope=all&per_page=50&page=N&search=<query>&source_branch=<branch>` (the route passes `sourceBranch` through to `source_branch` when present, matching local-branch MR-status UIs).
- MR detail: `GET /projects/:id/merge_requests/:merge_request_iid`.
- MR diffs: `GET /projects/:id/merge_requests/:merge_request_iid/diffs?per_page=100&page=N` (paginated; the route caps at 10 pages / 3000 files).
- MR notes: `GET /projects/:id/merge_requests/:merge_request_iid/notes?per_page=100`.
@@ -93,7 +93,7 @@ Nothing in the client or repo layers assumes the token came from a PAT.
| GET | `/api/gitlab/issues/list` | `?directory&page&query` -> `{ connected, repo?, issues[], page, hasMore }` |
| GET | `/api/gitlab/issues/get` | `?directory&number&namespace&project` -> `{ connected, repo?, issue }` |
| GET | `/api/gitlab/issues/comments` | `?directory&number&namespace&project` -> `{ connected, repo?, comments[] }` |
| GET | `/api/gitlab/mrs/list` | `?directory&page&query` -> `{ connected, repo?, mrs[], page, hasMore }` |
| GET | `/api/gitlab/mrs/list` | `?directory&page&query&sourceBranch` -> `{ connected, repo?, mrs[], page, hasMore }` |
| GET | `/api/gitlab/mrs/context` | `?directory&number&diff&namespace&project` -> `{ connected, repo?, mr, comments[], files[], diff? }` |
| GET | `/api/gitlab/repo/branches` | `?namespace&project` -> `{ branches[] }` |
+4
View File
@@ -507,6 +507,7 @@ export function registerGitLabRoutes(app, options = {}) {
const rawPage = typeof req.query?.page === 'string' ? Number(req.query.page) : 1;
const effectivePage = Number.isFinite(rawPage) && rawPage > 0 ? rawPage : 1;
const searchQuery = asString(req.query?.query);
const sourceBranch = asString(req.query?.sourceBranch);
const client = await getClient();
if (!client) {
@@ -522,6 +523,9 @@ export function registerGitLabRoutes(app, options = {}) {
if (searchQuery) {
params.search = searchQuery;
}
if (sourceBranch) {
params.source_branch = sourceBranch;
}
const resp = await withTimeout(client.mergeRequests(projectPath, params), ROUTE_TIMEOUT_MS, 'gitlab mrs list');
if (resp.status === 429) {
return res.status(503).json({ error: 'GitLab rate limited' });
@@ -396,6 +396,28 @@ describe('GitLab data routes', () => {
});
});
test('mrs/list passes the source branch filter to the GitLab API', async () => {
const fetchMock = scriptedFetch([(url) => (matches(/\/merge_requests\?/)(url) ? jsonResponse([]) : null)]);
const app = createApp();
await request(app).get('/api/gitlab/mrs/list?directory=%2Ftmp%2Fwork&sourceBranch=feat%2Fapi');
const requestedUrl = String(fetchMock.mock.calls[0][0]);
expect(requestedUrl).toContain('state=opened');
expect(requestedUrl).toContain('source_branch=feat%2Fapi');
expect(requestedUrl).toContain('per_page=50');
});
test('mrs/list omits the source branch filter when not provided', async () => {
const fetchMock = scriptedFetch([(url) => (matches(/\/merge_requests\?/)(url) ? jsonResponse([]) : null)]);
const app = createApp();
await request(app).get('/api/gitlab/mrs/list?directory=%2Ftmp%2Fwork');
const requestedUrl = String(fetchMock.mock.calls[0][0]);
expect(requestedUrl).not.toContain('source_branch');
});
test('mrs/context returns mr, comments, files, and a concatenated diff', async () => {
scriptedFetch([
(url) => (matches(/\/merge_requests\/9$/)(url)