feat: release notes

Each release can carry notes in Markdown, written before or after it
ships. A draft is generated from its finished tickets grouped by type, to
edit and copy from the releases page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 15:19:23 +02:00
parent 45a3dc1e16
commit 24b37b6628
40 changed files with 1251 additions and 31 deletions

View File

@@ -5532,6 +5532,197 @@
]
}
},
"/api/releases/{releaseId}/notes": {
"put": {
"description": "Also after the release ships; not once archived.",
"operationId": "ReleasesController_writeNotes",
"parameters": [
{
"name": "releaseId",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ReleaseNotesDto"
}
}
}
},
"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": "Write the release notes (Markdown)",
"tags": [
"Releases"
]
}
},
"/api/releases/{releaseId}/notes/draft": {
"get": {
"description": "Grouped by type; nothing is saved.",
"operationId": "ReleasesController_notesDraft",
"parameters": [
{
"name": "releaseId",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ReleaseNotesDraftDto"
}
}
}
},
"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"
}
}
}
}
},
"security": [
{
"pireon-oauth": []
},
{
"pireon": []
},
{}
],
"summary": "A Markdown draft of the notes from the finished tickets",
"tags": [
"Releases"
]
}
},
"/api/releases/{releaseId}/archive": {
"post": {
"operationId": "ReleasesController_archive",
@@ -9481,6 +9672,11 @@
"nullable": true,
"format": "date-time"
},
"notes": {
"type": "string",
"nullable": true,
"description": "What shipped, told for people (Markdown)"
},
"createdAt": {
"type": "string",
"format": "date-time"
@@ -9497,6 +9693,7 @@
"targetDate",
"status",
"releasedAt",
"notes",
"createdAt",
"version"
]
@@ -9625,6 +9822,32 @@
}
}
},
"ReleaseNotesDto": {
"type": "object",
"properties": {
"notes": {
"type": "string",
"nullable": true,
"maxLength": 20000,
"description": "Markdown; null removes the notes"
}
},
"required": [
"notes"
]
},
"ReleaseNotesDraftDto": {
"type": "object",
"properties": {
"markdown": {
"type": "string",
"example": "## v1.4.0\n\n### Novedades\n\n- Pago con tarjeta (WEB-1)\n"
}
},
"required": [
"markdown"
]
},
"Object": {
"type": "object",
"properties": {}

View File

@@ -496,6 +496,11 @@ type Mutation {
Follow a ticket: be told when it moves, is reassigned or gets a comment
"""
watchIssue(issueId: ID!): CommandResult!
"""
Write the release notes (Markdown; null removes them). Also after shipping.
"""
writeReleaseNotes(notes: String, releaseId: ID!): CommandResult!
}
type MyNotifications {
@@ -743,6 +748,14 @@ type Release {
"""Tickets included in this release"""
issues(limit: Int! = 100, offset: Int! = 0): IssuePage!
name: String!
"""What shipped, told for people (Markdown)"""
notes: String
"""
A Markdown draft of the notes from its finished tickets, by type (not saved)
"""
notesDraft: String!
progress: ReleaseProgress!
projectId: ID!
releasedAt: String