diff --git a/src/renderer/utils/api/client.ts b/src/renderer/utils/api/client.ts index 5187108cf..8a27126fc 100644 --- a/src/renderer/utils/api/client.ts +++ b/src/renderer/utils/api/client.ts @@ -207,9 +207,9 @@ export async function fetchIssueByNumber( notification.account.token, FetchIssueByNumberDocument, { - owner: notification.repository.owner.login, - name: notification.repository.name, - number: number, + ownerINDEX: notification.repository.owner.login, + nameINDEX: notification.repository.name, + numberINDEX: number, firstLabels: 100, lastComments: 1, }, @@ -230,11 +230,11 @@ export async function fetchPullByNumber( notification.account.token, FetchPullRequestByNumberDocument, { - owner: notification.repository.owner.login, - name: notification.repository.name, - number: number, - firstLabels: 100, + ownerINDEX: notification.repository.owner.login, + nameINDEX: notification.repository.name, + numberINDEX: number, firstClosingIssues: 100, + firstLabels: 100, lastComments: 1, lastReviews: 100, }, @@ -255,12 +255,12 @@ export async function fetchDiscussionByNumber( notification.account.token, FetchDiscussionByNumberDocument, { - owner: notification.repository.owner.login, - name: notification.repository.name, - number: number, + ownerINDEX: notification.repository.owner.login, + nameINDEX: notification.repository.name, + numberINDEX: number, + firstLabels: 100, lastComments: 10, lastReplies: 10, - firstLabels: 100, includeIsAnswered: isAnsweredDiscussionFeatureSupported( notification.account, ), diff --git a/src/renderer/utils/api/graphql/discussion.graphql b/src/renderer/utils/api/graphql/discussion.graphql index 63a70304d..d906db39b 100644 --- a/src/renderer/utils/api/graphql/discussion.graphql +++ b/src/renderer/utils/api/graphql/discussion.graphql @@ -1,14 +1,18 @@ query FetchDiscussionByNumber( - $owner: String! - $name: String! - $number: Int! + $ownerINDEX: String! + $nameINDEX: String! + $numberINDEX: Int! $lastComments: Int $lastReplies: Int $firstLabels: Int $includeIsAnswered: Boolean! ) { - repository(owner: $owner, name: $name) { - discussion(number: $number) { + ...DiscussionMergeQuery +} + +fragment DiscussionMergeQuery on Query { + nodeINDEX: repository(owner: $ownerINDEX, name: $nameINDEX) { + discussion(number: $numberINDEX) { ...DiscussionDetails } } diff --git a/src/renderer/utils/api/graphql/generated/gql.ts b/src/renderer/utils/api/graphql/generated/gql.ts index bc49c280c..e2f6853a9 100644 --- a/src/renderer/utils/api/graphql/generated/gql.ts +++ b/src/renderer/utils/api/graphql/generated/gql.ts @@ -16,16 +16,16 @@ import * as types from './graphql'; */ type Documents = { "fragment AuthorFields on Actor {\n login\n html_url: url\n avatar_url: avatarUrl\n type: __typename\n}\n\nfragment MilestoneFields on Milestone {\n state\n title\n}": typeof types.AuthorFieldsFragmentDoc, - "query FetchDiscussionByNumber($owner: String!, $name: String!, $number: Int!, $lastComments: Int, $lastReplies: Int, $firstLabels: Int, $includeIsAnswered: Boolean!) {\n repository(owner: $owner, name: $name) {\n discussion(number: $number) {\n ...DiscussionDetails\n }\n }\n}\n\nfragment DiscussionDetails on Discussion {\n __typename\n number\n title\n stateReason\n isAnswered @include(if: $includeIsAnswered)\n url\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n ...DiscussionCommentFields\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n}\n\nfragment CommentFields on DiscussionComment {\n databaseId\n createdAt\n author {\n ...AuthorFields\n }\n url\n}\n\nfragment DiscussionCommentFields on DiscussionComment {\n ...CommentFields\n replies(last: $lastReplies) {\n totalCount\n nodes {\n ...CommentFields\n }\n }\n}": typeof types.FetchDiscussionByNumberDocument, - "query FetchIssueByNumber($owner: String!, $name: String!, $number: Int!, $lastComments: Int, $firstLabels: Int) {\n repository(owner: $owner, name: $name) {\n issue(number: $number) {\n ...IssueDetails\n }\n }\n}\n\nfragment IssueDetails on Issue {\n __typename\n number\n title\n url\n state\n stateReason\n milestone {\n ...MilestoneFields\n }\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n url\n author {\n ...AuthorFields\n }\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n}": typeof types.FetchIssueByNumberDocument, - "query FetchPullRequestByNumber($owner: String!, $name: String!, $number: Int!, $firstLabels: Int, $lastComments: Int, $lastReviews: Int, $firstClosingIssues: Int) {\n repository(owner: $owner, name: $name) {\n pullRequest(number: $number) {\n ...PullRequestDetails\n }\n }\n}\n\nfragment PullRequestDetails on PullRequest {\n __typename\n number\n title\n url\n state\n merged\n isDraft\n isInMergeQueue\n milestone {\n ...MilestoneFields\n }\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n url\n author {\n ...AuthorFields\n }\n }\n }\n reviews(last: $lastReviews) {\n totalCount\n nodes {\n ...PullRequestReviewFields\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n closingIssuesReferences(first: $firstClosingIssues) {\n nodes {\n number\n }\n }\n}\n\nfragment PullRequestReviewFields on PullRequestReview {\n state\n author {\n login\n }\n}": typeof types.FetchPullRequestByNumberDocument, + "query FetchDiscussionByNumber($ownerINDEX: String!, $nameINDEX: String!, $numberINDEX: Int!, $lastComments: Int, $lastReplies: Int, $firstLabels: Int, $includeIsAnswered: Boolean!) {\n ...DiscussionMergeQuery\n}\n\nfragment DiscussionMergeQuery on Query {\n nodeINDEX: repository(owner: $ownerINDEX, name: $nameINDEX) {\n discussion(number: $numberINDEX) {\n ...DiscussionDetails\n }\n }\n}\n\nfragment DiscussionDetails on Discussion {\n __typename\n number\n title\n stateReason\n isAnswered @include(if: $includeIsAnswered)\n url\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n ...DiscussionCommentFields\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n}\n\nfragment CommentFields on DiscussionComment {\n databaseId\n createdAt\n author {\n ...AuthorFields\n }\n url\n}\n\nfragment DiscussionCommentFields on DiscussionComment {\n ...CommentFields\n replies(last: $lastReplies) {\n totalCount\n nodes {\n ...CommentFields\n }\n }\n}": typeof types.FetchDiscussionByNumberDocument, + "query FetchIssueByNumber($ownerINDEX: String!, $nameINDEX: String!, $numberINDEX: Int!, $lastComments: Int, $firstLabels: Int) {\n ...IssueMergeQuery\n}\n\nfragment IssueMergeQuery on Query {\n nodeINDEX: repository(owner: $ownerINDEX, name: $nameINDEX) {\n issue(number: $numberINDEX) {\n ...IssueDetails\n }\n }\n}\n\nfragment IssueDetails on Issue {\n __typename\n number\n title\n url\n state\n stateReason\n milestone {\n ...MilestoneFields\n }\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n url\n author {\n ...AuthorFields\n }\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n}": typeof types.FetchIssueByNumberDocument, + "query FetchPullRequestByNumber($ownerINDEX: String!, $nameINDEX: String!, $numberINDEX: Int!, $firstLabels: Int, $lastComments: Int, $lastReviews: Int, $firstClosingIssues: Int) {\n ...PullRequestMergeQuery\n}\n\nfragment PullRequestMergeQuery on Query {\n nodeINDEX: repository(owner: $ownerINDEX, name: $nameINDEX) {\n pullRequest(number: $numberINDEX) {\n ...PullRequestDetails\n }\n }\n}\n\nfragment PullRequestDetails on PullRequest {\n __typename\n number\n title\n url\n state\n merged\n isDraft\n isInMergeQueue\n milestone {\n ...MilestoneFields\n }\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n url\n author {\n ...AuthorFields\n }\n }\n }\n reviews(last: $lastReviews) {\n totalCount\n nodes {\n ...PullRequestReviewFields\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n closingIssuesReferences(first: $firstClosingIssues) {\n nodes {\n number\n }\n }\n}\n\nfragment PullRequestReviewFields on PullRequestReview {\n state\n author {\n login\n }\n}": typeof types.FetchPullRequestByNumberDocument, "query FetchAuthenticatedUserDetails {\n viewer {\n id\n name\n login\n avatarUrl\n }\n}": typeof types.FetchAuthenticatedUserDetailsDocument, }; const documents: Documents = { "fragment AuthorFields on Actor {\n login\n html_url: url\n avatar_url: avatarUrl\n type: __typename\n}\n\nfragment MilestoneFields on Milestone {\n state\n title\n}": types.AuthorFieldsFragmentDoc, - "query FetchDiscussionByNumber($owner: String!, $name: String!, $number: Int!, $lastComments: Int, $lastReplies: Int, $firstLabels: Int, $includeIsAnswered: Boolean!) {\n repository(owner: $owner, name: $name) {\n discussion(number: $number) {\n ...DiscussionDetails\n }\n }\n}\n\nfragment DiscussionDetails on Discussion {\n __typename\n number\n title\n stateReason\n isAnswered @include(if: $includeIsAnswered)\n url\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n ...DiscussionCommentFields\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n}\n\nfragment CommentFields on DiscussionComment {\n databaseId\n createdAt\n author {\n ...AuthorFields\n }\n url\n}\n\nfragment DiscussionCommentFields on DiscussionComment {\n ...CommentFields\n replies(last: $lastReplies) {\n totalCount\n nodes {\n ...CommentFields\n }\n }\n}": types.FetchDiscussionByNumberDocument, - "query FetchIssueByNumber($owner: String!, $name: String!, $number: Int!, $lastComments: Int, $firstLabels: Int) {\n repository(owner: $owner, name: $name) {\n issue(number: $number) {\n ...IssueDetails\n }\n }\n}\n\nfragment IssueDetails on Issue {\n __typename\n number\n title\n url\n state\n stateReason\n milestone {\n ...MilestoneFields\n }\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n url\n author {\n ...AuthorFields\n }\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n}": types.FetchIssueByNumberDocument, - "query FetchPullRequestByNumber($owner: String!, $name: String!, $number: Int!, $firstLabels: Int, $lastComments: Int, $lastReviews: Int, $firstClosingIssues: Int) {\n repository(owner: $owner, name: $name) {\n pullRequest(number: $number) {\n ...PullRequestDetails\n }\n }\n}\n\nfragment PullRequestDetails on PullRequest {\n __typename\n number\n title\n url\n state\n merged\n isDraft\n isInMergeQueue\n milestone {\n ...MilestoneFields\n }\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n url\n author {\n ...AuthorFields\n }\n }\n }\n reviews(last: $lastReviews) {\n totalCount\n nodes {\n ...PullRequestReviewFields\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n closingIssuesReferences(first: $firstClosingIssues) {\n nodes {\n number\n }\n }\n}\n\nfragment PullRequestReviewFields on PullRequestReview {\n state\n author {\n login\n }\n}": types.FetchPullRequestByNumberDocument, + "query FetchDiscussionByNumber($ownerINDEX: String!, $nameINDEX: String!, $numberINDEX: Int!, $lastComments: Int, $lastReplies: Int, $firstLabels: Int, $includeIsAnswered: Boolean!) {\n ...DiscussionMergeQuery\n}\n\nfragment DiscussionMergeQuery on Query {\n nodeINDEX: repository(owner: $ownerINDEX, name: $nameINDEX) {\n discussion(number: $numberINDEX) {\n ...DiscussionDetails\n }\n }\n}\n\nfragment DiscussionDetails on Discussion {\n __typename\n number\n title\n stateReason\n isAnswered @include(if: $includeIsAnswered)\n url\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n ...DiscussionCommentFields\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n}\n\nfragment CommentFields on DiscussionComment {\n databaseId\n createdAt\n author {\n ...AuthorFields\n }\n url\n}\n\nfragment DiscussionCommentFields on DiscussionComment {\n ...CommentFields\n replies(last: $lastReplies) {\n totalCount\n nodes {\n ...CommentFields\n }\n }\n}": types.FetchDiscussionByNumberDocument, + "query FetchIssueByNumber($ownerINDEX: String!, $nameINDEX: String!, $numberINDEX: Int!, $lastComments: Int, $firstLabels: Int) {\n ...IssueMergeQuery\n}\n\nfragment IssueMergeQuery on Query {\n nodeINDEX: repository(owner: $ownerINDEX, name: $nameINDEX) {\n issue(number: $numberINDEX) {\n ...IssueDetails\n }\n }\n}\n\nfragment IssueDetails on Issue {\n __typename\n number\n title\n url\n state\n stateReason\n milestone {\n ...MilestoneFields\n }\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n url\n author {\n ...AuthorFields\n }\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n}": types.FetchIssueByNumberDocument, + "query FetchPullRequestByNumber($ownerINDEX: String!, $nameINDEX: String!, $numberINDEX: Int!, $firstLabels: Int, $lastComments: Int, $lastReviews: Int, $firstClosingIssues: Int) {\n ...PullRequestMergeQuery\n}\n\nfragment PullRequestMergeQuery on Query {\n nodeINDEX: repository(owner: $ownerINDEX, name: $nameINDEX) {\n pullRequest(number: $numberINDEX) {\n ...PullRequestDetails\n }\n }\n}\n\nfragment PullRequestDetails on PullRequest {\n __typename\n number\n title\n url\n state\n merged\n isDraft\n isInMergeQueue\n milestone {\n ...MilestoneFields\n }\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n url\n author {\n ...AuthorFields\n }\n }\n }\n reviews(last: $lastReviews) {\n totalCount\n nodes {\n ...PullRequestReviewFields\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n closingIssuesReferences(first: $firstClosingIssues) {\n nodes {\n number\n }\n }\n}\n\nfragment PullRequestReviewFields on PullRequestReview {\n state\n author {\n login\n }\n}": types.FetchPullRequestByNumberDocument, "query FetchAuthenticatedUserDetails {\n viewer {\n id\n name\n login\n avatarUrl\n }\n}": types.FetchAuthenticatedUserDetailsDocument, }; @@ -36,15 +36,15 @@ export function graphql(source: "fragment AuthorFields on Actor {\n login\n ht /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "query FetchDiscussionByNumber($owner: String!, $name: String!, $number: Int!, $lastComments: Int, $lastReplies: Int, $firstLabels: Int, $includeIsAnswered: Boolean!) {\n repository(owner: $owner, name: $name) {\n discussion(number: $number) {\n ...DiscussionDetails\n }\n }\n}\n\nfragment DiscussionDetails on Discussion {\n __typename\n number\n title\n stateReason\n isAnswered @include(if: $includeIsAnswered)\n url\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n ...DiscussionCommentFields\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n}\n\nfragment CommentFields on DiscussionComment {\n databaseId\n createdAt\n author {\n ...AuthorFields\n }\n url\n}\n\nfragment DiscussionCommentFields on DiscussionComment {\n ...CommentFields\n replies(last: $lastReplies) {\n totalCount\n nodes {\n ...CommentFields\n }\n }\n}"): typeof import('./graphql').FetchDiscussionByNumberDocument; +export function graphql(source: "query FetchDiscussionByNumber($ownerINDEX: String!, $nameINDEX: String!, $numberINDEX: Int!, $lastComments: Int, $lastReplies: Int, $firstLabels: Int, $includeIsAnswered: Boolean!) {\n ...DiscussionMergeQuery\n}\n\nfragment DiscussionMergeQuery on Query {\n nodeINDEX: repository(owner: $ownerINDEX, name: $nameINDEX) {\n discussion(number: $numberINDEX) {\n ...DiscussionDetails\n }\n }\n}\n\nfragment DiscussionDetails on Discussion {\n __typename\n number\n title\n stateReason\n isAnswered @include(if: $includeIsAnswered)\n url\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n ...DiscussionCommentFields\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n}\n\nfragment CommentFields on DiscussionComment {\n databaseId\n createdAt\n author {\n ...AuthorFields\n }\n url\n}\n\nfragment DiscussionCommentFields on DiscussionComment {\n ...CommentFields\n replies(last: $lastReplies) {\n totalCount\n nodes {\n ...CommentFields\n }\n }\n}"): typeof import('./graphql').FetchDiscussionByNumberDocument; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "query FetchIssueByNumber($owner: String!, $name: String!, $number: Int!, $lastComments: Int, $firstLabels: Int) {\n repository(owner: $owner, name: $name) {\n issue(number: $number) {\n ...IssueDetails\n }\n }\n}\n\nfragment IssueDetails on Issue {\n __typename\n number\n title\n url\n state\n stateReason\n milestone {\n ...MilestoneFields\n }\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n url\n author {\n ...AuthorFields\n }\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n}"): typeof import('./graphql').FetchIssueByNumberDocument; +export function graphql(source: "query FetchIssueByNumber($ownerINDEX: String!, $nameINDEX: String!, $numberINDEX: Int!, $lastComments: Int, $firstLabels: Int) {\n ...IssueMergeQuery\n}\n\nfragment IssueMergeQuery on Query {\n nodeINDEX: repository(owner: $ownerINDEX, name: $nameINDEX) {\n issue(number: $numberINDEX) {\n ...IssueDetails\n }\n }\n}\n\nfragment IssueDetails on Issue {\n __typename\n number\n title\n url\n state\n stateReason\n milestone {\n ...MilestoneFields\n }\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n url\n author {\n ...AuthorFields\n }\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n}"): typeof import('./graphql').FetchIssueByNumberDocument; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function graphql(source: "query FetchPullRequestByNumber($owner: String!, $name: String!, $number: Int!, $firstLabels: Int, $lastComments: Int, $lastReviews: Int, $firstClosingIssues: Int) {\n repository(owner: $owner, name: $name) {\n pullRequest(number: $number) {\n ...PullRequestDetails\n }\n }\n}\n\nfragment PullRequestDetails on PullRequest {\n __typename\n number\n title\n url\n state\n merged\n isDraft\n isInMergeQueue\n milestone {\n ...MilestoneFields\n }\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n url\n author {\n ...AuthorFields\n }\n }\n }\n reviews(last: $lastReviews) {\n totalCount\n nodes {\n ...PullRequestReviewFields\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n closingIssuesReferences(first: $firstClosingIssues) {\n nodes {\n number\n }\n }\n}\n\nfragment PullRequestReviewFields on PullRequestReview {\n state\n author {\n login\n }\n}"): typeof import('./graphql').FetchPullRequestByNumberDocument; +export function graphql(source: "query FetchPullRequestByNumber($ownerINDEX: String!, $nameINDEX: String!, $numberINDEX: Int!, $firstLabels: Int, $lastComments: Int, $lastReviews: Int, $firstClosingIssues: Int) {\n ...PullRequestMergeQuery\n}\n\nfragment PullRequestMergeQuery on Query {\n nodeINDEX: repository(owner: $ownerINDEX, name: $nameINDEX) {\n pullRequest(number: $numberINDEX) {\n ...PullRequestDetails\n }\n }\n}\n\nfragment PullRequestDetails on PullRequest {\n __typename\n number\n title\n url\n state\n merged\n isDraft\n isInMergeQueue\n milestone {\n ...MilestoneFields\n }\n author {\n ...AuthorFields\n }\n comments(last: $lastComments) {\n totalCount\n nodes {\n url\n author {\n ...AuthorFields\n }\n }\n }\n reviews(last: $lastReviews) {\n totalCount\n nodes {\n ...PullRequestReviewFields\n }\n }\n labels(first: $firstLabels) {\n nodes {\n name\n }\n }\n closingIssuesReferences(first: $firstClosingIssues) {\n nodes {\n number\n }\n }\n}\n\nfragment PullRequestReviewFields on PullRequestReview {\n state\n author {\n login\n }\n}"): typeof import('./graphql').FetchPullRequestByNumberDocument; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/src/renderer/utils/api/graphql/generated/graphql.ts b/src/renderer/utils/api/graphql/generated/graphql.ts index 8ea49cf77..df3cc8de3 100644 --- a/src/renderer/utils/api/graphql/generated/graphql.ts +++ b/src/renderer/utils/api/graphql/generated/graphql.ts @@ -35915,9 +35915,9 @@ export type AuthorFieldsFragment = export type MilestoneFieldsFragment = { __typename?: 'Milestone', state: MilestoneState, title: string }; export type FetchDiscussionByNumberQueryVariables = Exact<{ - owner: Scalars['String']['input']; - name: Scalars['String']['input']; - number: Scalars['Int']['input']; + ownerINDEX: Scalars['String']['input']; + nameINDEX: Scalars['String']['input']; + numberINDEX: Scalars['Int']['input']; lastComments?: InputMaybe; lastReplies?: InputMaybe; firstLabels?: InputMaybe; @@ -35925,7 +35925,27 @@ export type FetchDiscussionByNumberQueryVariables = Exact<{ }>; -export type FetchDiscussionByNumberQuery = { __typename?: 'Query', repository?: { __typename?: 'Repository', discussion?: { __typename: 'Discussion', number: number, title: string, stateReason?: DiscussionStateReason | null, isAnswered?: boolean | null, url: any, author?: +export type FetchDiscussionByNumberQuery = { __typename?: 'Query', nodeINDEX?: { __typename?: 'Repository', discussion?: { __typename: 'Discussion', number: number, title: string, stateReason?: DiscussionStateReason | null, isAnswered?: boolean | null, url: any, author?: + | { __typename?: 'Bot', login: string, html_url: any, avatar_url: any, type: 'Bot' } + | { __typename?: 'EnterpriseUserAccount', login: string, html_url: any, avatar_url: any, type: 'EnterpriseUserAccount' } + | { __typename?: 'Mannequin', login: string, html_url: any, avatar_url: any, type: 'Mannequin' } + | { __typename?: 'Organization', login: string, html_url: any, avatar_url: any, type: 'Organization' } + | { __typename?: 'User', login: string, html_url: any, avatar_url: any, type: 'User' } + | null, comments: { __typename?: 'DiscussionCommentConnection', totalCount: number, nodes?: Array<{ __typename?: 'DiscussionComment', databaseId?: number | null, createdAt: any, url: any, replies: { __typename?: 'DiscussionCommentConnection', totalCount: number, nodes?: Array<{ __typename?: 'DiscussionComment', databaseId?: number | null, createdAt: any, url: any, author?: + | { __typename?: 'Bot', login: string, html_url: any, avatar_url: any, type: 'Bot' } + | { __typename?: 'EnterpriseUserAccount', login: string, html_url: any, avatar_url: any, type: 'EnterpriseUserAccount' } + | { __typename?: 'Mannequin', login: string, html_url: any, avatar_url: any, type: 'Mannequin' } + | { __typename?: 'Organization', login: string, html_url: any, avatar_url: any, type: 'Organization' } + | { __typename?: 'User', login: string, html_url: any, avatar_url: any, type: 'User' } + | null } | null> | null }, author?: + | { __typename?: 'Bot', login: string, html_url: any, avatar_url: any, type: 'Bot' } + | { __typename?: 'EnterpriseUserAccount', login: string, html_url: any, avatar_url: any, type: 'EnterpriseUserAccount' } + | { __typename?: 'Mannequin', login: string, html_url: any, avatar_url: any, type: 'Mannequin' } + | { __typename?: 'Organization', login: string, html_url: any, avatar_url: any, type: 'Organization' } + | { __typename?: 'User', login: string, html_url: any, avatar_url: any, type: 'User' } + | null } | null> | null }, labels?: { __typename?: 'LabelConnection', nodes?: Array<{ __typename?: 'Label', name: string } | null> | null } | null } | null } | null }; + +export type DiscussionMergeQueryFragment = { __typename?: 'Query', nodeINDEX?: { __typename?: 'Repository', discussion?: { __typename: 'Discussion', number: number, title: string, stateReason?: DiscussionStateReason | null, isAnswered?: boolean | null, url: any, author?: | { __typename?: 'Bot', login: string, html_url: any, avatar_url: any, type: 'Bot' } | { __typename?: 'EnterpriseUserAccount', login: string, html_url: any, avatar_url: any, type: 'EnterpriseUserAccount' } | { __typename?: 'Mannequin', login: string, html_url: any, avatar_url: any, type: 'Mannequin' } @@ -35988,15 +36008,29 @@ export type DiscussionCommentFieldsFragment = { __typename?: 'DiscussionComment' | null }; export type FetchIssueByNumberQueryVariables = Exact<{ - owner: Scalars['String']['input']; - name: Scalars['String']['input']; - number: Scalars['Int']['input']; + ownerINDEX: Scalars['String']['input']; + nameINDEX: Scalars['String']['input']; + numberINDEX: Scalars['Int']['input']; lastComments?: InputMaybe; firstLabels?: InputMaybe; }>; -export type FetchIssueByNumberQuery = { __typename?: 'Query', repository?: { __typename?: 'Repository', issue?: { __typename: 'Issue', number: number, title: string, url: any, state: IssueState, stateReason?: IssueStateReason | null, milestone?: { __typename?: 'Milestone', state: MilestoneState, title: string } | null, author?: +export type FetchIssueByNumberQuery = { __typename?: 'Query', nodeINDEX?: { __typename?: 'Repository', issue?: { __typename: 'Issue', number: number, title: string, url: any, state: IssueState, stateReason?: IssueStateReason | null, milestone?: { __typename?: 'Milestone', state: MilestoneState, title: string } | null, author?: + | { __typename?: 'Bot', login: string, html_url: any, avatar_url: any, type: 'Bot' } + | { __typename?: 'EnterpriseUserAccount', login: string, html_url: any, avatar_url: any, type: 'EnterpriseUserAccount' } + | { __typename?: 'Mannequin', login: string, html_url: any, avatar_url: any, type: 'Mannequin' } + | { __typename?: 'Organization', login: string, html_url: any, avatar_url: any, type: 'Organization' } + | { __typename?: 'User', login: string, html_url: any, avatar_url: any, type: 'User' } + | null, comments: { __typename?: 'IssueCommentConnection', totalCount: number, nodes?: Array<{ __typename?: 'IssueComment', url: any, author?: + | { __typename?: 'Bot', login: string, html_url: any, avatar_url: any, type: 'Bot' } + | { __typename?: 'EnterpriseUserAccount', login: string, html_url: any, avatar_url: any, type: 'EnterpriseUserAccount' } + | { __typename?: 'Mannequin', login: string, html_url: any, avatar_url: any, type: 'Mannequin' } + | { __typename?: 'Organization', login: string, html_url: any, avatar_url: any, type: 'Organization' } + | { __typename?: 'User', login: string, html_url: any, avatar_url: any, type: 'User' } + | null } | null> | null }, labels?: { __typename?: 'LabelConnection', nodes?: Array<{ __typename?: 'Label', name: string } | null> | null } | null } | null } | null }; + +export type IssueMergeQueryFragment = { __typename?: 'Query', nodeINDEX?: { __typename?: 'Repository', issue?: { __typename: 'Issue', number: number, title: string, url: any, state: IssueState, stateReason?: IssueStateReason | null, milestone?: { __typename?: 'Milestone', state: MilestoneState, title: string } | null, author?: | { __typename?: 'Bot', login: string, html_url: any, avatar_url: any, type: 'Bot' } | { __typename?: 'EnterpriseUserAccount', login: string, html_url: any, avatar_url: any, type: 'EnterpriseUserAccount' } | { __typename?: 'Mannequin', login: string, html_url: any, avatar_url: any, type: 'Mannequin' } @@ -36025,9 +36059,9 @@ export type IssueDetailsFragment = { __typename: 'Issue', number: number, title: | null } | null> | null }, labels?: { __typename?: 'LabelConnection', nodes?: Array<{ __typename?: 'Label', name: string } | null> | null } | null }; export type FetchPullRequestByNumberQueryVariables = Exact<{ - owner: Scalars['String']['input']; - name: Scalars['String']['input']; - number: Scalars['Int']['input']; + ownerINDEX: Scalars['String']['input']; + nameINDEX: Scalars['String']['input']; + numberINDEX: Scalars['Int']['input']; firstLabels?: InputMaybe; lastComments?: InputMaybe; lastReviews?: InputMaybe; @@ -36035,7 +36069,27 @@ export type FetchPullRequestByNumberQueryVariables = Exact<{ }>; -export type FetchPullRequestByNumberQuery = { __typename?: 'Query', repository?: { __typename?: 'Repository', pullRequest?: { __typename: 'PullRequest', number: number, title: string, url: any, state: PullRequestState, merged: boolean, isDraft: boolean, isInMergeQueue: boolean, milestone?: { __typename?: 'Milestone', state: MilestoneState, title: string } | null, author?: +export type FetchPullRequestByNumberQuery = { __typename?: 'Query', nodeINDEX?: { __typename?: 'Repository', pullRequest?: { __typename: 'PullRequest', number: number, title: string, url: any, state: PullRequestState, merged: boolean, isDraft: boolean, isInMergeQueue: boolean, milestone?: { __typename?: 'Milestone', state: MilestoneState, title: string } | null, author?: + | { __typename?: 'Bot', login: string, html_url: any, avatar_url: any, type: 'Bot' } + | { __typename?: 'EnterpriseUserAccount', login: string, html_url: any, avatar_url: any, type: 'EnterpriseUserAccount' } + | { __typename?: 'Mannequin', login: string, html_url: any, avatar_url: any, type: 'Mannequin' } + | { __typename?: 'Organization', login: string, html_url: any, avatar_url: any, type: 'Organization' } + | { __typename?: 'User', login: string, html_url: any, avatar_url: any, type: 'User' } + | null, comments: { __typename?: 'IssueCommentConnection', totalCount: number, nodes?: Array<{ __typename?: 'IssueComment', url: any, author?: + | { __typename?: 'Bot', login: string, html_url: any, avatar_url: any, type: 'Bot' } + | { __typename?: 'EnterpriseUserAccount', login: string, html_url: any, avatar_url: any, type: 'EnterpriseUserAccount' } + | { __typename?: 'Mannequin', login: string, html_url: any, avatar_url: any, type: 'Mannequin' } + | { __typename?: 'Organization', login: string, html_url: any, avatar_url: any, type: 'Organization' } + | { __typename?: 'User', login: string, html_url: any, avatar_url: any, type: 'User' } + | null } | null> | null }, reviews?: { __typename?: 'PullRequestReviewConnection', totalCount: number, nodes?: Array<{ __typename?: 'PullRequestReview', state: PullRequestReviewState, author?: + | { __typename?: 'Bot', login: string } + | { __typename?: 'EnterpriseUserAccount', login: string } + | { __typename?: 'Mannequin', login: string } + | { __typename?: 'Organization', login: string } + | { __typename?: 'User', login: string } + | null } | null> | null } | null, labels?: { __typename?: 'LabelConnection', nodes?: Array<{ __typename?: 'Label', name: string } | null> | null } | null, closingIssuesReferences?: { __typename?: 'IssueConnection', nodes?: Array<{ __typename?: 'Issue', number: number } | null> | null } | null } | null } | null }; + +export type PullRequestMergeQueryFragment = { __typename?: 'Query', nodeINDEX?: { __typename?: 'Repository', pullRequest?: { __typename: 'PullRequest', number: number, title: string, url: any, state: PullRequestState, merged: boolean, isDraft: boolean, isInMergeQueue: boolean, milestone?: { __typename?: 'Milestone', state: MilestoneState, title: string } | null, author?: | { __typename?: 'Bot', login: string, html_url: any, avatar_url: any, type: 'Bot' } | { __typename?: 'EnterpriseUserAccount', login: string, html_url: any, avatar_url: any, type: 'EnterpriseUserAccount' } | { __typename?: 'Mannequin', login: string, html_url: any, avatar_url: any, type: 'Mannequin' } @@ -36199,6 +36253,59 @@ fragment DiscussionCommentFields on DiscussionComment { } } }`, {"fragmentName":"DiscussionDetails"}) as unknown as TypedDocumentString; +export const DiscussionMergeQueryFragmentDoc = new TypedDocumentString(` + fragment DiscussionMergeQuery on Query { + nodeINDEX: repository(owner: $ownerINDEX, name: $nameINDEX) { + discussion(number: $numberINDEX) { + ...DiscussionDetails + } + } +} + fragment AuthorFields on Actor { + login + html_url: url + avatar_url: avatarUrl + type: __typename +} +fragment DiscussionDetails on Discussion { + __typename + number + title + stateReason + isAnswered @include(if: $includeIsAnswered) + url + author { + ...AuthorFields + } + comments(last: $lastComments) { + totalCount + nodes { + ...DiscussionCommentFields + } + } + labels(first: $firstLabels) { + nodes { + name + } + } +} +fragment CommentFields on DiscussionComment { + databaseId + createdAt + author { + ...AuthorFields + } + url +} +fragment DiscussionCommentFields on DiscussionComment { + ...CommentFields + replies(last: $lastReplies) { + totalCount + nodes { + ...CommentFields + } + } +}`, {"fragmentName":"DiscussionMergeQuery"}) as unknown as TypedDocumentString; export const MilestoneFieldsFragmentDoc = new TypedDocumentString(` fragment MilestoneFields on Milestone { state @@ -36244,6 +36351,52 @@ fragment MilestoneFields on Milestone { state title }`, {"fragmentName":"IssueDetails"}) as unknown as TypedDocumentString; +export const IssueMergeQueryFragmentDoc = new TypedDocumentString(` + fragment IssueMergeQuery on Query { + nodeINDEX: repository(owner: $ownerINDEX, name: $nameINDEX) { + issue(number: $numberINDEX) { + ...IssueDetails + } + } +} + fragment AuthorFields on Actor { + login + html_url: url + avatar_url: avatarUrl + type: __typename +} +fragment MilestoneFields on Milestone { + state + title +} +fragment IssueDetails on Issue { + __typename + number + title + url + state + stateReason + milestone { + ...MilestoneFields + } + author { + ...AuthorFields + } + comments(last: $lastComments) { + totalCount + nodes { + url + author { + ...AuthorFields + } + } + } + labels(first: $firstLabels) { + nodes { + name + } + } +}`, {"fragmentName":"IssueMergeQuery"}) as unknown as TypedDocumentString; export const PullRequestReviewFieldsFragmentDoc = new TypedDocumentString(` fragment PullRequestReviewFields on PullRequestReview { state @@ -36310,13 +36463,74 @@ fragment PullRequestReviewFields on PullRequestReview { login } }`, {"fragmentName":"PullRequestDetails"}) as unknown as TypedDocumentString; -export const FetchDiscussionByNumberDocument = new TypedDocumentString(` - query FetchDiscussionByNumber($owner: String!, $name: String!, $number: Int!, $lastComments: Int, $lastReplies: Int, $firstLabels: Int, $includeIsAnswered: Boolean!) { - repository(owner: $owner, name: $name) { - discussion(number: $number) { - ...DiscussionDetails +export const PullRequestMergeQueryFragmentDoc = new TypedDocumentString(` + fragment PullRequestMergeQuery on Query { + nodeINDEX: repository(owner: $ownerINDEX, name: $nameINDEX) { + pullRequest(number: $numberINDEX) { + ...PullRequestDetails + } + } +} + fragment AuthorFields on Actor { + login + html_url: url + avatar_url: avatarUrl + type: __typename +} +fragment MilestoneFields on Milestone { + state + title +} +fragment PullRequestDetails on PullRequest { + __typename + number + title + url + state + merged + isDraft + isInMergeQueue + milestone { + ...MilestoneFields + } + author { + ...AuthorFields + } + comments(last: $lastComments) { + totalCount + nodes { + url + author { + ...AuthorFields + } + } + } + reviews(last: $lastReviews) { + totalCount + nodes { + ...PullRequestReviewFields } } + labels(first: $firstLabels) { + nodes { + name + } + } + closingIssuesReferences(first: $firstClosingIssues) { + nodes { + number + } + } +} +fragment PullRequestReviewFields on PullRequestReview { + state + author { + login + } +}`, {"fragmentName":"PullRequestMergeQuery"}) as unknown as TypedDocumentString; +export const FetchDiscussionByNumberDocument = new TypedDocumentString(` + query FetchDiscussionByNumber($ownerINDEX: String!, $nameINDEX: String!, $numberINDEX: Int!, $lastComments: Int, $lastReplies: Int, $firstLabels: Int, $includeIsAnswered: Boolean!) { + ...DiscussionMergeQuery } fragment AuthorFields on Actor { login @@ -36324,6 +36538,13 @@ export const FetchDiscussionByNumberDocument = new TypedDocumentString(` avatar_url: avatarUrl type: __typename } +fragment DiscussionMergeQuery on Query { + nodeINDEX: repository(owner: $ownerINDEX, name: $nameINDEX) { + discussion(number: $numberINDEX) { + ...DiscussionDetails + } + } +} fragment DiscussionDetails on Discussion { __typename number @@ -36364,12 +36585,8 @@ fragment DiscussionCommentFields on DiscussionComment { } }`) as unknown as TypedDocumentString; export const FetchIssueByNumberDocument = new TypedDocumentString(` - query FetchIssueByNumber($owner: String!, $name: String!, $number: Int!, $lastComments: Int, $firstLabels: Int) { - repository(owner: $owner, name: $name) { - issue(number: $number) { - ...IssueDetails - } - } + query FetchIssueByNumber($ownerINDEX: String!, $nameINDEX: String!, $numberINDEX: Int!, $lastComments: Int, $firstLabels: Int) { + ...IssueMergeQuery } fragment AuthorFields on Actor { login @@ -36381,6 +36598,13 @@ fragment MilestoneFields on Milestone { state title } +fragment IssueMergeQuery on Query { + nodeINDEX: repository(owner: $ownerINDEX, name: $nameINDEX) { + issue(number: $numberINDEX) { + ...IssueDetails + } + } +} fragment IssueDetails on Issue { __typename number @@ -36410,12 +36634,8 @@ fragment IssueDetails on Issue { } }`) as unknown as TypedDocumentString; export const FetchPullRequestByNumberDocument = new TypedDocumentString(` - query FetchPullRequestByNumber($owner: String!, $name: String!, $number: Int!, $firstLabels: Int, $lastComments: Int, $lastReviews: Int, $firstClosingIssues: Int) { - repository(owner: $owner, name: $name) { - pullRequest(number: $number) { - ...PullRequestDetails - } - } + query FetchPullRequestByNumber($ownerINDEX: String!, $nameINDEX: String!, $numberINDEX: Int!, $firstLabels: Int, $lastComments: Int, $lastReviews: Int, $firstClosingIssues: Int) { + ...PullRequestMergeQuery } fragment AuthorFields on Actor { login @@ -36427,6 +36647,13 @@ fragment MilestoneFields on Milestone { state title } +fragment PullRequestMergeQuery on Query { + nodeINDEX: repository(owner: $ownerINDEX, name: $nameINDEX) { + pullRequest(number: $numberINDEX) { + ...PullRequestDetails + } + } +} fragment PullRequestDetails on PullRequest { __typename number diff --git a/src/renderer/utils/api/graphql/issue.graphql b/src/renderer/utils/api/graphql/issue.graphql index 091a6543e..fda39e33b 100644 --- a/src/renderer/utils/api/graphql/issue.graphql +++ b/src/renderer/utils/api/graphql/issue.graphql @@ -1,12 +1,15 @@ query FetchIssueByNumber( - $owner: String! - $name: String! - $number: Int! + $ownerINDEX: String! + $nameINDEX: String! + $numberINDEX: Int! $lastComments: Int $firstLabels: Int ) { - repository(owner: $owner, name: $name) { - issue(number: $number) { + ...IssueMergeQuery +} +fragment IssueMergeQuery on Query { + nodeINDEX: repository(owner: $ownerINDEX, name: $nameINDEX) { + issue(number: $numberINDEX) { ...IssueDetails } } diff --git a/src/renderer/utils/api/graphql/pull.graphql b/src/renderer/utils/api/graphql/pull.graphql index 9d5862cb6..d9fb30204 100644 --- a/src/renderer/utils/api/graphql/pull.graphql +++ b/src/renderer/utils/api/graphql/pull.graphql @@ -1,14 +1,18 @@ query FetchPullRequestByNumber( - $owner: String! - $name: String! - $number: Int! + $ownerINDEX: String! + $nameINDEX: String! + $numberINDEX: Int! $firstLabels: Int $lastComments: Int $lastReviews: Int $firstClosingIssues: Int ) { - repository(owner: $owner, name: $name) { - pullRequest(number: $number) { + ...PullRequestMergeQuery +} + +fragment PullRequestMergeQuery on Query { + nodeINDEX: repository(owner: $ownerINDEX, name: $nameINDEX) { + pullRequest(number: $numberINDEX) { ...PullRequestDetails } } diff --git a/src/renderer/utils/api/graphql/utils.ts b/src/renderer/utils/api/graphql/utils.ts new file mode 100644 index 000000000..8329b6746 --- /dev/null +++ b/src/renderer/utils/api/graphql/utils.ts @@ -0,0 +1,22 @@ +export function getQueryFragmentBody(doc: string): string | null { + // Find fragment on Query pattern + const fragmentMatch = doc.match(/fragment\s+\w+\s+on\s+Query\s+\{/); + if (!fragmentMatch) { + return null; + } + + const start = fragmentMatch.index + fragmentMatch[0].length - 1; // Position of opening brace + let depth = 0; + + for (let i = start; i < doc.length; i++) { + if (doc[i] === '{') { + depth++; + } else if (doc[i] === '}') { + depth--; + if (depth === 0) { + return doc.slice(start + 1, i).trim(); + } + } + } + return null; +} diff --git a/src/renderer/utils/notifications/handlers/checkSuite.ts b/src/renderer/utils/notifications/handlers/checkSuite.ts index 1985c50d8..918952adf 100644 --- a/src/renderer/utils/notifications/handlers/checkSuite.ts +++ b/src/renderer/utils/notifications/handlers/checkSuite.ts @@ -34,6 +34,7 @@ class CheckSuiteHandler extends DefaultHandler { async enrich( notification: Notification, _settings: SettingsState, + _fetchedData?: unknown, ): Promise { const state = getCheckSuiteAttributes(notification)?.status; diff --git a/src/renderer/utils/notifications/handlers/commit.ts b/src/renderer/utils/notifications/handlers/commit.ts index 4187c5056..4f208e8dc 100644 --- a/src/renderer/utils/notifications/handlers/commit.ts +++ b/src/renderer/utils/notifications/handlers/commit.ts @@ -18,9 +18,10 @@ import { getNotificationAuthor } from './utils'; class CommitHandler extends DefaultHandler { readonly type = 'Commit'; - async enrich( + async fetchAndEnrich( notification: Notification, settings: SettingsState, + _fetchedData?: unknown, ): Promise { const commitState: GitifyNotificationState = null; // Commit notifications are stateless diff --git a/src/renderer/utils/notifications/handlers/default.ts b/src/renderer/utils/notifications/handlers/default.ts index 8c674b6a3..9d2a18685 100644 --- a/src/renderer/utils/notifications/handlers/default.ts +++ b/src/renderer/utils/notifications/handlers/default.ts @@ -12,9 +12,25 @@ import { formatForDisplay } from './utils'; export class DefaultHandler implements NotificationTypeHandler { type?: SubjectType; + mergeQueryConfig() { + return undefined; + } + + query(_notification: Notification) { + return null; + } + + async fetchAndEnrich( + _notification: Notification, + _settings: SettingsState, + ): Promise { + return null; + } + async enrich( _notification: Notification, _settings: SettingsState, + _fetchedData?: unknown, ): Promise { return null; } diff --git a/src/renderer/utils/notifications/handlers/discussion.ts b/src/renderer/utils/notifications/handlers/discussion.ts index 57ca0e651..92aab59c0 100644 --- a/src/renderer/utils/notifications/handlers/discussion.ts +++ b/src/renderer/utils/notifications/handlers/discussion.ts @@ -19,22 +19,51 @@ import { } from '../../../types'; import type { Notification, Subject } from '../../../typesGitHub'; import { fetchDiscussionByNumber } from '../../api/client'; -import type { - CommentFieldsFragment, - DiscussionCommentFieldsFragment, +import { + type CommentFieldsFragment, + type DiscussionCommentFieldsFragment, + type DiscussionDetailsFragment, + DiscussionDetailsFragmentDoc, + DiscussionMergeQueryFragmentDoc, } from '../../api/graphql/generated/graphql'; +import { getQueryFragmentBody } from '../../api/graphql/utils'; import { DefaultHandler, defaultHandler } from './default'; +import type { GraphQLMergedQueryConfig } from './types'; import { getNotificationAuthor } from './utils'; class DiscussionHandler extends DefaultHandler { readonly type = 'Discussion'; + mergeQueryConfig() { + return { + queryFragment: getQueryFragmentBody( + DiscussionMergeQueryFragmentDoc.toString(), + ), + responseFragment: DiscussionDetailsFragmentDoc.toString(), + extras: [ + { name: 'lastComments', type: 'Int', defaultValue: 100 }, + { name: 'lastReplies', type: 'Int', defaultValue: 100 }, + { name: 'firstLabels', type: 'Int', defaultValue: 100 }, + { name: 'includeIsAnswered', type: 'Boolean!', defaultValue: true }, + ], + selection: ( + index: number, + ) => `node${index}: repository(owner: $owner${index}, name: $name${index}) { + discussion(number: $number${index}) { + ...DiscussionDetails + } + }`, + } as GraphQLMergedQueryConfig; + } + async enrich( notification: Notification, _settings: SettingsState, + fetchedData?: DiscussionDetailsFragment, ): Promise { - const response = await fetchDiscussionByNumber(notification); - const discussion = response.data.repository?.discussion; + const discussion = + fetchedData ?? + (await fetchDiscussionByNumber(notification)).data.nodeINDEX?.discussion; let discussionState: GitifyDiscussionState = 'OPEN'; diff --git a/src/renderer/utils/notifications/handlers/issue.ts b/src/renderer/utils/notifications/handlers/issue.ts index 2a1fb9206..27b0556b5 100644 --- a/src/renderer/utils/notifications/handlers/issue.ts +++ b/src/renderer/utils/notifications/handlers/issue.ts @@ -16,19 +16,46 @@ import type { } from '../../../types'; import { IconColor } from '../../../types'; import type { Notification, Subject } from '../../../typesGitHub'; -import { fetchIssueByNumber } from '../../api/client'; +import { + type IssueDetailsFragment, + IssueDetailsFragmentDoc, + IssueMergeQueryFragmentDoc, +} from '../../api/graphql/generated/graphql'; +import { getQueryFragmentBody } from '../../api/graphql/utils'; import { DefaultHandler, defaultHandler } from './default'; +import type { GraphQLMergedQueryConfig } from './types'; import { getNotificationAuthor } from './utils'; class IssueHandler extends DefaultHandler { readonly type = 'Issue'; + mergeQueryConfig() { + return { + queryFragment: getQueryFragmentBody( + IssueMergeQueryFragmentDoc.toString(), + ), + + responseFragment: IssueDetailsFragmentDoc.toString(), + extras: [ + { name: 'lastComments', type: 'Int', defaultValue: 100 }, + { name: 'firstLabels', type: 'Int', defaultValue: 100 }, + ], + selection: ( + index: number, + ) => `node${index}: repository(owner: $owner${index}, name: $name${index}) { + issue(number: $number${index}) { + ...IssueDetails + } + }`, + } as GraphQLMergedQueryConfig; + } + async enrich( - notification: Notification, + _notification: Notification, _settings: SettingsState, + fetchedData?: IssueDetailsFragment, ): Promise { - const response = await fetchIssueByNumber(notification); - const issue = response.data.repository?.issue; + const issue = fetchedData; const issueState = issue.stateReason ?? issue.state; diff --git a/src/renderer/utils/notifications/handlers/pullRequest.ts b/src/renderer/utils/notifications/handlers/pullRequest.ts index 55610e761..4ca28ea49 100644 --- a/src/renderer/utils/notifications/handlers/pullRequest.ts +++ b/src/renderer/utils/notifications/handlers/pullRequest.ts @@ -19,19 +19,43 @@ import { } from '../../../types'; import type { Notification, Subject } from '../../../typesGitHub'; import { fetchPullByNumber } from '../../api/client'; -import type { PullRequestReviewFieldsFragment } from '../../api/graphql/generated/graphql'; +import { + type PullRequestDetailsFragment, + PullRequestDetailsFragmentDoc, + PullRequestMergeQueryFragmentDoc, + type PullRequestReviewFieldsFragment, +} from '../../api/graphql/generated/graphql'; +import { getQueryFragmentBody } from '../../api/graphql/utils'; import { DefaultHandler, defaultHandler } from './default'; +import type { GraphQLMergedQueryConfig } from './types'; import { getNotificationAuthor } from './utils'; class PullRequestHandler extends DefaultHandler { readonly type = 'PullRequest' as const; + mergeQueryConfig() { + return { + queryFragment: getQueryFragmentBody( + PullRequestMergeQueryFragmentDoc.toString(), + ), + responseFragment: PullRequestDetailsFragmentDoc.toString(), + extras: [ + { name: 'firstLabels', type: 'Int', defaultValue: 100 }, + { name: 'lastComments', type: 'Int', defaultValue: 100 }, + { name: 'lastReviews', type: 'Int', defaultValue: 100 }, + { name: 'firstClosingIssues', type: 'Int', defaultValue: 100 }, + ], + } as GraphQLMergedQueryConfig; + } + async enrich( notification: Notification, _settings: SettingsState, + fetchedData?: PullRequestDetailsFragment, ): Promise { - const response = await fetchPullByNumber(notification); - const pr = response.data.repository.pullRequest; + const pr = + fetchedData ?? + (await fetchPullByNumber(notification)).data.nodeINDEX.pullRequest; let prState: GitifyPullRequestState = pr.state; if (pr.isDraft) { diff --git a/src/renderer/utils/notifications/handlers/release.ts b/src/renderer/utils/notifications/handlers/release.ts index 9b2fe3149..c79253b46 100644 --- a/src/renderer/utils/notifications/handlers/release.ts +++ b/src/renderer/utils/notifications/handlers/release.ts @@ -18,7 +18,7 @@ import { getNotificationAuthor } from './utils'; class ReleaseHandler extends DefaultHandler { readonly type = 'Release'; - async enrich( + async fetchAndEnrich( notification: Notification, settings: SettingsState, ): Promise { diff --git a/src/renderer/utils/notifications/handlers/types.ts b/src/renderer/utils/notifications/handlers/types.ts index ced167101..29e028591 100644 --- a/src/renderer/utils/notifications/handlers/types.ts +++ b/src/renderer/utils/notifications/handlers/types.ts @@ -5,15 +5,38 @@ import type { OcticonProps } from '@primer/octicons-react'; import type { GitifySubject, Link, SettingsState } from '../../../types'; import type { Notification, Subject, SubjectType } from '../../../typesGitHub'; -export interface NotificationTypeHandler { +export type GraphQLMergedQueryConfig = { + queryFragment: string; + responseFragment: string; + extras: Array<{ + name: string; + type: string; + defaultValue: number | boolean; + }>; +}; + +export interface NotificationTypeHandler { readonly type?: SubjectType; + mergeQueryConfig(): GraphQLMergedQueryConfig; + + query(notification: Notification): { query; variables } | null; + + /** + * Fetch remote data (if needed) and enrich a notification. + */ + fetchAndEnrich( + notification: Notification, + settings: SettingsState, + ): Promise; + /** * Enrich a notification. Settings may be unused for some handlers. */ enrich( notification: Notification, - settings: SettingsState, + settings?: SettingsState, + fetchedData?: TFragment, ): Promise; /** diff --git a/src/renderer/utils/notifications/notifications.ts b/src/renderer/utils/notifications/notifications.ts index ff822c872..791dd6c88 100644 --- a/src/renderer/utils/notifications/notifications.ts +++ b/src/renderer/utils/notifications/notifications.ts @@ -1,12 +1,17 @@ +import axios from 'axios'; + import type { AccountNotifications, GitifyState, GitifySubject, + Link, SettingsState, } from '../../types'; import type { Notification } from '../../typesGitHub'; import { listNotificationsForAuthenticatedUser } from '../api/client'; import { determineFailureType } from '../api/errors'; +import { getHeaders } from '../api/request'; +import { getGitHubGraphQLUrl, getNumberFromUrl } from '../api/utils'; import { rendererLogError, rendererLogWarn } from '../logger'; import { filterBaseNotifications, @@ -128,10 +133,185 @@ export async function enrichNotifications( if (!settings.detailedNotifications) { return notifications; } + type NotificationKind = 'PullRequest' | 'Issue' | 'Discussion'; + + const selections: string[] = []; + const variableDefinitions: string[] = []; + const variableValues: Record = {}; + const extraVariableDefinitions = new Map(); + const extraVariableValues: Record = {}; + const fragments = new Map(); + const targets: Array<{ + alias: string; + kind: NotificationKind; + notification: Notification; + }> = []; + + const collectFragments = (doc: string) => { + const fragmentRegex = + /fragment\s+[A-Za-z0-9_]+\s+on[\s\S]*?(?=(?:fragment\s+[A-Za-z0-9_]+\s+on)|$)/g; + const nameRegex = /fragment\s+([A-Za-z0-9_]+)\s+on/; + + const matches = doc.match(fragmentRegex) ?? []; + for (const match of matches) { + const nameMatch = match.match(nameRegex); + if (!nameMatch) { + continue; + } + const name = nameMatch[1]; + if (!fragments.has(name)) { + fragments.set(name, match.trim()); + } + } + }; + + let index = 0; + + for (const notification of notifications) { + const handler = createNotificationHandler(notification); + const kind = notification.subject.type as NotificationKind; + const config = handler.mergeQueryConfig(); + + if (!config) { + continue; + } + + const org = notification.repository.owner.login; + const repo = notification.repository.name; + const number = getNumberFromUrl(notification.subject.url); + + const alias = `node${index}`; + const queryFragment = config.queryFragment.replaceAll( + 'INDEX', + index.toString(), + ); + + selections.push(queryFragment); + variableDefinitions.push( + `$owner${index}: String!, $name${index}: String!, $number${index}: Int!`, + ); + variableValues[`owner${index}`] = org; + variableValues[`name${index}`] = repo; + variableValues[`number${index}`] = number; + targets.push({ alias, kind, notification }); + + for (const extra of config.extras) { + if (!extraVariableDefinitions.has(extra.name)) { + extraVariableDefinitions.set(extra.name, extra.type); + extraVariableValues[extra.name] = extra.defaultValue; + } + } + + collectFragments(config.responseFragment); + + index += 1; + } + + if (selections.length === 0) { + const enrichedNotifications = await Promise.all( + notifications.map(async (notification: Notification) => { + return enrichNotification(notification, settings); + }), + ); + return enrichedNotifications; + } + + const combinedVariableDefinitions = [ + ...variableDefinitions, + ...Array.from(extraVariableDefinitions.entries()).map( + ([name, type]) => `$${name}: ${type}`, + ), + ].join(', '); + + const mergedQuery = `query FetchMergedNotifications(${combinedVariableDefinitions}) { + ${selections.join('\n')} + } + + ${Array.from(fragments.values()).join('\n')} + `; + + const queryVariables = { + ...variableValues, + ...extraVariableValues, + }; + + let mergedData: Record | null = null; + + try { + const url = getGitHubGraphQLUrl( + notifications[0].account.hostname, + ).toString(); + const token = notifications[0].account.token; + + const headers = await getHeaders(url as Link, token); + + const response = await axios({ + method: 'POST', + url, + data: { + query: mergedQuery, + variables: queryVariables, + }, + headers: headers, + }); + + mergedData = + (response.data as { data?: Record })?.data ?? null; + } catch (err) { + rendererLogError( + 'enrichNotifications', + 'Failed to fetch merged notification details', + err, + ); + } const enrichedNotifications = await Promise.all( notifications.map(async (notification: Notification) => { - return enrichNotification(notification, settings); + const handler = createNotificationHandler(notification); + + const target = targets.find((item) => item.notification === notification); + + if (mergedData && target) { + const repoData = mergedData[target.alias] as + | { pullRequest?: unknown; issue?: unknown; discussion?: unknown } + | undefined; + + let fragment: unknown; + if (target.kind === 'PullRequest') { + fragment = repoData?.pullRequest; + } else if (target.kind === 'Issue') { + fragment = repoData?.issue; + } else if (target.kind === 'Discussion') { + fragment = repoData?.discussion; + } + + if (fragment) { + const details = await handler.enrich( + notification, + settings, + fragment, + ); + return { + ...notification, + subject: { + ...notification.subject, + ...details, + }, + }; + } + } + + const fetchedDetails = await handler.fetchAndEnrich( + notification, + settings, + ); + return { + ...notification, + subject: { + ...notification.subject, + ...fetchedDetails, + }, + }; }), ); @@ -153,7 +333,10 @@ export async function enrichNotification( try { const handler = createNotificationHandler(notification); - additionalSubjectDetails = await handler.enrich(notification, settings); + additionalSubjectDetails = await handler.fetchAndEnrich( + notification, + settings, + ); } catch (err) { rendererLogError( 'enrichNotification',