Files
task-stream/apps/api/schema.gql
Josep Piguillem 8fe581ff3e feat: a dashboard for clients, behind a secret link
Owners and admins turn it on in Configuración → General, pick what it
shows (tickets per status, the sprint under way, release forecasts,
open epics and activity per day) and can renew or drop the link. Whoever
has it sees /share/{token} without an account: only those blocks, no
people nor single tickets but the epics, no links into the app, read
again every minute. A renewed or dropped link says it no longer works.

REST (GET/PUT/DELETE /api/projects/{project}/share and the public
GET /api/shared/dashboards/{token}) and GraphQL (dashboardShare,
shareDashboard, unshareDashboard, sharedDashboard). The token is kept
in dashboard_shares and goes with the project.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 10:39:26 +02:00

2110 lines
43 KiB
GraphQL
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ------------------------------------------------------
# THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
# ------------------------------------------------------
type Activity {
actor: UserRef!
id: ID!
issueId: ID
issueKey: String
occurredAt: String!
projectId: ID!
summary: String!
type: String!
}
"""How much happened on one day"""
type ActivityDay {
count: Int!
"""YYYY-MM-DD"""
day: String!
}
input AddChecklistItemInput {
issueId: ID!
text: String!
}
"""Something on a day of my week"""
type AgendaItem {
"""YYYY-MM-DD"""
day: String!
issueKey: String
"""ISSUE, SPRINT_END or RELEASE"""
kind: String!
projectKey: String!
title: String!
}
type Aging {
issues: [InsightIssue!]!
"""p85 cycle time of the last 90 days"""
thresholdDays: Float
}
type AreaOption {
archived: Boolean!
id: String!
name: String!
}
input AreaOptionInput {
archived: Boolean! = false
id: String!
name: String!
}
"""
A file on a ticket. Download it from the REST API: GET /api/issues/{issueId}/attachments/{id}
"""
type Attachment {
"""The comment it was attached to, if any"""
commentId: ID
fileName: String!
id: ID!
mimeType: String!
"""Bytes"""
size: Int!
uploadedAt: String!
uploadedBy: UserRef!
}
"""When this happens to a ticket, if it is like this, do that"""
type Automation {
action: AutomationActionSpec!
active: Boolean!
conditions: AutomationConditions!
createdAt: String!
createdBy: UserRef!
id: ID!
"""Why the last run did nothing"""
lastError: String
lastRunAt: String
name: String!
projectId: ID!
runs: Int!
trigger: AutomationTriggerSpec!
}
"""What a rule does to the ticket"""
enum AutomationAction {
ADD_COMMENT
ADD_LABEL
ADD_TO_ACTIVE_SPRINT
ASSIGN
ASSIGN_REPORTER
SET_PRIORITY
SET_STATUS
}
input AutomationActionInput {
kind: AutomationAction!
value: String
}
type AutomationActionSpec {
kind: AutomationAction!
"""Status, member id, label, priority or comment text"""
value: String
}
type AutomationConditions {
areas: [String!]
label: String
priorities: [IssuePriority!]
types: [String!]
}
input AutomationConditionsInput {
areas: [String!]
label: String
priorities: [IssuePriority!]
types: [String!]
}
"""What starts a rule"""
enum AutomationTrigger {
COMMENT_ADDED
ISSUE_ASSIGNED
ISSUE_CREATED
PULL_REQUEST_MERGED
STATUS_CHANGED
}
input AutomationTriggerInput {
kind: AutomationTrigger!
status: String
}
type AutomationTriggerSpec {
kind: AutomationTrigger!
"""STATUS_CHANGED: only into this status"""
status: String
}
type BacklogHealth {
noArea: Int!
open: Int!
stale: Int!
staleDays: Int!
unassigned: Int!
unestimated: Int!
}
type BoardColumn {
issues: [Issue!]!
status: String!
total: Int!
}
"""Bugs created and closed in a week"""
type BugWeek {
closed: Int!
created: Int!
"""Monday (UTC), YYYY-MM-DD"""
week: String!
}
type BulkIssueResult {
error: String
issueId: ID!
ok: Boolean!
}
type BulkResult {
results: [BulkIssueResult!]!
updated: Int!
}
input BulkUpdateIssuesInput {
addLabels: [String!]
assigneeId: ID
issueIds: [ID!]!
"""Out of their release"""
noRelease: Boolean
"""Back to the backlog"""
noSprint: Boolean
priority: IssuePriority
releaseId: ID
sprintId: ID
status: String
"""Unassign everything"""
unassign: Boolean
}
type CalendarFeed {
createdAt: String!
"""
Secret of the calendar URL: GET /api/calendar/{token}.ics (iCalendar, no session)
"""
token: String!
}
input ChangeIssueStatusInput {
issueId: ID!
"""Required by statuses with requiresReason"""
reason: String
"""A status id"""
status: String!
}
"""What happened after a moment"""
type ChangesSince {
created: [InsightIssue!]!
createdCount: Int!
finished: [InsightIssue!]!
finishedCount: Int!
"""Times I was mentioned here"""
mentions: Int!
"""Sent back from QA"""
returned: [InsightIssue!]!
returnedCount: Int!
since: String!
}
"""A step inside a ticket that does not deserve a ticket of its own"""
type ChecklistItem {
done: Boolean!
id: ID!
text: String!
}
"""How far the tickets hanging from one have got"""
type ChildProgress {
"""Done or cancelled"""
finished: Int!
finishedStoryPoints: Int!
percent: Int!
storyPoints: Int!
total: Int!
}
"""
Writes are acknowledged immediately; the read model catches up asynchronously. Wait for a subscription event with version >= this version to see the change.
"""
type CommandResult {
id: ID!
key: String
version: Int!
}
type Comment {
author: UserRef!
"""Empty on a deleted comment kept for its replies"""
body: String!
createdAt: String!
"""Deleted, but kept (without its text) while it has replies"""
deleted: Boolean!
editedAt: String
id: ID!
issueId: ID!
"""In the order they are offered; only the ones somebody chose"""
reactions: [CommentReaction!]!
"""The top-level comment it answers; null for a top-level one"""
replyTo: ID
version: Int!
}
type CommentChange {
action: String!
actorId: ID!
"""Null when deleted"""
comment: Comment
commentId: ID!
issueId: ID!
version: Int!
}
type CommentPage {
items: [Comment!]!
total: Int!
}
"""One emoji on a comment"""
type CommentReaction {
emoji: String!
"""Who chose it"""
users: [UserRef!]!
}
type CountByAssignee {
count: Int!
"""Null = unassigned"""
user: UserRef
}
type CountByKey {
count: Int!
key: String!
}
type CountByStatus {
count: Int!
"""Status id"""
key: String!
}
input CreateAutomationInput {
action: AutomationActionInput!
conditions: AutomationConditionsInput
name: String!
projectId: ID!
trigger: AutomationTriggerInput!
}
input CreateIssueInput {
"""An area id"""
area: String
assigneeId: ID
"""Only the fields to change"""
customFields: [CustomFieldValueInput!]
description: String
dueDate: String
"""Figma frame for UI changes"""
figmaUrl: String
labels: [String!]
"""Epic or ticket of the same project the new one breaks down"""
parentId: ID
priority: IssuePriority
projectId: ID!
releaseId: ID
"""When work should start (roadmap), YYYY-MM-DD"""
startDate: String
storyPoints: Int
"""A team id"""
team: String
title: String!
"""A ticket type id"""
type: String!
}
input CreateProjectInput {
description: String
"""Readable by anyone, also without signing in"""
isPublic: Boolean
"""2-10 chars, e.g. "TS" → tickets TS-1, TS-2..."""
key: String!
name: String!
"""
A project you own or administer to start from: its statuses, transitions, types, areas, custom fields and automation rules are copied
"""
templateProjectId: ID
}
input CreateReleaseInput {
description: String
"""e.g. v1.4.0"""
name: String!
projectId: ID!
targetDate: String
}
input CreateSprintInput {
"""YYYY-MM-DD, included"""
endDate: String!
goal: String
name: String!
projectId: ID!
"""YYYY-MM-DD"""
startDate: String!
}
input CreateWebhookInput {
"""Event names, family.* or *"""
events: [String!]!
projectId: ID!
url: String!
}
type CreatedResolved {
created: Int!
resolved: Int!
"""Monday of the week (YYYY-MM-DD)"""
week: String!
}
type CumulativeFlow {
dates: [String!]!
series: [FlowSeries!]!
}
"""A field the project adds to its tickets; its type never changes"""
type CustomFieldDefinition {
archived: Boolean!
id: String!
name: String!
"""The choices of a SELECT field"""
options: [String!]!
type: CustomFieldType!
}
input CustomFieldDefinitionInput {
archived: Boolean! = false
id: String!
name: String!
"""The choices of a SELECT field"""
options: [String!]! = []
type: CustomFieldType!
}
"""What a custom field holds (DATE: YYYY-MM-DD)"""
enum CustomFieldType {
DATE
NUMBER
SELECT
TEXT
}
"""A ticket's value of a field"""
type CustomFieldValue {
fieldId: String!
value: String!
}
"""A custom field's value; null or '' clears it"""
input CustomFieldValueInput {
fieldId: String!
value: String
}
"""The project's link for clients (owners and admins)"""
type DashboardShare {
blocks: [String!]!
createdAt: String!
projectId: ID!
"""Secret of the link /share/{token}"""
token: String!
}
"""A commit, branch or pull request that mentions the ticket"""
type DevLink {
at: String!
author: String
kind: DevLinkKind!
"""github, gitea or gitlab"""
provider: String!
"""Short SHA, branch or PR number"""
ref: String!
"""The project's name for the repository it came from"""
repository: String
state: DevLinkState
title: String!
url: String!
}
enum DevLinkKind {
BRANCH
COMMIT
PULL_REQUEST
}
enum DevLinkState {
CLOSED
MERGED
OPEN
}
type DurationStats {
averageDays: Float
count: Int!
"""85% of tickets take at most this long"""
p85Days: Float
}
"""A top-level ticket that groups others, with their progress"""
type Epic {
issue: Issue!
progress: ChildProgress!
}
"""Bugs reported in the 14 days after a release shipped"""
type EscapedBugs {
bugs: Int!
name: String!
releaseId: ID!
releasedAt: String!
}
"""Open tickets per option of a list custom field"""
type FieldBreakdown {
counts: [OptionCount!]!
fieldId: String!
"""Open tickets with no value"""
none: Int!
}
type FirstSteps {
calendar: Boolean!
invited: Boolean!
project: Boolean!
ticket: Boolean!
}
type FlowSeries {
counts: [Int!]!
status: String!
}
"""
Webhooks from GitHub, Gitea or GitLab: commits, branches and pull requests naming a ticket key are linked to it
"""
type GitIntegration {
"""On while there is at least one repository"""
enabled: Boolean!
repositories: [GitRepository!]!
"""Path of the webhook on this API, the same for every repository"""
webhookPath: String!
}
"""A repository whose webhooks the project accepts"""
type GitRepository {
createdAt: String!
id: ID!
"""The project's name for it: api, web, admin…"""
name: String!
"""Webhook secret of this repository; only owners and admins see it"""
secret: String
}
"""The home page beyond my work"""
type Home {
activity: [Activity!]!
agenda: [AgendaItem!]!
firstSteps: FirstSteps!
me: MyWeek!
online: [Online!]!
projects: [HomeProject!]!
sprints: [HomeSprint!]!
suggestions: [Suggestion!]!
team: TeamWeek!
}
type HomeProject {
"""Activity per day, last 14, oldest first"""
activity: [Int!]!
open: Int!
overdue: Int!
projectId: ID!
sprint: HomeSprintBrief
}
type HomeSprint {
projectKey: String!
projectName: String!
sprint: SprintPulse!
}
type HomeSprintBrief {
behind: Boolean!
name: String!
percent: Int!
}
"""A ticket the dashboard names, with the number that put it there"""
type InsightIssue {
assignee: UserRef
"""Days in its status (aging, stuck) or past / to its due date"""
days: Float!
dueDate: String
id: ID!
key: String!
status: String!
title: String!
}
type Issue {
"""Statuses this ticket can move to next (project's workflow)"""
allowedTransitions: [String!]!
"""An area id"""
area: String
assignee: UserRef
attachments: [Attachment!]!
"""
Open tickets it waits for: the ones that block it and the ones it depends on
"""
blockedBy: [IssueParent!]!
checklist: [ChecklistItem!]!
commentCount: Int!
comments(limit: Int! = 50, offset: Int! = 0): CommentPage!
createdAt: String!
"""Values of the project's custom fields (only those set)"""
customFields: [CustomFieldValue!]!
description: String
"""Commits, branches and pull requests that mention it"""
devLinks: [DevLink!]!
dueDate: String
figmaUrl: String
"""History of this ticket"""
history(limit: Int! = 50): [Activity!]!
id: ID!
"""Human key, e.g. TS-42"""
key: String!
labels: [String!]!
"""Linked tickets I can see"""
linkedIssues: [Issue!]!
links: [IssueLink!]!
number: Int!
"""The epic or ticket this one breaks down"""
parent: IssueParent
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!
sprint: SprintRef
"""When work should start (roadmap)"""
startDate: String
"""A status id of the project's settings"""
status: String!
statusCategory: StatusCategory!
storyPoints: Int
"""A team id"""
team: String
title: String!
"""In the trash since then; null when it is not"""
trashedAt: String
"""A ticket type id of the project's settings"""
type: String!
updatedAt: String!
version: Int!
"""Whether I voted for it"""
voted: Boolean!
"""How many want it done"""
votes: Int!
"""People following the ticket"""
watchers: [UserRef!]!
}
type IssueChange {
action: String!
actorId: ID!
issue: Issue!
version: Int!
}
input IssueFilterInput {
areas: [String!]
assigneeId: ID
categories: [StatusCategory!]
"""Only tickets holding these custom values"""
customFields: [CustomFieldValueInput!]
"""Due on or after this day (YYYY-MM-DD)"""
dueFrom: String
"""Due on or before this day (YYYY-MM-DD)"""
dueTo: String
labels: [String!]
"""Only tickets with no area"""
noArea: Boolean
"""Only tickets nobody estimated"""
noEstimate: Boolean
"""Only tickets in no sprint"""
noSprint: Boolean
"""Only the tickets hanging from this one"""
parentId: ID
priorities: [IssuePriority!]
projectId: ID!
releaseId: ID
"""Matches key, title or description"""
search: String
sort: IssueSort = RECENT
sprintId: ID
statuses: [String!]
"""Team ids"""
teams: [String!]
types: [String!]
unassigned: Boolean
"""Only tickets not updated since this instant (ISO)"""
updatedBefore: String
"""Only the tickets I follow (none when anonymous)"""
watching: Boolean
}
type IssueLink {
"""OUTWARD: this issue → other. INWARD: other → this issue."""
direction: LinkDirection!
issueId: ID!
issueKey: String!
type: IssueLinkType!
}
enum IssueLinkType {
BLOCKS
DEPENDS_ON
DUPLICATES
RELATES_TO
TESTS
}
type IssuePage {
items: [Issue!]!
total: Int!
}
"""The ticket another one hangs from"""
type IssueParent {
id: ID!
key: String!
title: String!
}
enum IssuePriority {
CRITICAL
HIGH
LOW
LOWEST
MEDIUM
}
"""RECENT: last updated first. RANK: backlog order."""
enum IssueSort {
RANK
RECENT
}
"""What a new ticket of the type starts with"""
type IssueTemplate {
"""Steps added to every new ticket"""
checklist: [String!]!
"""Used when the ticket comes without a description"""
description: String
}
input IssueTemplateInput {
checklist: [String!]! = []
description: String
}
type IssueTypeOption {
archived: Boolean!
color: String!
icon: String!
id: String!
name: String!
template: IssueTemplate
}
input IssueTypeOptionInput {
archived: Boolean! = false
color: String!
icon: String!
id: String!
name: String!
template: IssueTemplateInput
}
"""
Where the time of the tickets done in the last 90 days went, created to done
"""
type Journey {
"""Part of the lead time in an in-progress status, 0-100"""
flowEfficiency: Int
leadDays: Float
samples: Int!
steps: [JourneyStep!]!
}
type JourneyStep {
"""Average days a closed ticket spent in it"""
days: Float!
"""Its part of the whole lead time, 0-100"""
share: Int!
"""Status id of the project settings"""
status: String!
}
enum LinkDirection {
INWARD
OUTWARD
}
input LinkIssuesInput {
issueId: ID!
targetIssueId: ID!
type: IssueLinkType!
}
type Me {
"""Roles inside the task-stream OAuth client"""
appRoles: [String!]!
email: String!
id: ID!
isActive: Boolean!
name: String!
"""Global roles in Pireon"""
roles: [String!]!
}
type Mutation {
"""Add a step to a ticket's checklist"""
addChecklistItem(input: AddChecklistItemInput!): CommandResult!
"""
Comment on a ticket, or answer a comment (replyTo; one level of replies)
"""
addComment(body: String!, issueId: ID!, replyTo: ID): CommandResult!
"""
Add a repository with its own secret (the first turns the integration on)
"""
addGitRepository(name: String!, projectId: ID!): GitRepository!
"""Add a Pireon user to the project"""
addProjectMember(input: ProjectMemberInput!): CommandResult!
"""
Archive the project (owner only): everything stays readable, nothing changes until it is unarchived
"""
archiveProject(projectId: ID!): CommandResult!
archiveRelease(
"""
Open release of the same project that takes its unfinished tickets; none: they leave it
"""
moveTo: ID
releaseId: ID!
): CommandResult!
"""Assign (or unassign with null) a project member"""
assignIssue(assigneeId: ID, issueId: ID!): CommandResult!
"""
The same change on up to 100 tickets; each goes through the normal rules and may refuse on its own
"""
bulkUpdateIssues(input: BulkUpdateIssuesInput!): BulkResult!
"""Move a ticket through the workflow (incl. QA)"""
changeIssueStatus(input: ChangeIssueStatusInput!): CommandResult!
changeProjectMemberRole(input: ProjectMemberInput!): CommandResult!
"""
Close the sprint under way. Unfinished tickets move to carryOverToSprintId, or back to the backlog when it is null.
"""
closeSprint(carryOverToSprintId: ID, sprintId: ID!): CommandResult!
"""
Add a rule. It acts as TaskStream, within the workflow, and what it does triggers no other rule.
"""
createAutomation(input: CreateAutomationInput!): Automation!
"""A new secret calendar URL; the previous one stops working"""
createCalendarFeed: CalendarFeed!
createIssue(input: CreateIssueInput!): CommandResult!
createProject(input: CreateProjectInput!): CommandResult!
createRelease(input: CreateReleaseInput!): CommandResult!
"""Plan a sprint (owners and admins)"""
createSprint(input: CreateSprintInput!): CommandResult!
createWebhook(input: CreateWebhookInput!): WebhookCreated!
deleteAutomation(automationId: ID!): Boolean!
"""Author or project admin"""
deleteComment(commentId: ID!): CommandResult!
"""Delete one of my views"""
deleteMyView(id: ID!): Boolean!
"""
Delete the project (owner only), with its tickets, comments and releases. `confirmKey` must be the project key.
"""
deleteProject(confirmKey: String!, projectId: ID!): CommandResult!
"""Delete a view: whoever saved it, or an admin if it is shared"""
deleteSavedFilter(id: ID!): Boolean!
"""Delete a sprint that never started; its tickets go back to the backlog"""
deleteSprint(sprintId: ID!): CommandResult!
deleteWebhook(webhookId: ID!): Boolean!
"""
A new ticket like this one: what it says, not where it stands (the key comes in the result)
"""
duplicateIssue(issueId: ID!): CommandResult!
editComment(body: String!, commentId: ID!): CommandResult!
"""I left this ticket's page"""
leavePresence(issueId: ID!): Boolean!
"""Relate tickets, e.g. FE DEPENDS_ON BE, QA TESTS FE"""
linkIssues(input: LinkIssuesInput!): CommandResult!
"""Mark these as read, or all of them when `ids` is omitted"""
markNotificationsRead(ids: [ID!]): Boolean!
"""
Ship the release. Fails while it has unfinished tickets unless force = true.
"""
publishRelease(force: Boolean! = false, releaseId: ID!): CommandResult!
"""Move a ticket in the backlog, between two others of the same project"""
rankIssue(input: RankIssueInput!): CommandResult!
"""React (👍 👎 😄 🎉 😕 ❤️ 🚀 👀) or take it back (on: false)"""
reactToComment(commentId: ID!, emoji: String!, on: Boolean!): CommandResult!
"""
Remove a file from a ticket (whoever uploaded it, or an owner/admin). Upload with the REST API.
"""
removeAttachment(attachmentId: ID!, issueId: ID!): CommandResult!
removeChecklistItem(issueId: ID!, itemId: ID!): CommandResult!
"""Remove a repository; what it linked stays on the tickets"""
removeGitRepository(projectId: ID!, repositoryId: ID!): Boolean!
removeProjectMember(projectId: ID!, userId: ID!): CommandResult!
"""Back from the trash"""
restoreIssue(issueId: ID!): CommandResult!
"""My calendar URL stops working"""
revokeCalendarFeed: Boolean!
"""A new secret for a repository; the old one stops working"""
rotateGitRepositorySecret(projectId: ID!, repositoryId: ID!): GitRepository!
"""Save the current board filters under a name (members of the project)"""
saveFilter(input: SaveFilterInput!): ID!
"""Save a view of my work under a name"""
saveMyView(input: SaveMyViewInput!): ID!
"""
Place a ticket under an epic or another ticket of the same project (null: back to the top). Three levels at most: epic → ticket → subtask.
"""
setIssueParent(issueId: ID!, parentId: ID): CommandResult!
"""Put a ticket in a release (null removes it)"""
setIssueRelease(issueId: ID!, releaseId: ID): CommandResult!
"""Put a ticket in a sprint (null: back to the backlog)"""
setIssueSprint(issueId: ID!, sprintId: ID): CommandResult!
"""Turn the link on or change its blocks; renew for a new secret"""
shareDashboard(input: ShareDashboardInput!): DashboardShare!
"""Start a planned sprint (only one under way per project)"""
startSprint(sprintId: ID!): CommandResult!
"""Push on this device"""
subscribePush(subscription: PushSubscriptionInput!): Boolean!
"""Send a ping now and tell how it went"""
testWebhook(webhookId: ID!): WebhookDelivery!
"""
I am on this ticket (send every ~10 s while the page is open): answers who else is, the ones editing first
"""
touchPresence(editing: Boolean! = false, issueId: ID!): [Presence!]!
"""
To the trash (whoever opened it, or an owner/admin): hidden everywhere, deleted for good after 30 days
"""
trashIssue(issueId: ID!): CommandResult!
"""Bring an archived project back (owner only)"""
unarchiveProject(projectId: ID!): CommandResult!
unlinkIssues(input: LinkIssuesInput!): CommandResult!
"""The link stops working"""
unshareDashboard(projectId: ID!): Boolean!
"""Push off on this device"""
unsubscribePush(endpoint: String!): Boolean!
"""Stop following a ticket"""
unwatchIssue(issueId: ID!): CommandResult!
"""Rename, pause or resume"""
updateAutomation(active: Boolean, automationId: ID!, name: String): Automation!
"""Reword, tick or untick a checklist step"""
updateChecklistItem(input: UpdateChecklistItemInput!): CommandResult!
updateIssue(input: UpdateIssueInput!): CommandResult!
updateNotificationSettings(email: Boolean!): NotificationSettings!
updateProject(input: UpdateProjectInput!): CommandResult!
"""
Replace statuses, transitions, ticket types and areas (owners and admins). Values can be archived, not deleted.
"""
updateProjectSettings(projectId: ID!, settings: ProjectSettingsInput!): CommandResult!
updateRelease(input: UpdateReleaseInput!): CommandResult!
updateSprint(input: UpdateSprintInput!): CommandResult!
updateWebhook(input: UpdateWebhookInput!): Webhook!
"""
Vote for a ticket (on) or take the vote back: anyone signed in on a public project, members on a private one
"""
voteIssue(issueId: ID!, on: Boolean!): CommandResult!
"""
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 {
items: [Notification!]!
"""All my unread ones, not just these"""
unread: Int!
}
"""A named view of my work (private)"""
type MyView {
createdAt: String!
criteria: MyViewCriteria!
id: ID!
name: String!
}
"""What a view of my work narrows to; empty: every ticket of my projects"""
type MyViewCriteria {
assignedToMe: Boolean
"""Only types with the bug icon"""
bugsOnly: Boolean
categories: [StatusCategory!]
"""Only open tickets past due"""
overdue: Boolean
priorities: [IssuePriority!]
"""Empty: all my projects"""
projectIds: [ID!]
search: String
watching: Boolean
}
input MyViewCriteriaInput {
assignedToMe: Boolean
bugsOnly: Boolean
categories: [StatusCategory!]
overdue: Boolean
priorities: [IssuePriority!]
"""Empty: all my projects"""
projectIds: [ID!]
search: String
watching: Boolean
}
type MyWeek {
lastWeek: Int!
"""Days in a row with something finished"""
streak: Int!
thisWeek: Int!
}
type Notification {
actor: UserRef!
createdAt: String!
id: ID!
issueId: ID
issueKey: String
kind: NotificationKind!
projectId: ID!
projectKey: String!
"""Null: unread"""
readAt: String
summary: String!
}
"""Why someone is told"""
enum NotificationKind {
ADDED_TO_PROJECT
ASSIGNED
COMMENTED
MENTIONED
PROPOSED
QA_FAILED
REMINDER
REPLIED
STATUS_CHANGED
WATCHED
}
type NotificationSettings {
"""Copies by e-mail"""
email: Boolean!
"""Whether this installation can send e-mail at all"""
emailAvailable: Boolean!
"""
VAPID public key to subscribe a device to push with; null: no push here
"""
pushPublicKey: String
}
"""Someone of my projects on a ticket now"""
type Online {
at: String!
editing: Boolean!
issueKey: String
issueTitle: String
projectKey: String!
user: UserRef!
}
type OptionCount {
count: Int!
value: String!
}
"""
One of my active projects at a glance: open work, risks, sprint, releases and epics
"""
type PortfolioProject {
blocked: Int!
"""Open epics, nearest due date first (six at most)"""
epics: [Epic!]!
isPublic: Boolean!
key: String!
name: String!
open: Int!
overdue: Int!
projectId: ID!
releases: [ReleaseForecast!]!
sprint: SprintPulse
stuck: Int!
total: Int!
updatedAt: String!
}
"""Someone on a ticket right now (heartbeat in the last 30 s)"""
type Presence {
"""Last heartbeat"""
at: String!
"""Editing the ticket or writing a comment"""
editing: Boolean!
user: UserRef!
}
type PriorityCount {
count: Int!
priority: IssuePriority!
}
type Project {
"""Takes bug reports and ideas from anyone (while public)"""
acceptsProposals: Boolean!
"""
Statistics of the last `days` days (7-365): created vs resolved, cumulative flow, cycle/lead time, throughput and QA
"""
analytics(days: Int! = 90): ProjectAnalytics!
"""Archived since then (read-only); null while active"""
archivedAt: String
"""Kanban board, one column per status"""
board(
area: String
assigneeId: ID
labels: [String!]
perColumn: Int! = 50
priorities: [IssuePriority!]
releaseId: ID
"""Matches key, title or description"""
search: String
sprintId: ID
types: [String!]
unassigned: Boolean
): [BoardColumn!]!
createdAt: String!
description: String
id: ID!
"""
The dashboard: sprint under way, overdue / blocked / stuck tickets, aging and bottlenecks
"""
insights(
"""ISO instant (e.g. my last visit): also tell what changed after it"""
since: String
): ProjectInsights!
"""Anyone can read it, also without signing in"""
isPublic: Boolean!
key: String!
members: [ProjectMember!]!
name: String!
owner: UserRef!
releases: [Release!]!
settings: ProjectSettings!
"""Project tracking dashboard"""
stats: ProjectStats!
updatedAt: String!
version: Int!
}
type ProjectAnalytics {
createdVsResolved: [CreatedResolved!]!
"""Tickets in each status at the end of every day"""
cumulativeFlow: CumulativeFlow!
"""Start of work → done"""
cycleTime: DurationStats!
from: String!
"""Creation → done"""
leadTime: DurationStats!
qa: QaStats!
"""Finished per person and week"""
throughput: [Throughput!]!
to: String!
}
type ProjectChange {
action: String!
actorId: ID!
project: Project!
version: Int!
}
"""
The dashboard: sprint under way, risks, aging, bottlenecks, release forecasts, team load, backlog health and changes
"""
type ProjectInsights {
aging: Aging!
backlog: BacklogHealth!
bottlenecks: [StatusTime!]!
"""Only when asked with `since`"""
changes: ChangesSince
"""One per active list custom field"""
fields: [FieldBreakdown!]!
generatedAt: String!
"""The last 12 weeks from a Monday, oldest first (84 days)"""
heatmap: [ActivityDay!]!
journey: Journey!
"""Null while the project has never had a proposal"""
proposals: ProposalsFunnel
quality: Quality!
releases: [ReleaseForecast!]!
risks: Risks!
sprint: SprintPulse
team: [TeamLoad!]!
}
type ProjectMember {
role: ProjectRole!
user: UserRef!
}
input ProjectMemberInput {
projectId: ID!
role: ProjectRole! = MEMBER
"""Pireon user id"""
userId: ID!
}
enum ProjectRole {
ADMIN
GUEST
MEMBER
OWNER
VIEWER
}
"""
Statuses (board order), transitions, ticket types, areas and custom fields
"""
type ProjectSettings {
areas: [AreaOption!]!
"""
Definition of done: tickets reach DONE only with their checklist ticked (owners and admins may override)
"""
checklistBeforeDone: Boolean!
customFields: [CustomFieldDefinition!]!
issueTypes: [IssueTypeOption!]!
statuses: [WorkflowStatus!]!
teams: [Team!]!
transitions: [StatusTransitions!]!
}
"""
The whole configuration (replaces the previous one). Statuses go in board order.
"""
input ProjectSettingsInput {
areas: [AreaOptionInput!]!
"""
Definition of done: DONE only with the checklist ticked. Absent: unchanged
"""
checklistBeforeDone: Boolean
"""Absent: unchanged. Fields cannot be removed: archive them."""
customFields: [CustomFieldDefinitionInput!]
issueTypes: [IssueTypeOptionInput!]!
statuses: [WorkflowStatusInput!]!
"""Absent: unchanged. Teams cannot be removed: archive them."""
teams: [TeamInput!]
transitions: [StatusTransitionsInput!]!
}
type ProjectStats {
byArea: [CountByKey!]!
"""Open tickets per person"""
byAssignee: [CountByAssignee!]!
byPriority: [CountByKey!]!
byStatus: [CountByStatus!]!
byType: [CountByKey!]!
open: Int!
total: Int!
}
"""The public's proposals, from received to done or discarded"""
type ProposalsFunnel {
discarded: Int!
done: Int!
received: Int!
"""In the last 30 days"""
receivedLately: Int!
"""Most voted waiting, at most 5"""
topWaiting: [WaitingProposal!]!
underWay: Int!
"""Open, not started, in no sprint nor release"""
waiting: Int!
}
"""A browser's Web Push subscription (PushSubscription.toJSON())"""
input PushSubscriptionInput {
auth: String!
endpoint: String!
p256dh: String!
}
"""QA verdicts per area (90 days)"""
type QaByArea {
"""Null: no area"""
area: String
passed: Int!
returned: Int!
}
type QaStats {
failed: Int!
failureRate: Float
passed: Int!
}
"""Bugs (types with the bug icon) and QA"""
type Quality {
"""Last 8 weeks, oldest first"""
bugWeeks: [BugWeek!]!
escaped: [EscapedBugs!]!
openBugsByPriority: [PriorityCount!]!
qaByArea: [QaByArea!]!
}
type Query {
"""Project activity feed (newest first)"""
activity(
"""ISO date cursor"""
before: String
issueId: ID
limit: Int! = 50
projectId: ID!
): [Activity!]!
automations(projectId: ID!): [Automation!]!
"""The project's link for clients, if any (owners and admins)"""
dashboardShare(projectId: ID!): DashboardShare
"""
A project's epics: top-level tickets that group others, with the progress of those
"""
epics(projectId: ID!): [Epic!]!
gitIntegration(projectId: ID!): GitIntegration!
issue(
"""Issue id or key (e.g. "TS-42")"""
idOrKey: String!
): Issue!
issues(filter: IssueFilterInput!, limit: Int! = 50, offset: Int! = 0): IssuePage!
"""The authenticated user"""
me: Me!
"""My calendar URL, if I made one"""
myCalendarFeed: CalendarFeed
"""
The home page beyond my work: agenda of the week, activity of my projects, the week in numbers, sprints, project cards, suggestions, first steps and who is on what now
"""
myHome: Home!
"""
Tickets across all my projects: assigned to me (default) or any (e.g. categories [QA])
"""
myIssues(
assignedToMe: Boolean! = true
"""Only types drawn with the bug icon"""
bugsOnly: Boolean
categories: [StatusCategory!]
limit: Int! = 50
offset: Int! = 0
"""Only open tickets past their due date"""
overdue: Boolean
priorities: [IssuePriority!]
"""Only these of my projects"""
projectIds: [ID!]
"""Matches key, title or description (global search)"""
search: String
statuses: [String!]
"""Only the tickets I follow"""
watching: Boolean! = false
): IssuePage!
"""My notifications, newest first, and how many are unread"""
myNotifications(limit: Int! = 30, unreadOnly: Boolean! = false): MyNotifications!
"""Projects where I am a member"""
myProjects: [Project!]!
"""My saved views of my work, across my projects"""
myViews: [MyView!]!
notificationSettings: NotificationSettings!
"""
My active projects (not archived, not as a guest) at a glance: open work, risks, sprint, release forecasts and epics
"""
portfolio: [PortfolioProject!]!
"""A project of mine, or any public one (no sign-in needed)"""
project(
"""Project id or key (e.g. "TS")"""
idOrKey: String!
): Project!
"""Projects anyone can read, also without signing in"""
publicProjects: [Project!]!
release(id: ID!): Release!
"""My views of the project's board, then the shared ones"""
savedFilters(projectId: ID!): [SavedFilter!]!
"""What a link for clients shows, for anyone with it"""
sharedDashboard(token: String!): SharedDashboard
sprint(id: ID!): Sprint!
"""
A project's sprints: the one under way, the planned ones, then the closed ones
"""
sprints(projectId: ID!): [Sprint!]!
"""A project's tickets in the trash, newest first"""
trash(projectId: ID!): [Issue!]!
"""Planned releases of all my projects, soonest target date first"""
upcomingReleases(limit: Int! = 10): [Release!]!
user(id: String!): User
"""Search users by name or email (to assign / add members)"""
users(limit: Int! = 20, search: String! = ""): [User!]!
"""Event names a webhook can subscribe to (also family.* and *)"""
webhookEvents: [String!]!
webhooks(projectId: ID!): [Webhook!]!
}
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!
createdAt: String!
description: String
id: ID!
"""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
status: ReleaseStatus!
targetDate: String
version: Int!
}
type ReleaseBurnup {
dates: [String!]!
done: [Int!]!
releaseId: ID!
"""Tickets in the release (not cancelled)"""
scope: [Int!]!
targetDate: String
}
type ReleaseChange {
action: String!
actorId: ID!
release: Release!
version: Int!
}
"""When a planned release would be finished, at the real pace"""
type ReleaseForecast {
finished: Int!
"""Half of the simulated futures finish by then"""
likelyDate: String
name: String!
releaseId: ID!
"""85% finish by then"""
safeDate: String
targetDate: String
total: Int!
"""DONE, NO_DATE, UNKNOWN (no pace yet), ON_TRACK, AT_RISK or LATE"""
verdict: String!
}
type ReleaseProgress {
byStatus: [CountByStatus!]!
finished: Int!
percent: Int!
total: Int!
}
type ReleaseRef {
id: ID!
name: String!
}
enum ReleaseStatus {
ARCHIVED
PLANNED
RELEASED
}
"""Tickets to look at now (worst first)"""
type Risks {
blocked: [InsightIssue!]!
blockedCount: Int!
"""Due within 7 days"""
dueSoon: [InsightIssue!]!
dueSoonCount: Int!
overdue: [InsightIssue!]!
overdueCount: Int!
stuck: [InsightIssue!]!
stuckCount: Int!
stuckDays: Int!
}
input SaveFilterInput {
criteria: SavedFilterCriteriaInput!
name: String!
projectId: ID!
"""Visible to the whole project (members, not viewers, share)"""
shared: Boolean! = false
}
input SaveMyViewInput {
criteria: MyViewCriteriaInput!
name: String!
}
"""
A named view of the board: private to whoever saved it, or shared with the project
"""
type SavedFilter {
createdAt: String!
criteria: SavedFilterCriteria!
id: ID!
name: String!
owner: UserRef!
projectId: ID!
shared: Boolean!
}
"""What a saved view narrows the board to; empty: everything"""
type SavedFilterCriteria {
areas: [String!]
assigneeId: ID
labels: [String!]
priorities: [IssuePriority!]
releaseId: ID
search: String
types: [String!]
unassigned: Boolean
}
input SavedFilterCriteriaInput {
areas: [String!]
assigneeId: ID
labels: [String!]
priorities: [IssuePriority!]
releaseId: ID
search: String
types: [String!]
unassigned: Boolean
}
"""The project's link for clients"""
input ShareDashboardInput {
"""Blocks to show: flow, sprint, releases, epics, activity"""
blocks: [String!]!
projectId: ID!
"""A new secret: the old link stops working"""
renew: Boolean
}
"""
What a link for clients shows: only the chosen blocks (the others are null)
"""
type SharedDashboard {
activity: [ActivityDay!]
blocks: [String!]!
epics: [SharedEpic!]
flow: [SharedFlow!]
generatedAt: String!
projectKey: String!
projectName: String!
releases: [ReleaseForecast!]
sprint: SprintPulse
}
"""An open epic and how far it got"""
type SharedEpic {
done: Int!
dueDate: String
key: String!
percent: Int!
title: String!
total: Int!
}
"""Tickets in one status"""
type SharedFlow {
"""A colour name of the project settings"""
color: String!
count: Int!
name: String!
status: String!
}
"""A time-boxed iteration: the tickets the team commits to"""
type Sprint {
burndown: SprintBurndown!
closedAt: String
createdAt: String!
"""Last day (included), YYYY-MM-DD"""
endDate: String!
goal: String
id: ID!
"""Tickets in this sprint, in backlog order"""
issues(limit: Int! = 100, offset: Int! = 0): IssuePage!
name: String!
progress: SprintProgress!
projectId: ID!
"""First day, YYYY-MM-DD"""
startDate: String!
startedAt: String
status: SprintStatus!
version: Int!
}
"""
Per day of the sprint: tickets in it, still open, and their points (null: days to come). `ideal` falls from the first day to zero.
"""
type SprintBurndown {
dates: [String!]!
ideal: [Float!]!
remaining: [Int]!
remainingPoints: [Int]!
scope: [Int]!
sprintId: ID!
}
type SprintChange {
action: String!
actorId: ID!
"""Null: deleted"""
sprint: Sprint
sprintId: ID!
version: Int!
}
type SprintProgress {
"""Done or cancelled"""
finished: Int!
finishedStoryPoints: Int!
percent: Int!
storyPoints: Int!
total: Int!
}
"""The sprint under way: time gone against work done"""
type SprintPulse {
"""At this pace, what is open would not be closed"""
behind: Boolean!
"""Days left after today"""
daysLeft: Int!
donePoints: Int!
doneTickets: Int!
endDate: String!
name: String!
neededPerDay: Float
pacePerDay: Float
points: Int!
sprintId: ID!
startDate: String!
tickets: Int!
totalDays: Int!
}
type SprintRef {
id: ID!
name: String!
}
"""PLANNED, ACTIVE (one per project at a time) or CLOSED"""
enum SprintStatus {
ACTIVE
CLOSED
PLANNED
}
"""
What a status means, whatever each project calls it. DONE and CANCELLED are finished.
"""
enum StatusCategory {
CANCELLED
DONE
IN_PROGRESS
QA
TODO
}
type StatusTime {
averageDays: Float
open: Int!
overLimit: Boolean!
samples: Int!
status: String!
wipLimit: Int
}
type StatusTransitions {
from: String!
to: [String!]!
}
input StatusTransitionsInput {
from: String!
to: [String!]!
}
type Subscription {
"""Live activity feed"""
activityAdded(projectId: ID!): Activity!
commentChanged(issueId: ID!): CommentChange!
"""Tickets created/updated/moved in a project (board in realtime)"""
issueChanged(projectId: ID!): IssueChange!
"""Projects I am added to / removed from"""
myProjectsChanged: ProjectChange!
"""My notifications, as they happen"""
notificationAdded: Notification!
projectChanged(projectId: ID!): ProjectChange!
releaseChanged(projectId: ID!): ReleaseChange!
sprintChanged(projectId: ID!): SprintChange!
}
type Suggestion {
count: Int!
"""UNASSIGNED, PROPOSALS or QA_WAITING"""
kind: String!
projectKey: String!
}
"""A group of members: tickets go to it; @team mentions tell them all"""
type Team {
archived: Boolean!
id: String!
memberIds: [ID!]!
name: String!
}
input TeamInput {
archived: Boolean! = false
id: String!
memberIds: [ID!]! = []
name: String!
}
type TeamLoad {
blocked: Int!
openPoints: Int!
openTickets: Int!
overloaded: Boolean!
"""Points closed per sprint, last three closed sprints"""
pointsPerSprint: Float
user: UserRef!
}
type TeamWeek {
lastWeek: Int!
"""Finished per day, last 14, oldest first"""
perDay: [Int!]!
thisWeek: Int!
}
type Throughput {
count: Int!
user: UserRef
week: String!
}
input UpdateChecklistItemInput {
done: Boolean
issueId: ID!
itemId: ID!
text: String
}
input UpdateIssueInput {
"""An area id"""
area: String
"""Only the fields to change"""
customFields: [CustomFieldValueInput!]
description: String
dueDate: String
"""Figma frame for UI changes"""
figmaUrl: String
issueId: ID!
labels: [String!]
priority: IssuePriority
"""When work should start (roadmap), YYYY-MM-DD"""
startDate: String
storyPoints: Int
"""A team id"""
team: String
title: String
type: String
}
input UpdateProjectInput {
"""
Take bug reports and ideas from anyone through the public form (while public)
"""
acceptsProposals: Boolean
description: String
isPublic: Boolean
name: String
projectId: ID!
}
input UpdateReleaseInput {
description: String
name: String
releaseId: ID!
targetDate: String
}
input UpdateSprintInput {
endDate: String
goal: String
name: String
sprintId: ID!
startDate: String
}
input UpdateWebhookInput {
active: Boolean
events: [String!]
url: String
webhookId: ID!
}
"""User managed by the Pireon identity server"""
type User {
email: String!
id: ID!
isActive: Boolean!
name: String!
}
type UserRef {
id: ID!
name: String!
}
type WaitingProposal {
"""Days since it came in"""
days: Int!
id: ID!
key: String!
title: String!
votes: Int!
}
"""
Where the project tells another system what happened (signed POST, HMAC-SHA256)
"""
type Webhook {
active: Boolean!
createdAt: String!
createdBy: UserRef!
"""Event names, family.* or *"""
events: [String!]!
id: ID!
lastDelivery: WebhookDelivery
projectId: ID!
url: String!
}
type WebhookCreated {
id: ID!
"""Signs every delivery. Shown only now: keep it."""
secret: String!
}
type WebhookDelivery {
at: String!
error: String
event: String!
ok: Boolean!
"""HTTP status"""
status: Int
}
type WorkflowStatus {
"""No new tickets or transitions into it"""
archived: Boolean!
category: StatusCategory!
"""Colour token: slate, violet, green…"""
color: String!
"""Stable id stored on tickets, e.g. IN_REVIEW"""
id: String!
name: String!
"""Entering it needs a reason"""
requiresReason: Boolean!
"""
Work-in-progress limit: most tickets the column should hold (a warning, not a lock)
"""
wipLimit: Int
}
input WorkflowStatusInput {
archived: Boolean! = false
category: StatusCategory!
color: String!
id: String!
name: String!
requiresReason: Boolean! = false
"""Work-in-progress limit (null: none)"""
wipLimit: Int
}