feat: saved views of my work, across my projects

A person saves named views of «my work»: projects, assigned to me, that I
follow, status categories, priorities, bugs only, overdue and text. They
are private (Mongo my_views, no history), listed and deleted by their
owner (GraphQL myViews / saveMyView / deleteMyView, REST /api/me/views),
and run as the cross-project search, which gains project, priority,
bugsOnly and overdue filters (also in MCP my_issues). The home page shows
them as a bar above «my work», with a drawer to save a new one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-15 14:22:20 +02:00
parent e14f445e6b
commit 5b0396bc10
46 changed files with 2648 additions and 35 deletions

View File

@@ -2260,6 +2260,47 @@
"schema": {
"type": "string"
}
},
{
"name": "project",
"required": false,
"in": "query",
"description": "Only these of my projects (ids). Repeat or separate with commas",
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
},
{
"name": "priority",
"required": false,
"in": "query",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/IssuePriority"
}
}
},
{
"name": "bugsOnly",
"required": false,
"in": "query",
"description": "Only types with the bug icon",
"schema": {
"type": "boolean"
}
},
{
"name": "overdue",
"required": false,
"in": "query",
"description": "Only open tickets past their due date",
"schema": {
"type": "boolean"
}
}
],
"responses": {
@@ -7806,6 +7847,271 @@
]
}
},
"/api/me/views": {
"get": {
"operationId": "SavedFiltersController_myViews",
"parameters": [],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MyViewDto"
}
}
}
}
},
"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": "My saved views of my work, across my projects (by name)",
"tags": [
"Saved filters"
]
},
"post": {
"description": "Run it with GET /api/me/issues and the same criteria (project, priority, bugsOnly, overdue…).",
"operationId": "SavedFiltersController_saveMyView",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SaveMyViewDto"
}
}
}
},
"responses": {
"201": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SavedFilterIdDto"
}
}
}
},
"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": "Save a view of my work under a name",
"tags": [
"Saved filters"
]
}
},
"/api/me/views/{id}": {
"delete": {
"operationId": "SavedFiltersController_deleteMyView",
"parameters": [
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"204": {
"description": ""
},
"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": "Delete one of my views",
"tags": [
"Saved filters"
]
}
},
"/api/projects/{project}/sprints": {
"get": {
"description": "The one under way, the planned ones, then the closed ones.",
@@ -14193,6 +14499,132 @@
"id"
]
},
"MyViewCriteriaResponseDto": {
"type": "object",
"properties": {
"projectIds": {
"type": "array",
"items": {
"type": "string",
"format": "uuid"
}
},
"assignedToMe": {
"type": "boolean"
},
"watching": {
"type": "boolean"
},
"categories": {
"type": "array",
"items": {
"$ref": "#/components/schemas/StatusCategory"
}
},
"priorities": {
"type": "array",
"items": {
"$ref": "#/components/schemas/IssuePriority"
}
},
"bugsOnly": {
"type": "boolean"
},
"overdue": {
"type": "boolean"
},
"search": {
"type": "string"
}
}
},
"MyViewDto": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"name": {
"type": "string",
"example": "Bugs críticos de todos mis proyectos"
},
"criteria": {
"$ref": "#/components/schemas/MyViewCriteriaResponseDto"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"id",
"name",
"criteria",
"createdAt"
]
},
"MyViewCriteriaDto": {
"type": "object",
"properties": {
"projectIds": {
"description": "Only these of my projects (empty: all)",
"type": "array",
"items": {
"type": "string",
"format": "uuid"
}
},
"assignedToMe": {
"type": "boolean"
},
"watching": {
"type": "boolean",
"description": "Only the tickets I follow"
},
"categories": {
"type": "array",
"items": {
"$ref": "#/components/schemas/StatusCategory"
}
},
"priorities": {
"type": "array",
"items": {
"$ref": "#/components/schemas/IssuePriority"
}
},
"bugsOnly": {
"type": "boolean",
"description": "Only types with the bug icon"
},
"overdue": {
"type": "boolean",
"description": "Only open tickets past due"
},
"search": {
"type": "string",
"maxLength": 200
}
}
},
"SaveMyViewDto": {
"type": "object",
"properties": {
"name": {
"type": "string",
"example": "Bugs críticos de todos mis proyectos",
"maxLength": 60
},
"criteria": {
"$ref": "#/components/schemas/MyViewCriteriaDto"
}
},
"required": [
"name",
"criteria"
]
},
"SprintStatus": {
"type": "string",
"enum": [