Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions graphql/mutations/issues.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ mutation CreateIssue($input: IssueCreateInput!) {
issueCreate(input: $input) {
success
issue {
...CompleteIssueWithCommentsFields
...CompleteIssueWithDefaultCommentsFields
}
}
}
Expand All @@ -28,7 +28,7 @@ mutation UpdateIssue($id: String!, $input: IssueUpdateInput!) {
issueUpdate(id: $id, input: $input) {
success
issue {
...CompleteIssueWithCommentsFields
...CompleteIssueWithDefaultCommentsFields
}
}
}
Expand All @@ -37,7 +37,7 @@ mutation ArchiveIssue($id: String!) {
issueArchive(id: $id) {
success
entity {
...CompleteIssueWithCommentsFields
...CompleteIssueWithDefaultCommentsFields
}
}
}
Expand All @@ -46,7 +46,7 @@ mutation UnarchiveIssue($id: String!) {
issueUnarchive(id: $id) {
success
entity {
...CompleteIssueWithCommentsFields
...CompleteIssueWithDefaultCommentsFields
}
}
}
Expand Down
70 changes: 60 additions & 10 deletions graphql/queries/issues.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,38 @@ fragment CompleteIssueFields on Issue {
}
}

# Minimal comment fields preserved for default issue reads
fragment IssueReadDefaultCommentFields on Comment {
id
body
}

# Comment fields for issue read discussion expansion
fragment IssueReadCommentFields on Comment {
id
body
createdAt
editedAt
parentId
user {
id
Comment thread
iamfj marked this conversation as resolved.
displayName
}
}

# Complete issue fragment with all relationships and default comments
#
# Preserves the historical issue read payload while keeping richer
# discussion metadata behind explicit flags.
fragment CompleteIssueWithDefaultCommentsFields on Issue {
...CompleteIssueFields
comments {
nodes {
...IssueReadDefaultCommentFields
}
}
}

# Complete issue fragment with all relationships and comments
#
# Combines all issue fragments into a comprehensive field selection.
Expand All @@ -99,8 +131,7 @@ fragment CompleteIssueWithCommentsFields on Issue {
...CompleteIssueFields
comments {
nodes {
id
body
...IssueReadCommentFields
}
}
}
Expand Down Expand Up @@ -210,21 +241,40 @@ query GetIssues($first: Int!, $after: String, $orderBy: PaginationOrderBy) {
}
}

# Get single issue by UUID with comments and all relationships
# Get single issue by UUID with lean fields
#
# Fetches complete issue data including comments by direct UUID lookup.
# Uses the comprehensive fragment with comment data for detailed view.
# Fetches issue data by direct UUID lookup with the backward-compatible
# default comment payload.
query GetIssueById($id: String!) {
issue(id: $id) {
...CompleteIssueWithCommentsFields
...CompleteIssueWithDefaultCommentsFields
}
}

# Get issue by identifier (team key + number)
# Get issue by identifier (team key + number) with lean fields
#
# Fetches issue using TEAM-123 format. Resolves team key and
# issue number to find the exact issue, returning complete data with comments.
# Fetches issue using TEAM-123 format with the backward-compatible
# default comment payload.
query GetIssueByIdentifier($teamKey: String!, $number: Float!) {
issues(
filter: { team: { key: { eq: $teamKey } }, number: { eq: $number } }
first: 1
) {
nodes {
...CompleteIssueWithDefaultCommentsFields
}
}
}

# Get single issue by UUID with full comments
query GetIssueByIdWithComments($id: String!) {
issue(id: $id) {
...CompleteIssueWithCommentsFields
}
}

