feat: a sprint calendar
A Calendario tab lays the project's sprints out month by month as bands, with the releases and the tickets due on each day. Owners and admins plan a sprint by clicking the day it starts, and the whole plan downloads as an iCalendar file. Tickets can be searched by due-date range. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,7 @@ import {
|
||||
BoardDocument,
|
||||
ChangeIssueStatusDocument,
|
||||
CreateIssueDocument,
|
||||
DueIssuesDocument,
|
||||
EpicsDocument,
|
||||
IssueChildrenDocument,
|
||||
IssueDocument,
|
||||
@@ -183,6 +184,14 @@ export class GraphqlIssuesGateway extends IssuesGateway {
|
||||
);
|
||||
}
|
||||
|
||||
due(projectId: string, from: string, to: string): Observable<IssueSummary[]> {
|
||||
return this.gql
|
||||
.query(DueIssuesDocument, {
|
||||
filter: { projectId, dueFrom: from, dueTo: to },
|
||||
})
|
||||
.pipe(map((d) => d.issues.items.map(toIssueSummary)));
|
||||
}
|
||||
|
||||
trash(issueId: string): Observable<CommandResult> {
|
||||
return this.gql
|
||||
.mutate(TrashIssueDocument, { issueId })
|
||||
|
||||
@@ -149,6 +149,10 @@ export type IssueFilterInput = {
|
||||
areas?: Array<string> | null | undefined;
|
||||
assigneeId?: string | null | undefined;
|
||||
categories?: Array<StatusCategory> | null | undefined;
|
||||
/** Due on or after this day (YYYY-MM-DD) */
|
||||
dueFrom?: string | null | undefined;
|
||||
/** Due on or before this day (YYYY-MM-DD) */
|
||||
dueTo?: string | null | undefined;
|
||||
labels?: Array<string> | null | undefined;
|
||||
/** Only tickets in no sprint */
|
||||
noSprint?: boolean | null | undefined;
|
||||
@@ -1124,6 +1128,39 @@ export type TrashQuery = {
|
||||
}>;
|
||||
};
|
||||
|
||||
export type DueIssuesQueryVariables = Exact<{
|
||||
filter: IssueFilterInput;
|
||||
}>;
|
||||
|
||||
export type DueIssuesQuery = {
|
||||
issues: {
|
||||
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;
|
||||
storyPoints: number | null;
|
||||
startDate: string | null;
|
||||
dueDate: string | null;
|
||||
trashedAt: string | null;
|
||||
assignee: { id: string; name: string } | null;
|
||||
release: { id: string; name: string } | null;
|
||||
sprint: { id: string; name: string } | null;
|
||||
parent: { id: string; key: string; title: string } | null;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
|
||||
export type NotificationFieldsFragment = {
|
||||
id: string;
|
||||
kind: NotificationKind;
|
||||
@@ -6993,6 +7030,167 @@ export const TrashDocument = {
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<TrashQuery, TrashQueryVariables>;
|
||||
export const DueIssuesDocument = {
|
||||
kind: 'Document',
|
||||
definitions: [
|
||||
{
|
||||
kind: 'OperationDefinition',
|
||||
operation: 'query',
|
||||
name: { kind: 'Name', value: 'DueIssues' },
|
||||
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: '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: 'sprint' },
|
||||
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' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'storyPoints' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'startDate' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'dueDate' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'trashedAt' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<DueIssuesQuery, DueIssuesQueryVariables>;
|
||||
export const MyNotificationsDocument = {
|
||||
kind: 'Document',
|
||||
definitions: [
|
||||
|
||||
@@ -239,3 +239,11 @@ query Trash($projectId: ID!) {
|
||||
...IssueSummaryFields
|
||||
}
|
||||
}
|
||||
|
||||
query DueIssues($filter: IssueFilterInput!) {
|
||||
issues(filter: $filter, limit: 200) {
|
||||
items {
|
||||
...IssueSummaryFields
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user