feat: ask a teammate to review a ticket

«Pedir revisión» on a ticket asks a member (with an optional note): they
are notified and see it under «Te piden revisión» in my work until they
answer «Todo bien» or «Pedir cambios» (with what to change) from the
ticket's Revisiones box; whoever asked hears the answer and can withdraw
an open request. Events of the ticket (requested, answered, cancelled),
so they show in its history and the activity feed. Viewers can be asked
and answer; not oneself, nor twice the same person with one pending.

API: requestReview / answerReview / cancelReview, Issue.reviews,
myIssues(reviewing); POST /api/issues/{issue}/reviews,
POST …/reviews/{id}/answer, DELETE …/reviews/{id}. MCP: request_review,
answer_review, my_issues reviewing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-20 20:38:30 +02:00
parent 172c53d21d
commit 93d028e870
53 changed files with 2836 additions and 54 deletions

View File

@@ -578,6 +578,60 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/issues/{issue}/reviews": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Ask a member to review the ticket
* @description Whoever can change the ticket asks; the reviewer is told and sees it in GET /api/me/issues?reviewing=true until they answer.
*/
post: operations["IssuesController_requestReview"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/issues/{issue}/reviews/{review}/answer": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Answer a review I was asked for (all good, or changes) */
post: operations["IssuesController_answerReview"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/issues/{issue}/reviews/{review}": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
post?: never;
/** Withdraw a review request I made */
delete: operations["IssuesController_cancelReview"];
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/issues/{issue}/follow-tree": {
parameters: {
query?: never;
@@ -2421,6 +2475,25 @@ export interface components {
*/
commentId: string | null;
};
/**
* @description Null while waiting for an answer
* @enum {string}
*/
ReviewVerdict: "APPROVED" | "CHANGES";
IssueReviewDto: {
/** Format: uuid */
id: string;
requester: components["schemas"]["UserRefDto"];
reviewer: components["schemas"]["UserRefDto"];
note: string | null;
/** Format: date-time */
requestedAt: string;
/** @description Null while waiting for an answer */
verdict: components["schemas"]["ReviewVerdict"] | null;
answer: string | null;
/** Format: date-time */
answeredAt: string | null;
};
IssueParentDto: {
/** Format: uuid */
id: string;
@@ -2520,6 +2593,8 @@ export interface components {
attachments: components["schemas"]["AttachmentDto"][];
/** @description People following it */
watchers: components["schemas"]["UserRefDto"][];
/** @description Reviews asked, open and answered, oldest first */
reviews: components["schemas"]["IssueReviewDto"][];
/** @description The epic or ticket this one breaks down */
parent: components["schemas"]["IssueParentDto"] | null;
/** @description Open tickets it waits for: the ones that block it and the ones it depends on */
@@ -3017,6 +3092,16 @@ export interface components {
/** @description What to remember */
note?: string;
};
RequestReviewDto: {
/** @description Reviewer's user id (a member of the project) */
reviewer: string;
/** @description What to look at */
note?: string;
};
AnswerReviewDto: {
verdict: components["schemas"]["ReviewVerdict"];
answer?: string;
};
IssuePageDto: {
items: components["schemas"]["IssueDto"][];
/** @description Total matches (for pagination) */
@@ -3493,7 +3578,7 @@ export interface components {
path: string | null;
};
/** @enum {string} */
NotificationKind: "ASSIGNED" | "STATUS_CHANGED" | "QA_FAILED" | "COMMENTED" | "MENTIONED" | "REPLIED" | "PROPOSED" | "WATCHED" | "ADDED_TO_PROJECT" | "REMINDER";
NotificationKind: "ASSIGNED" | "STATUS_CHANGED" | "QA_FAILED" | "COMMENTED" | "MENTIONED" | "REPLIED" | "PROPOSED" | "WATCHED" | "ADDED_TO_PROJECT" | "REMINDER" | "REVIEW_REQUESTED" | "REVIEW_ANSWERED";
NotificationDto: {
id: string;
kind: components["schemas"]["NotificationKind"];
@@ -6278,6 +6363,244 @@ export interface operations {
};
};
};
IssuesController_requestReview: {
parameters: {
query?: never;
header?: never;
path: {
/** @description Ticket id or key */
issue: string;
};
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["RequestReviewDto"];
};
};
responses: {
201: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["CommandResultDto"];
};
};
/** @description Invalid input */
400: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiErrorDto"];
};
};
/** @description Missing or rejected Pireon token */
401: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiErrorDto"];
};
};
/** @description Not a member of the project (or not allowed) */
403: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiErrorDto"];
};
};
/** @description Not found */
404: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiErrorDto"];
};
};
/** @description Someone changed it at the same time: retry */
409: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiErrorDto"];
};
};
/** @description Business rule (e.g. a transition the workflow forbids) */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiErrorDto"];
};
};
};
};
IssuesController_answerReview: {
parameters: {
query?: never;
header?: never;
path: {
/** @description Ticket id or key */
issue: string;
review: string;
};
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["AnswerReviewDto"];
};
};
responses: {
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["CommandResultDto"];
};
};
/** @description Invalid input */
400: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiErrorDto"];
};
};
/** @description Missing or rejected Pireon token */
401: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiErrorDto"];
};
};
/** @description Not a member of the project (or not allowed) */
403: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiErrorDto"];
};
};
/** @description Not found */
404: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiErrorDto"];
};
};
/** @description Someone changed it at the same time: retry */
409: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiErrorDto"];
};
};
/** @description Business rule (e.g. a transition the workflow forbids) */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiErrorDto"];
};
};
};
};
IssuesController_cancelReview: {
parameters: {
query?: never;
header?: never;
path: {
/** @description Ticket id or key */
issue: string;
review: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["CommandResultDto"];
};
};
/** @description Invalid input */
400: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiErrorDto"];
};
};
/** @description Missing or rejected Pireon token */
401: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiErrorDto"];
};
};
/** @description Not a member of the project (or not allowed) */
403: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiErrorDto"];
};
};
/** @description Not found */
404: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiErrorDto"];
};
};
/** @description Someone changed it at the same time: retry */
409: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiErrorDto"];
};
};
/** @description Business rule (e.g. a transition the workflow forbids) */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ApiErrorDto"];
};
};
};
};
IssuesController_followTree: {
parameters: {
query?: never;
@@ -6587,6 +6910,8 @@ export interface operations {
assignedToMe?: boolean;
/** @description Only the tickets I follow (with assignedToMe=false, whoever they are assigned to) */
watching?: boolean;
/** @description Only the tickets I was asked to review and have not answered */
reviewing?: boolean;
/** @description Status ids. Repeat the parameter or separate with commas */
status?: string[];
/** @description Whatever each project calls its statuses */