# Get issue by identifier with full comments
query GetIssueByIdentifierWithComments($teamKey: String!, $number: Float!) {
issues(
filter: { team: { key: { eq: $teamKey } }, number: { eq: $number } }
first: 1
Expand Down Expand Up @@ -473,7 +523,7 @@ query BatchResolveForCreate(

# Complete issue fragment with attachments
fragment CompleteIssueWithAttachmentsFields on Issue {
...CompleteIssueWithCommentsFields
...CompleteIssueWithDefaultCommentsFields
attachments {
nodes {
...AttachmentFields
Expand Down
54 changes: 52 additions & 2 deletions src/commands/issues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ import {
getIssue,
getIssueByIdentifier,
getIssueByIdentifierWithAttachments,
getIssueByIdentifierWithComments,
getIssueByIdentifierWithCommentThreads,
getIssueWithAttachments,
getIssueWithComments,
getIssueWithCommentThreads,
listIssues,
searchIssues,
unarchiveIssue,
Expand Down Expand Up @@ -106,6 +110,12 @@ interface UpdateOptions {
removeRelation?: string;
}

interface ReadOptions {
withAttachments?: boolean;
withComments?: boolean;
withCommentThreads?: boolean;
}

export const ISSUES_META: DomainMeta = {
name: "issues",
summary: "work items with status, priority, assignee, labels",
Expand Down Expand Up @@ -375,6 +385,11 @@ export function setupIssuesCommands(program: Command): void {
.command("read <issue>")
.description("get full issue details including description")
.option("--with-attachments", "include issue attachments")
.option("--with-comments", "include full issue comments")
.option(
"--with-comment-threads",
"group issue comments into root comments with replies",
)
.addHelpText(
"after",
`\nWhen passing issue IDs, both UUID and identifiers like ABC-123 are supported.`,
Expand All @@ -383,7 +398,7 @@ export function setupIssuesCommands(program: Command): void {
handleCommand(async (...args: unknown[]) => {
const [issue, options, command] = args as [
string,
{ withAttachments?: boolean },
ReadOptions,
Command,
];
const ctx = createContext(command.parent!.parent!.opts());
Expand All @@ -401,7 +416,42 @@ export function setupIssuesCommands(program: Command): void {
);
outputSuccess(result);
}
} else if (isUuid(issue)) {
return;
}

if (options.withCommentThreads) {
if (isUuid(issue)) {
const result = await getIssueWithCommentThreads(ctx.gql, issue);
outputSuccess(result);
} else {
const { teamKey, issueNumber } = parseIssueIdentifier(issue);
const result = await getIssueByIdentifierWithCommentThreads(
ctx.gql,
teamKey,
issueNumber,
);
outputSuccess(result);
}
return;
}

if (options.withComments) {
if (isUuid(issue)) {
const result = await getIssueWithComments(ctx.gql, issue);
outputSuccess(result);
} else {
const { teamKey, issueNumber } = parseIssueIdentifier(issue);
const result = await getIssueByIdentifierWithComments(
ctx.gql,
teamKey,
issueNumber,
);
outputSuccess(result);
}
return;
}

if (isUuid(issue)) {
const result = await getIssue(ctx.gql, issue);
outputSuccess(result);
} else {
Expand Down
25 changes: 25 additions & 0 deletions src/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ import type {
GetInitiativeUpdateQuery,
GetIssueByIdentifierQuery,
GetIssueByIdentifierWithAttachmentsQuery,
GetIssueByIdentifierWithCommentsQuery,
GetIssueByIdQuery,
GetIssueByIdWithAttachmentsQuery,
GetIssueByIdWithCommentsQuery,
GetIssuesQuery,
GetProjectMilestoneByIdQuery,
GetProjectQuery,
Expand Down Expand Up @@ -76,6 +78,29 @@ export type TeamDetail = NonNullable<GetTeamByIdQuery["team"]> & {
export type Issue = GetIssuesQuery["issues"]["nodes"][0];
export type IssueDetail = NonNullable<GetIssueByIdQuery["issue"]>;
export type IssueByIdentifier = GetIssueByIdentifierQuery["issues"]["nodes"][0];
export type IssueDetailWithComments = NonNullable<
GetIssueByIdWithCommentsQuery["issue"]
>;
export type IssueByIdentifierWithComments =
GetIssueByIdentifierWithCommentsQuery["issues"]["nodes"][0];
export type IssueComment = NonNullable<
NonNullable<IssueDetailWithComments["comments"]>["nodes"][0]
>;
export type IssueCommentThread = IssueComment & {
replies: IssueCommentThread[];
};
export type IssueDetailWithCommentThreads = Omit<
IssueDetailWithComments,
"comments"
> & {
comments: { nodes: IssueCommentThread[] };
};
export type IssueByIdentifierWithCommentThreads = Omit<
IssueByIdentifierWithComments,
"comments"
> & {
comments: { nodes: IssueCommentThread[] };
};
export type IssueDetailWithAttachments = NonNullable<
GetIssueByIdWithAttachmentsQuery["issue"]
>;
Expand Down
Loading
Loading