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:
@@ -74,6 +74,7 @@ fragment IssueSummaryFields on Issue {
|
||||
key
|
||||
title
|
||||
}
|
||||
rank
|
||||
}
|
||||
|
||||
fragment IssueFields on Issue {
|
||||
|
||||
@@ -75,6 +75,7 @@ export type IssueFilterInput = {
|
||||
releaseId?: string | null | undefined;
|
||||
/** Matches key, title or description */
|
||||
search?: string | null | undefined;
|
||||
sort?: IssueSort | null | undefined;
|
||||
statuses?: Array<string> | null | undefined;
|
||||
types?: Array<string> | null | undefined;
|
||||
unassigned?: boolean | null | undefined;
|
||||
@@ -89,6 +90,9 @@ export type IssueLinkType =
|
||||
|
||||
export type IssuePriority = 'CRITICAL' | 'HIGH' | 'LOW' | 'LOWEST' | 'MEDIUM';
|
||||
|
||||
/** RECENT: last updated first. RANK: backlog order. */
|
||||
export type IssueSort = 'RANK' | 'RECENT';
|
||||
|
||||
export type IssueTypeOptionInput = {
|
||||
archived?: boolean;
|
||||
color: string;
|
||||
@@ -133,6 +137,14 @@ export type ProjectSettingsInput = {
|
||||
transitions: Array<StatusTransitionsInput>;
|
||||
};
|
||||
|
||||
export type RankIssueInput = {
|
||||
/** The ticket that ends up right above (null: the top) */
|
||||
aboveIssueId?: string | null | undefined;
|
||||
/** The ticket that ends up right below (null: the bottom) */
|
||||
belowIssueId?: string | null | undefined;
|
||||
issueId: string;
|
||||
};
|
||||
|
||||
export type ReleaseStatus = 'ARCHIVED' | 'PLANNED' | 'RELEASED';
|
||||
|
||||
/** What a status means, whatever each project calls it. DONE and CANCELLED are finished. */
|
||||
@@ -233,6 +245,7 @@ export type IssueSummaryFieldsFragment = {
|
||||
commentCount: number;
|
||||
updatedAt: string;
|
||||
version: number;
|
||||
rank: string;
|
||||
assignee: { id: string; name: string } | null;
|
||||
release: { id: string; name: string } | null;
|
||||
parent: { id: string; key: string; title: string } | null;
|
||||
@@ -258,6 +271,7 @@ export type IssueFieldsFragment = {
|
||||
commentCount: number;
|
||||
updatedAt: string;
|
||||
version: number;
|
||||
rank: string;
|
||||
reporter: { id: string; name: string };
|
||||
links: Array<{
|
||||
type: IssueLinkType;
|
||||
@@ -343,6 +357,7 @@ export type BoardQuery = {
|
||||
commentCount: number;
|
||||
updatedAt: string;
|
||||
version: number;
|
||||
rank: string;
|
||||
assignee: { id: string; name: string } | null;
|
||||
release: { id: string; name: string } | null;
|
||||
parent: { id: string; key: string; title: string } | null;
|
||||
@@ -376,6 +391,7 @@ export type IssueQuery = {
|
||||
commentCount: number;
|
||||
updatedAt: string;
|
||||
version: number;
|
||||
rank: string;
|
||||
reporter: { id: string; name: string };
|
||||
links: Array<{
|
||||
type: IssueLinkType;
|
||||
@@ -420,6 +436,7 @@ export type SearchIssuesQuery = {
|
||||
commentCount: number;
|
||||
updatedAt: string;
|
||||
version: number;
|
||||
rank: string;
|
||||
assignee: { id: string; name: string } | null;
|
||||
release: { id: string; name: string } | null;
|
||||
parent: { id: string; key: string; title: string } | null;
|
||||
@@ -570,6 +587,7 @@ export type MyIssuesQuery = {
|
||||
commentCount: number;
|
||||
updatedAt: string;
|
||||
version: number;
|
||||
rank: string;
|
||||
assignee: { id: string; name: string } | null;
|
||||
release: { id: string; name: string } | null;
|
||||
parent: { id: string; key: string; title: string } | null;
|
||||
@@ -597,6 +615,7 @@ export type IssueChildrenQuery = {
|
||||
commentCount: number;
|
||||
updatedAt: string;
|
||||
version: number;
|
||||
rank: string;
|
||||
assignee: { id: string; name: string } | null;
|
||||
release: { id: string; name: string } | null;
|
||||
parent: { id: string; key: string; title: string } | null;
|
||||
@@ -624,6 +643,7 @@ export type EpicsQuery = {
|
||||
commentCount: number;
|
||||
updatedAt: string;
|
||||
version: number;
|
||||
rank: string;
|
||||
assignee: { id: string; name: string } | null;
|
||||
release: { id: string; name: string } | null;
|
||||
parent: { id: string; key: string; title: string } | null;
|
||||
@@ -672,6 +692,43 @@ export type RemoveChecklistItemMutation = {
|
||||
removeChecklistItem: { id: string; key: string | null; version: number };
|
||||
};
|
||||
|
||||
export type BacklogQueryVariables = Exact<{
|
||||
filter: IssueFilterInput;
|
||||
}>;
|
||||
|
||||
export type BacklogQuery = {
|
||||
issues: {
|
||||
total: number;
|
||||
items: Array<{
|
||||
id: string;
|
||||
projectId: string;
|
||||
key: string;
|
||||
type: string;
|
||||
area: string | null;
|
||||
title: string;
|
||||
status: string;
|
||||
statusCategory: StatusCategory;
|
||||
priority: IssuePriority;
|
||||
labels: Array<string>;
|
||||
commentCount: number;
|
||||
updatedAt: string;
|
||||
version: number;
|
||||
rank: string;
|
||||
assignee: { id: string; name: string } | null;
|
||||
release: { id: string; name: string } | null;
|
||||
parent: { id: string; key: string; title: string } | null;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
|
||||
export type RankIssueMutationVariables = Exact<{
|
||||
input: RankIssueInput;
|
||||
}>;
|
||||
|
||||
export type RankIssueMutation = {
|
||||
rankIssue: { id: string; key: string | null; version: number };
|
||||
};
|
||||
|
||||
export type NotificationFieldsFragment = {
|
||||
id: string;
|
||||
kind: NotificationKind;
|
||||
@@ -1104,6 +1161,7 @@ export type IssueChangedSubscription = {
|
||||
commentCount: number;
|
||||
updatedAt: string;
|
||||
version: number;
|
||||
rank: string;
|
||||
assignee: { id: string; name: string } | null;
|
||||
release: { id: string; name: string } | null;
|
||||
parent: { id: string; key: string; title: string } | null;
|
||||
@@ -1478,6 +1536,7 @@ export const IssueSummaryFieldsFragmentDoc = {
|
||||
],
|
||||
},
|
||||
},
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'rank' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -1680,6 +1739,7 @@ export const IssueFieldsFragmentDoc = {
|
||||
],
|
||||
},
|
||||
},
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'rank' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -2094,6 +2154,7 @@ export const BoardDocument = {
|
||||
],
|
||||
},
|
||||
},
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'rank' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -2225,6 +2286,7 @@ export const IssueDocument = {
|
||||
],
|
||||
},
|
||||
},
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'rank' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -2475,6 +2537,7 @@ export const SearchIssuesDocument = {
|
||||
],
|
||||
},
|
||||
},
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'rank' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -3690,6 +3753,7 @@ export const MyIssuesDocument = {
|
||||
],
|
||||
},
|
||||
},
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'rank' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -3835,6 +3899,7 @@ export const IssueChildrenDocument = {
|
||||
],
|
||||
},
|
||||
},
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'rank' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -3998,6 +4063,7 @@ export const EpicsDocument = {
|
||||
],
|
||||
},
|
||||
},
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'rank' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -4324,6 +4390,223 @@ export const RemoveChecklistItemDocument = {
|
||||
RemoveChecklistItemMutation,
|
||||
RemoveChecklistItemMutationVariables
|
||||
>;
|
||||
export const BacklogDocument = {
|
||||
kind: 'Document',
|
||||
definitions: [
|
||||
{
|
||||
kind: 'OperationDefinition',
|
||||
operation: 'query',
|
||||
name: { kind: 'Name', value: 'Backlog' },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: 'VariableDefinition',
|
||||
variable: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'filter' },
|
||||
},
|
||||
type: {
|
||||
kind: 'NonNullType',
|
||||
type: {
|
||||
kind: 'NamedType',
|
||||
name: { kind: 'Name', value: 'IssueFilterInput' },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'issues' },
|
||||
arguments: [
|
||||
{
|
||||
kind: 'Argument',
|
||||
name: { kind: 'Name', value: 'filter' },
|
||||
value: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'filter' },
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'Argument',
|
||||
name: { kind: 'Name', value: 'limit' },
|
||||
value: { kind: 'IntValue', value: '200' },
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'items' },
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'FragmentSpread',
|
||||
name: { kind: 'Name', value: 'IssueSummaryFields' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'total' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'FragmentDefinition',
|
||||
name: { kind: 'Name', value: 'UserRefFields' },
|
||||
typeCondition: {
|
||||
kind: 'NamedType',
|
||||
name: { kind: 'Name', value: 'UserRef' },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'name' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'FragmentDefinition',
|
||||
name: { kind: 'Name', value: 'IssueSummaryFields' },
|
||||
typeCondition: {
|
||||
kind: 'NamedType',
|
||||
name: { kind: 'Name', value: 'Issue' },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'projectId' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'key' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'type' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'area' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'title' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'status' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'statusCategory' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'priority' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'labels' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'commentCount' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'updatedAt' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'version' } },
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'assignee' },
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'FragmentSpread',
|
||||
name: { kind: 'Name', value: 'UserRefFields' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'release' },
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'name' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'parent' },
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'key' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'title' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'rank' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<BacklogQuery, BacklogQueryVariables>;
|
||||
export const RankIssueDocument = {
|
||||
kind: 'Document',
|
||||
definitions: [
|
||||
{
|
||||
kind: 'OperationDefinition',
|
||||
operation: 'mutation',
|
||||
name: { kind: 'Name', value: 'RankIssue' },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: 'VariableDefinition',
|
||||
variable: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'input' },
|
||||
},
|
||||
type: {
|
||||
kind: 'NonNullType',
|
||||
type: {
|
||||
kind: 'NamedType',
|
||||
name: { kind: 'Name', value: 'RankIssueInput' },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'rankIssue' },
|
||||
arguments: [
|
||||
{
|
||||
kind: 'Argument',
|
||||
name: { kind: 'Name', value: 'input' },
|
||||
value: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'input' },
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'FragmentSpread',
|
||||
name: { kind: 'Name', value: 'CommandResultFields' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'FragmentDefinition',
|
||||
name: { kind: 'Name', value: 'CommandResultFields' },
|
||||
typeCondition: {
|
||||
kind: 'NamedType',
|
||||
name: { kind: 'Name', value: 'CommandResult' },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'id' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'key' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'version' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<RankIssueMutation, RankIssueMutationVariables>;
|
||||
export const MyNotificationsDocument = {
|
||||
kind: 'Document',
|
||||
definitions: [
|
||||
@@ -7020,6 +7303,7 @@ export const IssueChangedDocument = {
|
||||
],
|
||||
},
|
||||
},
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'rank' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -176,3 +176,18 @@ mutation RemoveChecklistItem($issueId: ID!, $itemId: ID!) {
|
||||
...CommandResultFields
|
||||
}
|
||||
}
|
||||
|
||||
query Backlog($filter: IssueFilterInput!) {
|
||||
issues(filter: $filter, limit: 200) {
|
||||
items {
|
||||
...IssueSummaryFields
|
||||
}
|
||||
total
|
||||
}
|
||||
}
|
||||
|
||||
mutation RankIssue($input: RankIssueInput!) {
|
||||
rankIssue(input: $input) {
|
||||
...CommandResultFields
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user