feat: an ordered backlog
A Backlog tab lists the tickets still to start in the order the team will take them; drag them, or use the arrows, to reorder. Moving a ticket only rewrites that ticket's rank, read from the event store, and board columns follow the same order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2188,6 +2188,15 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sort",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "RECENT (default): last updated first. RANK: backlog order.",
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/IssueSort"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "search",
|
||||
"required": false,
|
||||
@@ -3098,6 +3107,118 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/issues/{issue}/rank": {
|
||||
"put": {
|
||||
"description": "Between the ticket that ends up above it and the one below (either may be left out for the top or the bottom). Board columns follow the same order.",
|
||||
"operationId": "IssuesController_rank",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "issue",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Ticket id or key",
|
||||
"schema": {
|
||||
"example": "WEB-12",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/RankIssueDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": "Move the ticket in the backlog",
|
||||
"tags": [
|
||||
"Issues"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/issues/{issue}/checklist": {
|
||||
"post": {
|
||||
"operationId": "IssuesController_addChecklistItem",
|
||||
@@ -6940,6 +7061,10 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"rank": {
|
||||
"type": "string",
|
||||
"description": "Place in the backlog and its board column (ascending, as text)"
|
||||
},
|
||||
"checklist": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@@ -6986,6 +7111,7 @@
|
||||
"attachments",
|
||||
"watchers",
|
||||
"parent",
|
||||
"rank",
|
||||
"checklist",
|
||||
"commentCount",
|
||||
"createdAt",
|
||||
@@ -7421,6 +7547,13 @@
|
||||
"total"
|
||||
]
|
||||
},
|
||||
"IssueSort": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"RECENT",
|
||||
"RANK"
|
||||
]
|
||||
},
|
||||
"CreateIssueDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -7656,6 +7789,23 @@
|
||||
"parent"
|
||||
]
|
||||
},
|
||||
"RankIssueDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"above": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"example": "WEB-4",
|
||||
"description": "The ticket that ends up right above (id or key; null: the top)"
|
||||
},
|
||||
"below": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"example": "WEB-9",
|
||||
"description": "The ticket that ends up right below (id or key; null: the bottom)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ChecklistItemBodyDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -242,6 +242,11 @@ type Issue {
|
||||
priority: IssuePriority!
|
||||
projectId: ID!
|
||||
projectKey: String!
|
||||
|
||||
"""
|
||||
Place in the backlog and in its board column (ascending, compare as text)
|
||||
"""
|
||||
rank: String!
|
||||
release: ReleaseRef
|
||||
reporter: UserRef!
|
||||
|
||||
@@ -281,6 +286,7 @@ input IssueFilterInput {
|
||||
|
||||
"""Matches key, title or description"""
|
||||
search: String
|
||||
sort: IssueSort = RECENT
|
||||
statuses: [String!]
|
||||
types: [String!]
|
||||
unassigned: Boolean
|
||||
@@ -322,6 +328,12 @@ enum IssuePriority {
|
||||
MEDIUM
|
||||
}
|
||||
|
||||
"""RECENT: last updated first. RANK: backlog order."""
|
||||
enum IssueSort {
|
||||
RANK
|
||||
RECENT
|
||||
}
|
||||
|
||||
type IssueTypeOption {
|
||||
archived: Boolean!
|
||||
color: String!
|
||||
@@ -400,6 +412,9 @@ type Mutation {
|
||||
"""
|
||||
publishRelease(force: Boolean! = false, releaseId: ID!): CommandResult!
|
||||
|
||||
"""Move a ticket in the backlog, between two others of the same project"""
|
||||
rankIssue(input: RankIssueInput!): CommandResult!
|
||||
|
||||
"""
|
||||
Remove a file from a ticket (whoever uploaded it, or an owner/admin). Upload with the REST API.
|
||||
"""
|
||||
@@ -641,6 +656,15 @@ type Query {
|
||||
users(limit: Int! = 20, search: String! = ""): [User!]!
|
||||
}
|
||||
|
||||
input RankIssueInput {
|
||||
"""The ticket that ends up right above (null: the top)"""
|
||||
aboveIssueId: ID
|
||||
|
||||
"""The ticket that ends up right below (null: the bottom)"""
|
||||
belowIssueId: ID
|
||||
issueId: ID!
|
||||
}
|
||||
|
||||
type Release {
|
||||
"""Scope vs finished tickets, day by day"""
|
||||
burnup: ReleaseBurnup!
|
||||
|
||||
@@ -475,6 +475,26 @@ export interface paths {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/issues/{issue}/rank": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
/**
|
||||
* Move the ticket in the backlog
|
||||
* @description Between the ticket that ends up above it and the one below (either may be left out for the top or the bottom). Board columns follow the same order.
|
||||
*/
|
||||
put: operations["IssuesController_rank"];
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/issues/{issue}/checklist": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@@ -1208,6 +1228,8 @@ export interface components {
|
||||
watchers: components["schemas"]["UserRefDto"][];
|
||||
/** @description The epic or ticket this one breaks down */
|
||||
parent: components["schemas"]["IssueParentDto"] | null;
|
||||
/** @description Place in the backlog and its board column (ascending, as text) */
|
||||
rank: string;
|
||||
checklist: components["schemas"]["ChecklistItemDto"][];
|
||||
commentCount: number;
|
||||
/** Format: date-time */
|
||||
@@ -1334,6 +1356,8 @@ export interface components {
|
||||
/** @description Total matches (for pagination) */
|
||||
total: number;
|
||||
};
|
||||
/** @enum {string} */
|
||||
IssueSort: "RECENT" | "RANK";
|
||||
CreateIssueDto: {
|
||||
description?: string | null;
|
||||
/**
|
||||
@@ -1425,6 +1449,18 @@ export interface components {
|
||||
*/
|
||||
parent: string | null;
|
||||
};
|
||||
RankIssueDto: {
|
||||
/**
|
||||
* @description The ticket that ends up right above (id or key; null: the top)
|
||||
* @example WEB-4
|
||||
*/
|
||||
above?: string | null;
|
||||
/**
|
||||
* @description The ticket that ends up right below (id or key; null: the bottom)
|
||||
* @example WEB-9
|
||||
*/
|
||||
below?: string | null;
|
||||
};
|
||||
ChecklistItemBodyDto: {
|
||||
/** @example Actualizar el changelog */
|
||||
text: string;
|
||||
@@ -3017,6 +3053,8 @@ export interface operations {
|
||||
label?: string[];
|
||||
/** @description Only the tickets hanging from this one (id or key) */
|
||||
parent?: string;
|
||||
/** @description RECENT (default): last updated first. RANK: backlog order. */
|
||||
sort?: components["schemas"]["IssueSort"];
|
||||
/** @description Matches key, title or description */
|
||||
search?: string;
|
||||
};
|
||||
@@ -3671,6 +3709,86 @@ export interface operations {
|
||||
};
|
||||
};
|
||||
};
|
||||
IssuesController_rank: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
/** @description Ticket id or key */
|
||||
issue: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["RankIssueDto"];
|
||||
};
|
||||
};
|
||||
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_addChecklistItem: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
|
||||
@@ -28,6 +28,7 @@ describe('appRoutes', () => {
|
||||
'',
|
||||
'board',
|
||||
'issues/:issueKey',
|
||||
'backlog',
|
||||
'epics',
|
||||
'releases',
|
||||
'dashboard',
|
||||
|
||||
Reference in New Issue
Block a user