feat: formatted text, a list view, ticket details, public proposals and votes
Formatted text
- Descriptions, comments, replies and release notes render Markdown,
sanitised with DOMPurify (tested against scripts, handlers and
javascript: links). A pasted image is uploaded and written as
; the web fetches it with the reader's session,
so it works on private projects. Every text box has a preview. The
attachment upload now answers its attachmentId.
List view
- /p/:key/list: every ticket as rows, sortable by any column, filtered by
text, open/closed, type, priority and assignee; status, priority and
assignee change in place; bulk edit (the backlog's bar, now shared).
Ticket details
- Reactions on comments (GitHub's eight), for members and viewers.
- Duplicate a ticket: what it says, not where it stands.
- blockedBy: open tickets it waits for (BLOCKS / DEPENDS_ON), worked out
when reading with one query per board or page; shown on cards, list
rows and the ticket.
Public proposals
- A public project can take bug reports and ideas from anyone
(acceptsProposals): POST /api/explore/projects/{project}/proposals and
the form at /p/:key/proponer. They become tickets labelled «propuesta»,
opened by TaskStream; a honeypot field and 5 per address every 15
minutes keep spam out. No e-mail is asked: it would be public.
Votes
- Anyone signed in votes on a public project's tickets, members on a
private one (voteIssue, PUT/DELETE /api/issues/{issue}/vote): votes and
voted on each ticket, buttons on the ticket and the roadmap, a column
in the list. The showcase carries votes and the open proposals.
MCP: vote_issue, duplicate_issue, react_to_comment; listings show
blockers and votes. Docs, Word and PDF updated; chat notifications
(Discord, Slack, Telegram) noted for later.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3324,6 +3324,309 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/issues/{issue}/vote": {
|
||||
"put": {
|
||||
"description": "Anyone signed in, on a public project (that is the point: the people it is for say what they want); members on a private one. Voting twice changes nothing.",
|
||||
"operationId": "IssuesController_vote",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "issue",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Ticket id or key",
|
||||
"schema": {
|
||||
"example": "WEB-12",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CommandResultDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid input",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Missing or rejected Pireon token",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Not a member of the project (or not allowed)",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "Someone changed it at the same time: retry",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Business rule (e.g. a transition the workflow forbids)",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"pireon-oauth": []
|
||||
},
|
||||
{
|
||||
"pireon": []
|
||||
}
|
||||
],
|
||||
"summary": "Vote for the ticket",
|
||||
"tags": [
|
||||
"Issues"
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"operationId": "IssuesController_unvote",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "issue",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Ticket id or key",
|
||||
"schema": {
|
||||
"example": "WEB-12",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CommandResultDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid input",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Missing or rejected Pireon token",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Not a member of the project (or not allowed)",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "Someone changed it at the same time: retry",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Business rule (e.g. a transition the workflow forbids)",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"pireon-oauth": []
|
||||
},
|
||||
{
|
||||
"pireon": []
|
||||
}
|
||||
],
|
||||
"summary": "Take back my vote for the ticket",
|
||||
"tags": [
|
||||
"Issues"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/issues/{issue}/duplicate": {
|
||||
"post": {
|
||||
"description": "Copies what it says (type, title, description, area, priority, labels, Figma, estimate, dates, parent and checklist steps, unticked), not where it stands: the copy starts in the first status, unassigned, out of sprints and releases, without comments or files. The result carries its key.",
|
||||
"operationId": "IssuesController_duplicate",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "issue",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Ticket id or key",
|
||||
"schema": {
|
||||
"example": "WEB-12",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CommandResultDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid input",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Missing or rejected Pireon token",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Not a member of the project (or not allowed)",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "Someone changed it at the same time: retry",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Business rule (e.g. a transition the workflow forbids)",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"pireon-oauth": []
|
||||
},
|
||||
{
|
||||
"pireon": []
|
||||
}
|
||||
],
|
||||
"summary": "Duplicate the ticket",
|
||||
"tags": [
|
||||
"Issues"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/projects/{project}/trash": {
|
||||
"get": {
|
||||
"operationId": "IssuesController_trashed",
|
||||
@@ -4653,7 +4956,7 @@
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CommandResultDto"
|
||||
"$ref": "#/components/schemas/AttachmentResultDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5129,6 +5432,116 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/comments/{commentId}/reactions": {
|
||||
"post": {
|
||||
"description": "One of 👍 👎 😄 🎉 😕 ❤️ 🚀 👀; repeating it changes nothing.",
|
||||
"operationId": "CommentsController_react",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "commentId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ReactToCommentDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CommandResultDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid input",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Missing or rejected Pireon token",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Not a member of the project (or not allowed)",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "Someone changed it at the same time: retry",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Business rule (e.g. a transition the workflow forbids)",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"pireon-oauth": []
|
||||
},
|
||||
{
|
||||
"pireon": []
|
||||
}
|
||||
],
|
||||
"summary": "React to a comment, or take the reaction back (members)",
|
||||
"tags": [
|
||||
"Comments"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/me/releases": {
|
||||
"get": {
|
||||
"operationId": "ReleasesController_upcoming",
|
||||
@@ -6321,6 +6734,60 @@
|
||||
"security": []
|
||||
}
|
||||
},
|
||||
"/api/explore/projects/{project}/proposals": {
|
||||
"post": {
|
||||
"description": "For public projects that take proposals. It becomes a ticket labelled `propuesta`, opened by TaskStream in the first status (Bug or Feature), signed with `author` if given. Five per address every 15 minutes.",
|
||||
"operationId": "ExploreController_propose",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "project",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Project id or key",
|
||||
"schema": {
|
||||
"example": "WEB",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProposalDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProposalResultDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "It does not take proposals"
|
||||
},
|
||||
"404": {
|
||||
"description": "Not a public project"
|
||||
},
|
||||
"429": {
|
||||
"description": "Too many from this address"
|
||||
}
|
||||
},
|
||||
"summary": "Report a bug or propose an idea (no token needed)",
|
||||
"tags": [
|
||||
"Explore"
|
||||
],
|
||||
"security": []
|
||||
}
|
||||
},
|
||||
"/api/me/notifications": {
|
||||
"get": {
|
||||
"operationId": "NotificationsController_mine",
|
||||
@@ -9794,6 +10261,10 @@
|
||||
"type": "boolean",
|
||||
"description": "Readable by anyone, also without a token"
|
||||
},
|
||||
"acceptsProposals": {
|
||||
"type": "boolean",
|
||||
"description": "Takes bug reports and ideas from anyone (POST /api/explore/projects/{project}/proposals) while public"
|
||||
},
|
||||
"owner": {
|
||||
"$ref": "#/components/schemas/UserRefDto"
|
||||
},
|
||||
@@ -9825,6 +10296,7 @@
|
||||
"name",
|
||||
"description",
|
||||
"isPublic",
|
||||
"acceptsProposals",
|
||||
"owner",
|
||||
"members",
|
||||
"settings",
|
||||
@@ -9901,6 +10373,10 @@
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Anyone can read it without signing in (board, tickets, comments, releases, activity, statistics)"
|
||||
},
|
||||
"acceptsProposals": {
|
||||
"type": "boolean",
|
||||
"description": "Take bug reports and ideas from anyone through the public form (while public)"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -10510,6 +10986,17 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"blockedBy": {
|
||||
"description": "Open tickets it waits for: the ones that block it and the ones it depends on",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/IssueParentDto"
|
||||
}
|
||||
},
|
||||
"votes": {
|
||||
"type": "number",
|
||||
"description": "How many want it done"
|
||||
},
|
||||
"rank": {
|
||||
"type": "string",
|
||||
"description": "Place in the backlog and its board column (ascending, as text)"
|
||||
@@ -10575,6 +11062,8 @@
|
||||
"attachments",
|
||||
"watchers",
|
||||
"parent",
|
||||
"blockedBy",
|
||||
"votes",
|
||||
"rank",
|
||||
"checklist",
|
||||
"trashedAt",
|
||||
@@ -11426,6 +11915,25 @@
|
||||
"targetIssue"
|
||||
]
|
||||
},
|
||||
"CommentReactionDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"emoji": {
|
||||
"type": "string",
|
||||
"example": "👍"
|
||||
},
|
||||
"users": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/UserRefDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"emoji",
|
||||
"users"
|
||||
]
|
||||
},
|
||||
"CommentDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -11454,6 +11962,13 @@
|
||||
"type": "boolean",
|
||||
"description": "Deleted, but kept (without its text) while it has replies"
|
||||
},
|
||||
"reactions": {
|
||||
"description": "In the order they are offered; only the ones somebody chose",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/CommentReactionDto"
|
||||
}
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
@@ -11474,6 +11989,7 @@
|
||||
"body",
|
||||
"replyTo",
|
||||
"deleted",
|
||||
"reactions",
|
||||
"createdAt",
|
||||
"editedAt",
|
||||
"version"
|
||||
@@ -11533,6 +12049,34 @@
|
||||
"file"
|
||||
]
|
||||
},
|
||||
"AttachmentResultDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Id of the aggregate written"
|
||||
},
|
||||
"key": {
|
||||
"type": "string",
|
||||
"description": "Human key when one was assigned (project or issue)",
|
||||
"example": "WEB-13"
|
||||
},
|
||||
"version": {
|
||||
"type": "number",
|
||||
"description": "Version after the write. Writes are acknowledged at once and the read model catches up asynchronously: a GET may briefly return the previous version (or 404 right after a create)."
|
||||
},
|
||||
"attachmentId": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"description": "The new file; `` shows an image inside a description or comment"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"version",
|
||||
"attachmentId"
|
||||
]
|
||||
},
|
||||
"CommentBodyDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -11546,6 +12090,33 @@
|
||||
"body"
|
||||
]
|
||||
},
|
||||
"ReactToCommentDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"emoji": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"👍",
|
||||
"👎",
|
||||
"😄",
|
||||
"🎉",
|
||||
"😕",
|
||||
"❤️",
|
||||
"🚀",
|
||||
"👀"
|
||||
],
|
||||
"example": "👍"
|
||||
},
|
||||
"on": {
|
||||
"type": "boolean",
|
||||
"description": "true adds the reaction, false takes it back"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"emoji",
|
||||
"on"
|
||||
]
|
||||
},
|
||||
"ReleaseStatus": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
@@ -11847,6 +12418,10 @@
|
||||
"done": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"votes": {
|
||||
"type": "number",
|
||||
"description": "How many want it"
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"example": "/p/WEB/issues/WEB-3",
|
||||
@@ -11860,6 +12435,7 @@
|
||||
"dueDate",
|
||||
"percent",
|
||||
"done",
|
||||
"votes",
|
||||
"path"
|
||||
]
|
||||
},
|
||||
@@ -11927,6 +12503,63 @@
|
||||
"path"
|
||||
]
|
||||
},
|
||||
"ShowcaseProposalDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "A ticket type id"
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"example": "/p/WEB/issues/WEB-9"
|
||||
},
|
||||
"votes": {
|
||||
"type": "number",
|
||||
"description": "How many want it"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"key",
|
||||
"title",
|
||||
"type",
|
||||
"path",
|
||||
"votes"
|
||||
]
|
||||
},
|
||||
"ShowcaseProposalsDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"open": {
|
||||
"type": "boolean",
|
||||
"description": "Whether it takes bug reports and ideas now"
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"example": "/p/WEB/proponer",
|
||||
"description": "The form in the web app, while it takes them"
|
||||
},
|
||||
"top": {
|
||||
"description": "Open proposals, most voted first",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ShowcaseProposalDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"open",
|
||||
"path",
|
||||
"top"
|
||||
]
|
||||
},
|
||||
"ShowcaseDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -11960,6 +12593,9 @@
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/ShowcaseIssueDto"
|
||||
}
|
||||
},
|
||||
"proposals": {
|
||||
"$ref": "#/components/schemas/ShowcaseProposalsDto"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -11967,7 +12603,65 @@
|
||||
"activeSprint",
|
||||
"roadmap",
|
||||
"releases",
|
||||
"goodFirst"
|
||||
"goodFirst",
|
||||
"proposals"
|
||||
]
|
||||
},
|
||||
"ProposalDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"BUG",
|
||||
"IDEA"
|
||||
],
|
||||
"description": "A bug report or an idea"
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 150
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"maxLength": 5000,
|
||||
"description": "What happens, or what they would like (Markdown)"
|
||||
},
|
||||
"author": {
|
||||
"type": "string",
|
||||
"maxLength": 80,
|
||||
"description": "How they sign it; shown in the ticket (no e-mail: it would be public)"
|
||||
},
|
||||
"website": {
|
||||
"type": "string",
|
||||
"description": "Leave empty: a field only bots fill in"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"kind",
|
||||
"title"
|
||||
]
|
||||
},
|
||||
"ProposalResultDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"example": "WEB-42",
|
||||
"description": "The ticket it became"
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"example": "/p/WEB/issues/WEB-42",
|
||||
"description": "Where to see it in the web app"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"key",
|
||||
"path"
|
||||
]
|
||||
},
|
||||
"NotificationKind": {
|
||||
|
||||
@@ -207,6 +207,9 @@ type Comment {
|
||||
id: ID!
|
||||
issueId: ID!
|
||||
|
||||
"""In the order they are offered; only the ones somebody chose"""
|
||||
reactions: [CommentReaction!]!
|
||||
|
||||
"""The top-level comment it answers; null for a top-level one"""
|
||||
replyTo: ID
|
||||
version: Int!
|
||||
@@ -228,6 +231,14 @@ type CommentPage {
|
||||
total: Int!
|
||||
}
|
||||
|
||||
"""One emoji on a comment"""
|
||||
type CommentReaction {
|
||||
emoji: String!
|
||||
|
||||
"""Who chose it"""
|
||||
users: [UserRef!]!
|
||||
}
|
||||
|
||||
type CountByAssignee {
|
||||
count: Int!
|
||||
|
||||
@@ -414,6 +425,11 @@ type Issue {
|
||||
area: String
|
||||
assignee: UserRef
|
||||
attachments: [Attachment!]!
|
||||
|
||||
"""
|
||||
Open tickets it waits for: the ones that block it and the ones it depends on
|
||||
"""
|
||||
blockedBy: [IssueParent!]!
|
||||
checklist: [ChecklistItem!]!
|
||||
commentCount: Int!
|
||||
comments(limit: Int! = 50, offset: Int! = 0): CommentPage!
|
||||
@@ -469,6 +485,12 @@ type Issue {
|
||||
updatedAt: String!
|
||||
version: Int!
|
||||
|
||||
"""Whether I voted for it"""
|
||||
voted: Boolean!
|
||||
|
||||
"""How many want it done"""
|
||||
votes: Int!
|
||||
|
||||
"""People following the ticket"""
|
||||
watchers: [UserRef!]!
|
||||
}
|
||||
@@ -672,6 +694,11 @@ type Mutation {
|
||||
"""Delete a sprint that never started; its tickets go back to the backlog"""
|
||||
deleteSprint(sprintId: ID!): CommandResult!
|
||||
deleteWebhook(webhookId: ID!): Boolean!
|
||||
|
||||
"""
|
||||
A new ticket like this one: what it says, not where it stands (the key comes in the result)
|
||||
"""
|
||||
duplicateIssue(issueId: ID!): CommandResult!
|
||||
editComment(body: String!, commentId: ID!): CommandResult!
|
||||
|
||||
"""Relate tickets, e.g. FE DEPENDS_ON BE, QA TESTS FE"""
|
||||
@@ -688,6 +715,9 @@ type Mutation {
|
||||
"""Move a ticket in the backlog, between two others of the same project"""
|
||||
rankIssue(input: RankIssueInput!): CommandResult!
|
||||
|
||||
"""React (👍 👎 😄 🎉 😕 ❤️ 🚀 👀) or take it back (on: false)"""
|
||||
reactToComment(commentId: ID!, emoji: String!, on: Boolean!): CommandResult!
|
||||
|
||||
"""
|
||||
Remove a file from a ticket (whoever uploaded it, or an owner/admin). Upload with the REST API.
|
||||
"""
|
||||
@@ -750,6 +780,11 @@ type Mutation {
|
||||
updateSprint(input: UpdateSprintInput!): CommandResult!
|
||||
updateWebhook(input: UpdateWebhookInput!): Webhook!
|
||||
|
||||
"""
|
||||
Vote for a ticket (on) or take the vote back: anyone signed in on a public project, members on a private one
|
||||
"""
|
||||
voteIssue(issueId: ID!, on: Boolean!): CommandResult!
|
||||
|
||||
"""
|
||||
Follow a ticket: be told when it moves, is reassigned or gets a comment
|
||||
"""
|
||||
@@ -805,6 +840,9 @@ type NotificationSettings {
|
||||
}
|
||||
|
||||
type Project {
|
||||
"""Takes bug reports and ideas from anyone (while public)"""
|
||||
acceptsProposals: Boolean!
|
||||
|
||||
"""
|
||||
Statistics of the last `days` days (7-365): created vs resolved, cumulative flow, cycle/lead time, throughput and QA
|
||||
"""
|
||||
@@ -1263,6 +1301,10 @@ input UpdateIssueInput {
|
||||
}
|
||||
|
||||
input UpdateProjectInput {
|
||||
"""
|
||||
Take bug reports and ideas from anyone through the public form (while public)
|
||||
"""
|
||||
acceptsProposals: Boolean
|
||||
description: String
|
||||
isPublic: Boolean
|
||||
name: String
|
||||
|
||||
@@ -476,6 +476,47 @@ export interface paths {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/issues/{issue}/vote": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
/**
|
||||
* Vote for the ticket
|
||||
* @description Anyone signed in, on a public project (that is the point: the people it is for say what they want); members on a private one. Voting twice changes nothing.
|
||||
*/
|
||||
put: operations["IssuesController_vote"];
|
||||
post?: never;
|
||||
/** Take back my vote for the ticket */
|
||||
delete: operations["IssuesController_unvote"];
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/issues/{issue}/duplicate": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/**
|
||||
* Duplicate the ticket
|
||||
* @description Copies what it says (type, title, description, area, priority, labels, Figma, estimate, dates, parent and checklist steps, unticked), not where it stands: the copy starts in the first status, unassigned, out of sprints and releases, without comments or files. The result carries its key.
|
||||
*/
|
||||
post: operations["IssuesController_duplicate"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/projects/{project}/trash": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@@ -716,6 +757,26 @@ export interface paths {
|
||||
patch: operations["CommentsController_edit"];
|
||||
trace?: never;
|
||||
};
|
||||
"/api/comments/{commentId}/reactions": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/**
|
||||
* React to a comment, or take the reaction back (members)
|
||||
* @description One of 👍 👎 😄 🎉 😕 ❤️ 🚀 👀; repeating it changes nothing.
|
||||
*/
|
||||
post: operations["CommentsController_react"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/me/releases": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@@ -937,6 +998,26 @@ export interface paths {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/explore/projects/{project}/proposals": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/**
|
||||
* Report a bug or propose an idea (no token needed)
|
||||
* @description For public projects that take proposals. It becomes a ticket labelled `propuesta`, opened by TaskStream in the first status (Bug or Feature), signed with `author` if given. Five per address every 15 minutes.
|
||||
*/
|
||||
post: operations["ExploreController_propose"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/me/notifications": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@@ -1540,6 +1621,8 @@ export interface components {
|
||||
description: string | null;
|
||||
/** @description Readable by anyone, also without a token */
|
||||
isPublic: boolean;
|
||||
/** @description Takes bug reports and ideas from anyone (POST /api/explore/projects/{project}/proposals) while public */
|
||||
acceptsProposals: boolean;
|
||||
owner: components["schemas"]["UserRefDto"];
|
||||
members: components["schemas"]["ProjectMemberDto"][];
|
||||
settings: components["schemas"]["ProjectSettingsDto"];
|
||||
@@ -1585,6 +1668,8 @@ export interface components {
|
||||
* @default false
|
||||
*/
|
||||
isPublic: boolean;
|
||||
/** @description Take bug reports and ideas from anyone through the public form (while public) */
|
||||
acceptsProposals?: boolean;
|
||||
};
|
||||
WorkflowStatusBodyDto: {
|
||||
/**
|
||||
@@ -1786,6 +1871,10 @@ export interface components {
|
||||
watchers: components["schemas"]["UserRefDto"][];
|
||||
/** @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 */
|
||||
blockedBy: components["schemas"]["IssueParentDto"][];
|
||||
/** @description How many want it done */
|
||||
votes: number;
|
||||
/** @description Place in the backlog and its board column (ascending, as text) */
|
||||
rank: string;
|
||||
checklist: components["schemas"]["ChecklistItemDto"][];
|
||||
@@ -2081,6 +2170,11 @@ export interface components {
|
||||
*/
|
||||
targetIssue: string;
|
||||
};
|
||||
CommentReactionDto: {
|
||||
/** @example 👍 */
|
||||
emoji: string;
|
||||
users: components["schemas"]["UserRefDto"][];
|
||||
};
|
||||
CommentDto: {
|
||||
/** Format: uuid */
|
||||
id: string;
|
||||
@@ -2096,6 +2190,8 @@ export interface components {
|
||||
replyTo: string | null;
|
||||
/** @description Deleted, but kept (without its text) while it has replies */
|
||||
deleted: boolean;
|
||||
/** @description In the order they are offered; only the ones somebody chose */
|
||||
reactions: components["schemas"]["CommentReactionDto"][];
|
||||
/** Format: date-time */
|
||||
createdAt: string;
|
||||
/** Format: date-time */
|
||||
@@ -2127,10 +2223,35 @@ export interface components {
|
||||
*/
|
||||
commentId?: string;
|
||||
};
|
||||
AttachmentResultDto: {
|
||||
/** @description Id of the aggregate written */
|
||||
id: string;
|
||||
/**
|
||||
* @description Human key when one was assigned (project or issue)
|
||||
* @example WEB-13
|
||||
*/
|
||||
key?: string;
|
||||
/** @description Version after the write. Writes are acknowledged at once and the read model catches up asynchronously: a GET may briefly return the previous version (or 404 right after a create). */
|
||||
version: number;
|
||||
/**
|
||||
* Format: uuid
|
||||
* @description The new file; `` shows an image inside a description or comment
|
||||
*/
|
||||
attachmentId: string;
|
||||
};
|
||||
CommentBodyDto: {
|
||||
/** @example Reproducido en Safari 17. */
|
||||
body: string;
|
||||
};
|
||||
ReactToCommentDto: {
|
||||
/**
|
||||
* @example 👍
|
||||
* @enum {string}
|
||||
*/
|
||||
emoji: "👍" | "👎" | "😄" | "🎉" | "😕" | "❤️" | "🚀" | "👀";
|
||||
/** @description true adds the reaction, false takes it back */
|
||||
on: boolean;
|
||||
};
|
||||
/** @enum {string} */
|
||||
ReleaseStatus: "PLANNED" | "RELEASED" | "ARCHIVED";
|
||||
ReleaseDto: {
|
||||
@@ -2235,6 +2356,8 @@ export interface components {
|
||||
dueDate: string | null;
|
||||
percent: number;
|
||||
done: boolean;
|
||||
/** @description How many want it */
|
||||
votes: number;
|
||||
/**
|
||||
* @description In the web app
|
||||
* @example /p/WEB/issues/WEB-3
|
||||
@@ -2258,6 +2381,27 @@ export interface components {
|
||||
/** @example /p/WEB/issues/WEB-9 */
|
||||
path: string;
|
||||
};
|
||||
ShowcaseProposalDto: {
|
||||
key: string;
|
||||
title: string;
|
||||
/** @description A ticket type id */
|
||||
type: string;
|
||||
/** @example /p/WEB/issues/WEB-9 */
|
||||
path: string;
|
||||
/** @description How many want it */
|
||||
votes: number;
|
||||
};
|
||||
ShowcaseProposalsDto: {
|
||||
/** @description Whether it takes bug reports and ideas now */
|
||||
open: boolean;
|
||||
/**
|
||||
* @description The form in the web app, while it takes them
|
||||
* @example /p/WEB/proponer
|
||||
*/
|
||||
path: string | null;
|
||||
/** @description Open proposals, most voted first */
|
||||
top: components["schemas"]["ShowcaseProposalDto"][];
|
||||
};
|
||||
ShowcaseDto: {
|
||||
project: components["schemas"]["ShowcaseProjectDto"];
|
||||
activeSprint: components["schemas"]["ShowcaseSprintDto"] | null;
|
||||
@@ -2265,6 +2409,33 @@ export interface components {
|
||||
releases: components["schemas"]["ShowcaseReleaseDto"][];
|
||||
/** @description Open tickets labelled good-first-issue */
|
||||
goodFirst: components["schemas"]["ShowcaseIssueDto"][];
|
||||
proposals: components["schemas"]["ShowcaseProposalsDto"];
|
||||
};
|
||||
ProposalDto: {
|
||||
/**
|
||||
* @description A bug report or an idea
|
||||
* @enum {string}
|
||||
*/
|
||||
kind: "BUG" | "IDEA";
|
||||
title: string;
|
||||
/** @description What happens, or what they would like (Markdown) */
|
||||
description?: string;
|
||||
/** @description How they sign it; shown in the ticket (no e-mail: it would be public) */
|
||||
author?: string;
|
||||
/** @description Leave empty: a field only bots fill in */
|
||||
website?: string;
|
||||
};
|
||||
ProposalResultDto: {
|
||||
/**
|
||||
* @description The ticket it became
|
||||
* @example WEB-42
|
||||
*/
|
||||
key: string | null;
|
||||
/**
|
||||
* @description Where to see it in the web app
|
||||
* @example /p/WEB/issues/WEB-42
|
||||
*/
|
||||
path: string | null;
|
||||
};
|
||||
/** @enum {string} */
|
||||
NotificationKind: "ASSIGNED" | "STATUS_CHANGED" | "QA_FAILED" | "COMMENTED" | "MENTIONED" | "REPLIED" | "WATCHED" | "ADDED_TO_PROJECT" | "REMINDER";
|
||||
@@ -4753,6 +4924,234 @@ export interface operations {
|
||||
};
|
||||
};
|
||||
};
|
||||
IssuesController_vote: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
/** @description Ticket id or key */
|
||||
issue: 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_unvote: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
/** @description Ticket id or key */
|
||||
issue: 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_duplicate: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
/** @description Ticket id or key */
|
||||
issue: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
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_trashed: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@@ -5668,7 +6067,7 @@ export interface operations {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["CommandResultDto"];
|
||||
"application/json": components["schemas"]["AttachmentResultDto"];
|
||||
};
|
||||
};
|
||||
/** @description Invalid input */
|
||||
@@ -6016,6 +6415,85 @@ export interface operations {
|
||||
};
|
||||
};
|
||||
};
|
||||
CommentsController_react: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
commentId: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["ReactToCommentDto"];
|
||||
};
|
||||
};
|
||||
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"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
ReleasesController_upcoming: {
|
||||
parameters: {
|
||||
query?: {
|
||||
@@ -6859,6 +7337,53 @@ export interface operations {
|
||||
};
|
||||
};
|
||||
};
|
||||
ExploreController_propose: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
/** @description Project id or key */
|
||||
project: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["ProposalDto"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
201: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["ProposalResultDto"];
|
||||
};
|
||||
};
|
||||
/** @description It does not take proposals */
|
||||
403: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Not a public project */
|
||||
404: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Too many from this address */
|
||||
429: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
NotificationsController_mine: {
|
||||
parameters: {
|
||||
query?: {
|
||||
|
||||
@@ -19,6 +19,7 @@ const REMOVE = {
|
||||
|
||||
const PRIORITY = z.enum(['LOWEST', 'LOW', 'MEDIUM', 'HIGH', 'CRITICAL']);
|
||||
const CATEGORY = z.enum(['TODO', 'IN_PROGRESS', 'QA', 'DONE', 'CANCELLED']);
|
||||
const REACTION = z.enum(['👍', '👎', '😄', '🎉', '😕', '❤️', '🚀', '👀']);
|
||||
const LINK_TYPE = z.enum([
|
||||
'BLOCKS',
|
||||
'DEPENDS_ON',
|
||||
@@ -242,6 +243,10 @@ export class IssuesResolver {
|
||||
author: c.deleted ? null : c.author.name,
|
||||
at: c.createdAt,
|
||||
body: c.deleted ? '(deleted; kept for its replies)' : c.body,
|
||||
reactions: c.reactions.map((r) => ({
|
||||
emoji: r.emoji,
|
||||
by: r.users.map((u) => u.name),
|
||||
})),
|
||||
}))
|
||||
: undefined;
|
||||
const history = params.withHistory
|
||||
@@ -639,4 +644,70 @@ export class IssuesResolver {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@Tool({
|
||||
name: 'duplicate_issue',
|
||||
description:
|
||||
"A new ticket like this one: what it says (type, title, description, area, priority, labels, Figma, estimate, dates, parent, checklist steps unticked), not where it stands (first status, unassigned, no sprint, release, comments or files). Answers the copy's key.",
|
||||
paramsSchema: z.object({ issue: issueRef }),
|
||||
annotations: WRITE,
|
||||
})
|
||||
duplicate({ issue }: { issue: string }, ctx: McpContext) {
|
||||
return toolResult(() =>
|
||||
this.api.data(
|
||||
this.api.as(ctx).POST('/api/issues/{issue}/duplicate', {
|
||||
params: { path: { issue } },
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@Tool({
|
||||
name: 'react_to_comment',
|
||||
description:
|
||||
'React to a comment (👍 👎 😄 🎉 😕 ❤️ 🚀 👀) or take the reaction back (on: false). Comment ids come with get_issue.',
|
||||
paramsSchema: z.object({
|
||||
commentId: z.string().min(1),
|
||||
emoji: REACTION,
|
||||
on: z.boolean().default(true),
|
||||
}),
|
||||
annotations: { ...WRITE, idempotentHint: true },
|
||||
})
|
||||
react(
|
||||
params: {
|
||||
commentId: string;
|
||||
emoji: z.infer<typeof REACTION>;
|
||||
on: boolean;
|
||||
},
|
||||
ctx: McpContext,
|
||||
) {
|
||||
const { commentId, ...body } = params;
|
||||
return toolResult(() =>
|
||||
this.api.data(
|
||||
this.api.as(ctx).POST('/api/comments/{commentId}/reactions', {
|
||||
params: { path: { commentId } },
|
||||
body,
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@Tool({
|
||||
name: 'vote_issue',
|
||||
description:
|
||||
'Vote for a ticket (on) or take the vote back: anyone signed in on a public project, members on a private one. Votes say what people want done.',
|
||||
paramsSchema: z.object({ issue: issueRef, on: z.boolean().default(true) }),
|
||||
annotations: { ...WRITE, idempotentHint: true },
|
||||
})
|
||||
vote({ issue, on }: { issue: string; on: boolean }, ctx: McpContext) {
|
||||
const client = this.api.as(ctx);
|
||||
const options = { params: { path: { issue } } };
|
||||
return toolResult(() =>
|
||||
this.api.data(
|
||||
on
|
||||
? client.PUT('/api/issues/{issue}/vote', options)
|
||||
: client.DELETE('/api/issues/{issue}/vote', options),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@ export const issueLine = (issue: Issue) => ({
|
||||
release: issue.release?.name ?? null,
|
||||
sprint: issue.sprint?.name ?? null,
|
||||
parent: issue.parent?.key ?? null,
|
||||
/** Open tickets it waits for. */
|
||||
blockedBy: issue.blockedBy.map((b) => b.key),
|
||||
votes: issue.votes,
|
||||
storyPoints: issue.storyPoints,
|
||||
dueDate: issue.dueDate,
|
||||
labels: issue.labels,
|
||||
|
||||
@@ -27,6 +27,8 @@ describe('appRoutes', () => {
|
||||
expect(project?.children?.map((route) => route.path)).toEqual([
|
||||
'',
|
||||
'board',
|
||||
'proponer',
|
||||
'list',
|
||||
'issues/:issueKey',
|
||||
'backlog',
|
||||
'sprints',
|
||||
|
||||
Reference in New Issue
Block a user