From 6b0cd1834451471ad525945e14b518808c50e7b4 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Wed, 24 Dec 2025 08:21:49 +1000 Subject: [PATCH 1/9] feat(api): graphql query merging Signed-off-by: Adam Setch --- package.json | 1 + pnpm-lock.yaml | 12 ++++++++++ .../utils/notifications/handlers/default.ts | 4 ++++ .../notifications/handlers/pullRequest.ts | 23 ++++++++++++++++++- .../utils/notifications/handlers/types.ts | 2 ++ .../utils/notifications/notifications.ts | 19 +++++++++++++++ 6 files changed, 60 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index f47d43e72..db00826b3 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "dependencies": { "electron-log": "5.4.3", "electron-updater": "6.6.2", + "graphql-combine-query": "^1.2.4", "menubar": "9.5.2", "react": "19.2.3", "react-dom": "19.2.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 44229f751..57c770396 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: electron-updater: specifier: 6.6.2 version: 6.6.2 + graphql-combine-query: + specifier: ^1.2.4 + version: 1.2.4(graphql@16.12.0) menubar: specifier: 9.5.2 version: 9.5.2(electron@39.2.7) @@ -3501,6 +3504,11 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + graphql-combine-query@1.2.4: + resolution: {integrity: sha512-Os0d5cS8nH1cRnejGyACda0lraPzMJJjtd21KrfjKi1NWgEgzXzP8s29cpLlne+Y+51/2mS4j/3aTaIlCYMexg==} + peerDependencies: + graphql: ^15.1.0 || ^16.0.0 + graphql-config@5.1.5: resolution: {integrity: sha512-mG2LL1HccpU8qg5ajLROgdsBzx/o2M6kgI3uAmoaXiSH9PCUbtIyLomLqUtCFaAeG2YCFsl0M5cfQ9rKmDoMVA==} engines: {node: '>= 16.0.0'} @@ -10011,6 +10019,10 @@ snapshots: graceful-fs@4.2.11: {} + graphql-combine-query@1.2.4(graphql@16.12.0): + dependencies: + graphql: 16.12.0 + graphql-config@5.1.5(@types/node@24.10.4)(graphql@16.12.0)(typescript@5.9.3): dependencies: '@graphql-tools/graphql-file-loader': 8.1.8(graphql@16.12.0) diff --git a/src/renderer/utils/notifications/handlers/default.ts b/src/renderer/utils/notifications/handlers/default.ts index 8c674b6a3..0cc520f87 100644 --- a/src/renderer/utils/notifications/handlers/default.ts +++ b/src/renderer/utils/notifications/handlers/default.ts @@ -12,6 +12,10 @@ import { formatForDisplay } from './utils'; export class DefaultHandler implements NotificationTypeHandler { type?: SubjectType; + query(_notification: Notification) { + return null; + } + async enrich( _notification: Notification, _settings: SettingsState, diff --git a/src/renderer/utils/notifications/handlers/pullRequest.ts b/src/renderer/utils/notifications/handlers/pullRequest.ts index 55610e761..223e76017 100644 --- a/src/renderer/utils/notifications/handlers/pullRequest.ts +++ b/src/renderer/utils/notifications/handlers/pullRequest.ts @@ -19,13 +19,34 @@ import { } from '../../../types'; import type { Notification, Subject } from '../../../typesGitHub'; import { fetchPullByNumber } from '../../api/client'; -import type { PullRequestReviewFieldsFragment } from '../../api/graphql/generated/graphql'; +import { + FetchPullRequestByNumberDocument, + type PullRequestReviewFieldsFragment, +} from '../../api/graphql/generated/graphql'; +import { getNumberFromUrl } from '../../api/utils'; import { DefaultHandler, defaultHandler } from './default'; import { getNotificationAuthor } from './utils'; class PullRequestHandler extends DefaultHandler { readonly type = 'PullRequest' as const; + query(notification: Notification) { + const number = getNumberFromUrl(notification.subject.url); + + return { + query: FetchPullRequestByNumberDocument, + variables: { + owner: notification.repository.owner.login, + name: notification.repository.name, + number: number, + firstLabels: 100, + firstClosingIssues: 100, + lastComments: 1, + lastReviews: 100, + }, + }; + } + async enrich( notification: Notification, _settings: SettingsState, diff --git a/src/renderer/utils/notifications/handlers/types.ts b/src/renderer/utils/notifications/handlers/types.ts index ced167101..df3ce16d7 100644 --- a/src/renderer/utils/notifications/handlers/types.ts +++ b/src/renderer/utils/notifications/handlers/types.ts @@ -8,6 +8,8 @@ import type { Notification, Subject, SubjectType } from '../../../typesGitHub'; export interface NotificationTypeHandler { readonly type?: SubjectType; + query(notification: Notification): { query; variables } | null; + /** * Enrich a notification. Settings may be unused for some handlers. */ diff --git a/src/renderer/utils/notifications/notifications.ts b/src/renderer/utils/notifications/notifications.ts index ff822c872..a3dc590e7 100644 --- a/src/renderer/utils/notifications/notifications.ts +++ b/src/renderer/utils/notifications/notifications.ts @@ -1,3 +1,6 @@ +import { parse } from 'graphql'; +import combineQuery from 'graphql-combine-query'; + import type { AccountNotifications, GitifyState, @@ -129,6 +132,22 @@ export async function enrichNotifications( return notifications; } + // Build combined query for pull requests (builder is immutable) + const buildQuery = combineQuery('PullRequestBatch'); + + for (const notification of notifications) { + if (notification.subject.type === 'PullRequest') { + const handler = createNotificationHandler(notification); + const queryData = handler.query(notification); + + if (queryData?.query && queryData?.variables) { + buildQuery.addN(parse(queryData.query), queryData.variables); + } + } + } + + // console.log('ADAM COMBINED QUERY: ', JSON.stringify(buildQuery, null, 2)); + const enrichedNotifications = await Promise.all( notifications.map(async (notification: Notification) => { return enrichNotification(notification, settings); From b3e76c959550426ec366505fa3e1bed9b0f2513e Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Fri, 26 Dec 2025 14:43:23 +1000 Subject: [PATCH 2/9] feat(api): merge query Signed-off-by: Adam Setch --- .../notifications/handlers/pullRequest.ts | 23 +---- .../utils/notifications/notifications.ts | 93 +++++++++++++++++-- 2 files changed, 85 insertions(+), 31 deletions(-) diff --git a/src/renderer/utils/notifications/handlers/pullRequest.ts b/src/renderer/utils/notifications/handlers/pullRequest.ts index 223e76017..55610e761 100644 --- a/src/renderer/utils/notifications/handlers/pullRequest.ts +++ b/src/renderer/utils/notifications/handlers/pullRequest.ts @@ -19,34 +19,13 @@ import { } from '../../../types'; import type { Notification, Subject } from '../../../typesGitHub'; import { fetchPullByNumber } from '../../api/client'; -import { - FetchPullRequestByNumberDocument, - type PullRequestReviewFieldsFragment, -} from '../../api/graphql/generated/graphql'; -import { getNumberFromUrl } from '../../api/utils'; +import type { PullRequestReviewFieldsFragment } from '../../api/graphql/generated/graphql'; import { DefaultHandler, defaultHandler } from './default'; import { getNotificationAuthor } from './utils'; class PullRequestHandler extends DefaultHandler { readonly type = 'PullRequest' as const; - query(notification: Notification) { - const number = getNumberFromUrl(notification.subject.url); - - return { - query: FetchPullRequestByNumberDocument, - variables: { - owner: notification.repository.owner.login, - name: notification.repository.name, - number: number, - firstLabels: 100, - firstClosingIssues: 100, - lastComments: 1, - lastReviews: 100, - }, - }; - } - async enrich( notification: Notification, _settings: SettingsState, diff --git a/src/renderer/utils/notifications/notifications.ts b/src/renderer/utils/notifications/notifications.ts index a3dc590e7..501245d69 100644 --- a/src/renderer/utils/notifications/notifications.ts +++ b/src/renderer/utils/notifications/notifications.ts @@ -1,15 +1,17 @@ -import { parse } from 'graphql'; -import combineQuery from 'graphql-combine-query'; +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, @@ -133,20 +135,93 @@ export async function enrichNotifications( } // Build combined query for pull requests (builder is immutable) - const buildQuery = combineQuery('PullRequestBatch'); + let mergedQuery = ''; + let i = 0; + const args = []; for (const notification of notifications) { if (notification.subject.type === 'PullRequest') { - const handler = createNotificationHandler(notification); - const queryData = handler.query(notification); + const org = notification.repository.owner.login; + const repo = notification.repository.name; + const number = getNumberFromUrl(notification.subject.url); - if (queryData?.query && queryData?.variables) { - buildQuery.addN(parse(queryData.query), queryData.variables); - } + args.push({ + org: org, + repo: repo, + number: number, + }); + + mergedQuery += `repo${i}: repository(owner: $owner${i}, name: $name${i}) { + pullRequest(number: $number${i}) { + title + } + }\n`; + + i += 1; + + // const handler = createNotificationHandler(notification); + // const queryData = handler.query(notification); } } - // console.log('ADAM COMBINED QUERY: ', JSON.stringify(buildQuery, null, 2)); + let queryArgs = ''; + let queryArgsVariables = {}; + + for (let idx = 0; idx < args.length; idx++) { + const arg = args[idx]; + if (idx > 0) { + queryArgs += ', '; + } + queryArgs += `$owner${idx}: String!, $name${idx}: String!, $number${idx}: Int!`; + queryArgsVariables = { + ...queryArgsVariables, + [`owner${idx}`]: arg.org, + [`name${idx}`]: arg.repo, + [`number${idx}`]: arg.number, + }; + } + + mergedQuery = `query JumboQuery(${queryArgs}) {\n${mergedQuery}}\n`; + + console.log('ADAM COMBINED QUERY ', JSON.stringify(mergedQuery, null, 2)); + console.log( + 'ADAM COMBINED ARGS ', + JSON.stringify(queryArgsVariables, null, 2), + ); + + try { + const url = getGitHubGraphQLUrl( + notifications[0].account.hostname, + ).toString(); + const token = notifications[0].account.token; + + const headers = await getHeaders(url as Link, token); + + axios({ + method: 'POST', + url, + data: { + query: mergedQuery, + variables: queryArgsVariables, + }, + headers: headers, + }).then((response) => { + console.log('ADAM RESPONSE ', JSON.stringify(response, null, 2)); + }); + } catch (err) { + console.error('oops'); + } + + // const headers = await getHeaders(url.toString() as Link, token); + + // await axios.post(url.toString(), combined, { headers }); + // } catch (err) { + // rendererLogError( + // 'enrichNotifications', + // 'failed to fetch batch pull request details', + // err, + // ); + // } const enrichedNotifications = await Promise.all( notifications.map(async (notification: Notification) => { From caf1476f1097e58a04d0986b218ea8451a6d70c4 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Fri, 26 Dec 2025 14:53:24 +1000 Subject: [PATCH 3/9] feat(api): merge query Signed-off-by: Adam Setch --- .../utils/notifications/notifications.ts | 72 +++++++++---------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/src/renderer/utils/notifications/notifications.ts b/src/renderer/utils/notifications/notifications.ts index 501245d69..7f89c87e0 100644 --- a/src/renderer/utils/notifications/notifications.ts +++ b/src/renderer/utils/notifications/notifications.ts @@ -10,6 +10,7 @@ import type { import type { Notification } from '../../typesGitHub'; import { listNotificationsForAuthenticatedUser } from '../api/client'; import { determineFailureType } from '../api/errors'; +import { PullRequestDetailsFragmentDoc } from '../api/graphql/generated/graphql'; import { getHeaders } from '../api/request'; import { getGitHubGraphQLUrl, getNumberFromUrl } from '../api/utils'; import { rendererLogError, rendererLogWarn } from '../logger'; @@ -134,10 +135,10 @@ export async function enrichNotifications( return notifications; } - // Build combined query for pull requests (builder is immutable) + // Build combined query for pull requests let mergedQuery = ''; let i = 0; - const args = []; + const args: Array<{ org: string; repo: string; number: number }> = []; for (const notification of notifications) { if (notification.subject.type === 'PullRequest') { @@ -146,26 +147,33 @@ export async function enrichNotifications( const number = getNumberFromUrl(notification.subject.url); args.push({ - org: org, - repo: repo, - number: number, + org, + repo, + number, }); - mergedQuery += `repo${i}: repository(owner: $owner${i}, name: $name${i}) { + mergedQuery += `pr${i}: repository(owner: $owner${i}, name: $name${i}) { pullRequest(number: $number${i}) { - title + ...PullRequestDetails } }\n`; i += 1; - - // const handler = createNotificationHandler(notification); - // const queryData = handler.query(notification); } } + // If no pull requests, return early + if (args.length === 0) { + const enrichedNotifications = await Promise.all( + notifications.map(async (notification: Notification) => { + return enrichNotification(notification, settings); + }), + ); + return enrichedNotifications; + } + let queryArgs = ''; - let queryArgsVariables = {}; + const queryArgsVariables: Record = {}; for (let idx = 0; idx < args.length; idx++) { const arg = args[idx]; @@ -173,21 +181,24 @@ export async function enrichNotifications( queryArgs += ', '; } queryArgs += `$owner${idx}: String!, $name${idx}: String!, $number${idx}: Int!`; - queryArgsVariables = { - ...queryArgsVariables, - [`owner${idx}`]: arg.org, - [`name${idx}`]: arg.repo, - [`number${idx}`]: arg.number, - }; + queryArgsVariables[`owner${idx}`] = arg.org; + queryArgsVariables[`name${idx}`] = arg.repo; + queryArgsVariables[`number${idx}`] = arg.number; } - mergedQuery = `query JumboQuery(${queryArgs}) {\n${mergedQuery}}\n`; + // Add variables from PullRequestDetailsFragment + queryArgs += + ', $firstLabels: Int, $lastComments: Int, $lastReviews: Int, $firstClosingIssues: Int'; + queryArgsVariables['firstLabels'] = 100; + queryArgsVariables['lastComments'] = 100; + queryArgsVariables['lastReviews'] = 100; + queryArgsVariables['firstClosingIssues'] = 100; - console.log('ADAM COMBINED QUERY ', JSON.stringify(mergedQuery, null, 2)); - console.log( - 'ADAM COMBINED ARGS ', - JSON.stringify(queryArgsVariables, null, 2), - ); + const fragmentQuery = PullRequestDetailsFragmentDoc.toString(); + mergedQuery = `query FetchMergedPullRequests(${queryArgs}) {\n${mergedQuery}}\n\n${fragmentQuery}`; + + console.log('MERGED QUERY ', JSON.stringify(mergedQuery, null, 2)); + console.log('MERGED ARGS ', JSON.stringify(queryArgsVariables, null, 2)); try { const url = getGitHubGraphQLUrl( @@ -206,23 +217,12 @@ export async function enrichNotifications( }, headers: headers, }).then((response) => { - console.log('ADAM RESPONSE ', JSON.stringify(response, null, 2)); + console.log('MERGED RESPONSE ', JSON.stringify(response, null, 2)); }); } catch (err) { - console.error('oops'); + console.error('Failed to fetch merged pull request details', err); } - // const headers = await getHeaders(url.toString() as Link, token); - - // await axios.post(url.toString(), combined, { headers }); - // } catch (err) { - // rendererLogError( - // 'enrichNotifications', - // 'failed to fetch batch pull request details', - // err, - // ); - // } - const enrichedNotifications = await Promise.all( notifications.map(async (notification: Notification) => { return enrichNotification(notification, settings); From 2ab9f687065f9fe6708f3fada598504a40517381 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Fri, 26 Dec 2025 15:45:24 +1000 Subject: [PATCH 4/9] feat(api): merge query Signed-off-by: Adam Setch --- .../utils/notifications/notifications.ts | 188 +++++++++++++----- 1 file changed, 140 insertions(+), 48 deletions(-) diff --git a/src/renderer/utils/notifications/notifications.ts b/src/renderer/utils/notifications/notifications.ts index 7f89c87e0..d320bd815 100644 --- a/src/renderer/utils/notifications/notifications.ts +++ b/src/renderer/utils/notifications/notifications.ts @@ -10,7 +10,11 @@ import type { import type { Notification } from '../../typesGitHub'; import { listNotificationsForAuthenticatedUser } from '../api/client'; import { determineFailureType } from '../api/errors'; -import { PullRequestDetailsFragmentDoc } from '../api/graphql/generated/graphql'; +import { + DiscussionDetailsFragmentDoc, + IssueDetailsFragmentDoc, + PullRequestDetailsFragmentDoc, +} from '../api/graphql/generated/graphql'; import { getHeaders } from '../api/request'; import { getGitHubGraphQLUrl, getNumberFromUrl } from '../api/utils'; import { rendererLogError, rendererLogWarn } from '../logger'; @@ -134,36 +138,131 @@ export async function enrichNotifications( if (!settings.detailedNotifications) { return notifications; } + type NotificationKind = 'PullRequest' | 'Issue' | 'Discussion'; + + type QueryConfig = { + aliasPrefix: string; + fragment: string; + extras: Array<{ + name: string; + type: string; + defaultValue: number | boolean; + }>; + selection: (index: number) => string; + }; - // Build combined query for pull requests - let mergedQuery = ''; - let i = 0; - const args: Array<{ org: string; repo: string; number: number }> = []; - - for (const notification of notifications) { - if (notification.subject.type === 'PullRequest') { - const org = notification.repository.owner.login; - const repo = notification.repository.name; - const number = getNumberFromUrl(notification.subject.url); - - args.push({ - org, - repo, - number, - }); - - mergedQuery += `pr${i}: repository(owner: $owner${i}, name: $name${i}) { - pullRequest(number: $number${i}) { + const queryConfigs: Record = { + PullRequest: { + aliasPrefix: 'pr', + fragment: 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 }, + ], + selection: ( + index: number, + ) => `pr${index}: repository(owner: $owner${index}, name: $name${index}) { + pullRequest(number: $number${index}) { ...PullRequestDetails } - }\n`; + }`, + }, + Issue: { + aliasPrefix: 'issue', + fragment: IssueDetailsFragmentDoc.toString(), + extras: [ + { name: 'lastComments', type: 'Int', defaultValue: 100 }, + { name: 'firstLabels', type: 'Int', defaultValue: 100 }, + ], + selection: ( + index: number, + ) => `issue${index}: repository(owner: $owner${index}, name: $name${index}) { + issue(number: $number${index}) { + ...IssueDetails + } + }`, + }, + Discussion: { + aliasPrefix: 'discussion', + fragment: 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, + ) => `discussion${index}: repository(owner: $owner${index}, name: $name${index}) { + discussion(number: $number${index}) { + ...DiscussionDetails + } + }`, + }, + }; + + const selections: string[] = []; + const variableDefinitions: string[] = []; + const variableValues: Record = {}; + const extraVariableDefinitions = new Map(); + const extraVariableValues: Record = {}; + const fragments = new Map(); + + 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()); + } + } + }; - i += 1; + let index = 0; + + for (const notification of notifications) { + const kind = notification.subject.type as NotificationKind; + const config = queryConfigs[kind]; + + if (!config) { + continue; + } + + const org = notification.repository.owner.login; + const repo = notification.repository.name; + const number = getNumberFromUrl(notification.subject.url); + + selections.push(config.selection(index)); + variableDefinitions.push( + `$owner${index}: String!, $name${index}: String!, $number${index}: Int!`, + ); + variableValues[`owner${index}`] = org; + variableValues[`name${index}`] = repo; + variableValues[`number${index}`] = number; + + for (const extra of config.extras) { + if (!extraVariableDefinitions.has(extra.name)) { + extraVariableDefinitions.set(extra.name, extra.type); + extraVariableValues[extra.name] = extra.defaultValue; + } } + + collectFragments(config.fragment); + + index += 1; } - // If no pull requests, return early - if (args.length === 0) { + if (selections.length === 0) { const enrichedNotifications = await Promise.all( notifications.map(async (notification: Notification) => { return enrichNotification(notification, settings); @@ -172,33 +271,26 @@ export async function enrichNotifications( return enrichedNotifications; } - let queryArgs = ''; - const queryArgsVariables: Record = {}; + const combinedVariableDefinitions = [ + ...variableDefinitions, + ...Array.from(extraVariableDefinitions.entries()).map( + ([name, type]) => `$${name}: ${type}`, + ), + ].join(', '); - for (let idx = 0; idx < args.length; idx++) { - const arg = args[idx]; - if (idx > 0) { - queryArgs += ', '; - } - queryArgs += `$owner${idx}: String!, $name${idx}: String!, $number${idx}: Int!`; - queryArgsVariables[`owner${idx}`] = arg.org; - queryArgsVariables[`name${idx}`] = arg.repo; - queryArgsVariables[`number${idx}`] = arg.number; - } + const mergedQuery = `query FetchMergedNotifications(${combinedVariableDefinitions}) { +${selections.join('\n')} +} - // Add variables from PullRequestDetailsFragment - queryArgs += - ', $firstLabels: Int, $lastComments: Int, $lastReviews: Int, $firstClosingIssues: Int'; - queryArgsVariables['firstLabels'] = 100; - queryArgsVariables['lastComments'] = 100; - queryArgsVariables['lastReviews'] = 100; - queryArgsVariables['firstClosingIssues'] = 100; +${Array.from(fragments.values()).join('\n')}`; - const fragmentQuery = PullRequestDetailsFragmentDoc.toString(); - mergedQuery = `query FetchMergedPullRequests(${queryArgs}) {\n${mergedQuery}}\n\n${fragmentQuery}`; + const queryVariables = { + ...variableValues, + ...extraVariableValues, + }; console.log('MERGED QUERY ', JSON.stringify(mergedQuery, null, 2)); - console.log('MERGED ARGS ', JSON.stringify(queryArgsVariables, null, 2)); + console.log('MERGED ARGS ', JSON.stringify(queryVariables, null, 2)); try { const url = getGitHubGraphQLUrl( @@ -213,14 +305,14 @@ export async function enrichNotifications( url, data: { query: mergedQuery, - variables: queryArgsVariables, + variables: queryVariables, }, headers: headers, }).then((response) => { console.log('MERGED RESPONSE ', JSON.stringify(response, null, 2)); }); } catch (err) { - console.error('Failed to fetch merged pull request details', err); + console.error('Failed to fetch merged notification details', err); } const enrichedNotifications = await Promise.all( From 5bd4cd85f354177b5c8d703fe189be4848d37938 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Fri, 26 Dec 2025 16:03:51 +1000 Subject: [PATCH 5/9] feat(api): merge query Signed-off-by: Adam Setch --- .../notifications/handlers/checkSuite.ts | 1 + .../utils/notifications/handlers/commit.ts | 1 + .../utils/notifications/handlers/default.ts | 12 ++- .../notifications/handlers/discussion.ts | 7 +- .../utils/notifications/handlers/issue.ts | 5 +- .../notifications/handlers/pullRequest.ts | 11 ++- .../utils/notifications/handlers/types.ts | 11 ++- .../utils/notifications/notifications.ts | 75 +++++++++++++++++-- 8 files changed, 103 insertions(+), 20 deletions(-) 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..86979cd11 100644 --- a/src/renderer/utils/notifications/handlers/commit.ts +++ b/src/renderer/utils/notifications/handlers/commit.ts @@ -21,6 +21,7 @@ class CommitHandler extends DefaultHandler { async enrich( 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 0cc520f87..601977406 100644 --- a/src/renderer/utils/notifications/handlers/default.ts +++ b/src/renderer/utils/notifications/handlers/default.ts @@ -16,13 +16,17 @@ export class DefaultHandler implements NotificationTypeHandler { return null; } - async enrich( - _notification: Notification, - _settings: SettingsState, - ): Promise { + async enrich(_fetchedData?: unknown): Promise { return null; } + async fetchAndEnrich( + notification: Notification, + settings: SettingsState, + ): Promise { + return this.enrich(notification, settings, undefined); + } + iconType(_subject: Subject): FC | null { return QuestionIcon; } diff --git a/src/renderer/utils/notifications/handlers/discussion.ts b/src/renderer/utils/notifications/handlers/discussion.ts index 57ca0e651..fb954ca6c 100644 --- a/src/renderer/utils/notifications/handlers/discussion.ts +++ b/src/renderer/utils/notifications/handlers/discussion.ts @@ -22,6 +22,7 @@ import { fetchDiscussionByNumber } from '../../api/client'; import type { CommentFieldsFragment, DiscussionCommentFieldsFragment, + DiscussionDetailsFragment, } from '../../api/graphql/generated/graphql'; import { DefaultHandler, defaultHandler } from './default'; import { getNotificationAuthor } from './utils'; @@ -32,9 +33,11 @@ class DiscussionHandler extends DefaultHandler { 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.repository?.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..45371de7c 100644 --- a/src/renderer/utils/notifications/handlers/issue.ts +++ b/src/renderer/utils/notifications/handlers/issue.ts @@ -17,6 +17,7 @@ import type { import { IconColor } from '../../../types'; import type { Notification, Subject } from '../../../typesGitHub'; import { fetchIssueByNumber } from '../../api/client'; +import type { IssueDetailsFragment } from '../../api/graphql/generated/graphql'; import { DefaultHandler, defaultHandler } from './default'; import { getNotificationAuthor } from './utils'; @@ -26,9 +27,9 @@ class IssueHandler extends DefaultHandler { async enrich( 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..137ced0b5 100644 --- a/src/renderer/utils/notifications/handlers/pullRequest.ts +++ b/src/renderer/utils/notifications/handlers/pullRequest.ts @@ -19,7 +19,10 @@ 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, + PullRequestReviewFieldsFragment, +} from '../../api/graphql/generated/graphql'; import { DefaultHandler, defaultHandler } from './default'; import { getNotificationAuthor } from './utils'; @@ -29,9 +32,11 @@ class PullRequestHandler extends DefaultHandler { 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.repository.pullRequest; let prState: GitifyPullRequestState = pr.state; if (pr.isDraft) { diff --git a/src/renderer/utils/notifications/handlers/types.ts b/src/renderer/utils/notifications/handlers/types.ts index df3ce16d7..8341e8537 100644 --- a/src/renderer/utils/notifications/handlers/types.ts +++ b/src/renderer/utils/notifications/handlers/types.ts @@ -5,7 +5,7 @@ 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 interface NotificationTypeHandler { readonly type?: SubjectType; query(notification: Notification): { query; variables } | null; @@ -16,6 +16,15 @@ export interface NotificationTypeHandler { enrich( notification: Notification, settings: SettingsState, + fetchedData?: TFragment, + ): Promise; + + /** + * Fetch remote data (if needed) and enrich a notification. + */ + fetchAndEnrich( + notification: Notification, + settings: SettingsState, ): Promise; /** diff --git a/src/renderer/utils/notifications/notifications.ts b/src/renderer/utils/notifications/notifications.ts index d320bd815..9be90acc8 100644 --- a/src/renderer/utils/notifications/notifications.ts +++ b/src/renderer/utils/notifications/notifications.ts @@ -209,6 +209,11 @@ export async function enrichNotifications( 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 = @@ -242,6 +247,8 @@ export async function enrichNotifications( const repo = notification.repository.name; const number = getNumberFromUrl(notification.subject.url); + const alias = `${config.aliasPrefix}${index}`; + selections.push(config.selection(index)); variableDefinitions.push( `$owner${index}: String!, $name${index}: String!, $number${index}: Int!`, @@ -249,6 +256,7 @@ export async function enrichNotifications( 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)) { @@ -289,8 +297,7 @@ ${Array.from(fragments.values()).join('\n')}`; ...extraVariableValues, }; - console.log('MERGED QUERY ', JSON.stringify(mergedQuery, null, 2)); - console.log('MERGED ARGS ', JSON.stringify(queryVariables, null, 2)); + let mergedData: Record | null = null; try { const url = getGitHubGraphQLUrl( @@ -300,7 +307,7 @@ ${Array.from(fragments.values()).join('\n')}`; const headers = await getHeaders(url as Link, token); - axios({ + const response = await axios({ method: 'POST', url, data: { @@ -308,16 +315,65 @@ ${Array.from(fragments.values()).join('\n')}`; variables: queryVariables, }, headers: headers, - }).then((response) => { - console.log('MERGED RESPONSE ', JSON.stringify(response, null, 2)); }); + + mergedData = + (response.data as { data?: Record })?.data ?? null; } catch (err) { - console.error('Failed to fetch merged notification details', 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, + }, + }; }), ); @@ -339,7 +395,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', From 996bc3e1746093e26a0274acfbb0425fda2690bf Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Sat, 27 Dec 2025 08:24:48 +1000 Subject: [PATCH 6/9] feat(api): merge query Signed-off-by: Adam Setch --- .../utils/notifications/handlers/default.ts | 14 +++++++++----- src/renderer/utils/notifications/handlers/issue.ts | 3 +-- src/renderer/utils/notifications/handlers/types.ts | 12 ++++++------ 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/renderer/utils/notifications/handlers/default.ts b/src/renderer/utils/notifications/handlers/default.ts index 601977406..7997e4e94 100644 --- a/src/renderer/utils/notifications/handlers/default.ts +++ b/src/renderer/utils/notifications/handlers/default.ts @@ -16,15 +16,19 @@ export class DefaultHandler implements NotificationTypeHandler { return null; } - async enrich(_fetchedData?: unknown): Promise { + async fetchAndEnrich( + _notification: Notification, + _settings: SettingsState, + ): Promise { return null; } - async fetchAndEnrich( - notification: Notification, - settings: SettingsState, + async enrich( + _notification: Notification, + _settings: SettingsState, + _fetchedData?: unknown, ): Promise { - return this.enrich(notification, settings, undefined); + return null; } iconType(_subject: Subject): FC | null { diff --git a/src/renderer/utils/notifications/handlers/issue.ts b/src/renderer/utils/notifications/handlers/issue.ts index 45371de7c..14370a0e4 100644 --- a/src/renderer/utils/notifications/handlers/issue.ts +++ b/src/renderer/utils/notifications/handlers/issue.ts @@ -16,7 +16,6 @@ import type { } from '../../../types'; import { IconColor } from '../../../types'; import type { Notification, Subject } from '../../../typesGitHub'; -import { fetchIssueByNumber } from '../../api/client'; import type { IssueDetailsFragment } from '../../api/graphql/generated/graphql'; import { DefaultHandler, defaultHandler } from './default'; import { getNotificationAuthor } from './utils'; @@ -25,7 +24,7 @@ class IssueHandler extends DefaultHandler { readonly type = 'Issue'; async enrich( - notification: Notification, + _notification: Notification, _settings: SettingsState, fetchedData?: IssueDetailsFragment, ): Promise { diff --git a/src/renderer/utils/notifications/handlers/types.ts b/src/renderer/utils/notifications/handlers/types.ts index 8341e8537..3c3c3be91 100644 --- a/src/renderer/utils/notifications/handlers/types.ts +++ b/src/renderer/utils/notifications/handlers/types.ts @@ -11,20 +11,20 @@ export interface NotificationTypeHandler { query(notification: Notification): { query; variables } | null; /** - * Enrich a notification. Settings may be unused for some handlers. + * Fetch remote data (if needed) and enrich a notification. */ - enrich( + fetchAndEnrich( notification: Notification, settings: SettingsState, - fetchedData?: TFragment, ): Promise; /** - * Fetch remote data (if needed) and enrich a notification. + * Enrich a notification. Settings may be unused for some handlers. */ - fetchAndEnrich( + enrich( notification: Notification, - settings: SettingsState, + settings?: SettingsState, + fetchedData?: TFragment, ): Promise; /** From ee79b7fed823f5fdbc99ec683721d3f72e84c287 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Sat, 27 Dec 2025 08:26:30 +1000 Subject: [PATCH 7/9] feat(api): merge query Signed-off-by: Adam Setch --- package.json | 3 +-- pnpm-lock.yaml | 12 ------------ 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/package.json b/package.json index 5c85a3151..3c39b15f6 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,6 @@ "dependencies": { "electron-log": "5.4.3", "electron-updater": "6.6.2", - "graphql-combine-query": "^1.2.4", "menubar": "9.5.2", "react": "19.2.3", "react-dom": "19.2.3", @@ -149,4 +148,4 @@ "*": "biome check --fix --no-errors-on-unmatched", "*.{js,ts,tsx}": "pnpm test --findRelatedTests --passWithNoTests --updateSnapshot" } -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ef40217d1..636f76e84 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,9 +14,6 @@ importers: electron-updater: specifier: 6.6.2 version: 6.6.2 - graphql-combine-query: - specifier: ^1.2.4 - version: 1.2.4(graphql@16.12.0) menubar: specifier: 9.5.2 version: 9.5.2(electron@39.2.7) @@ -3510,11 +3507,6 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - graphql-combine-query@1.2.4: - resolution: {integrity: sha512-Os0d5cS8nH1cRnejGyACda0lraPzMJJjtd21KrfjKi1NWgEgzXzP8s29cpLlne+Y+51/2mS4j/3aTaIlCYMexg==} - peerDependencies: - graphql: ^15.1.0 || ^16.0.0 - graphql-config@5.1.5: resolution: {integrity: sha512-mG2LL1HccpU8qg5ajLROgdsBzx/o2M6kgI3uAmoaXiSH9PCUbtIyLomLqUtCFaAeG2YCFsl0M5cfQ9rKmDoMVA==} engines: {node: '>= 16.0.0'} @@ -10027,10 +10019,6 @@ snapshots: graceful-fs@4.2.11: {} - graphql-combine-query@1.2.4(graphql@16.12.0): - dependencies: - graphql: 16.12.0 - graphql-config@5.1.5(@types/node@24.10.4)(graphql@16.12.0)(typescript@5.9.3): dependencies: '@graphql-tools/graphql-file-loader': 8.1.8(graphql@16.12.0) From 6bccaa9fcce6821ec6cadbb8bdeda2a3a6e73efe Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Sat, 27 Dec 2025 08:35:15 +1000 Subject: [PATCH 8/9] feat(api): merge query Signed-off-by: Adam Setch --- package.json | 2 +- src/renderer/utils/notifications/handlers/commit.ts | 2 +- src/renderer/utils/notifications/handlers/release.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 3c39b15f6..6f9c4c233 100644 --- a/package.json +++ b/package.json @@ -148,4 +148,4 @@ "*": "biome check --fix --no-errors-on-unmatched", "*.{js,ts,tsx}": "pnpm test --findRelatedTests --passWithNoTests --updateSnapshot" } -} \ No newline at end of file +} diff --git a/src/renderer/utils/notifications/handlers/commit.ts b/src/renderer/utils/notifications/handlers/commit.ts index 86979cd11..4f208e8dc 100644 --- a/src/renderer/utils/notifications/handlers/commit.ts +++ b/src/renderer/utils/notifications/handlers/commit.ts @@ -18,7 +18,7 @@ import { getNotificationAuthor } from './utils'; class CommitHandler extends DefaultHandler { readonly type = 'Commit'; - async enrich( + async fetchAndEnrich( notification: Notification, settings: SettingsState, _fetchedData?: unknown, 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 { From 4dc178511baf7e80ac4b3a76e7994054ab5d26d6 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Sat, 27 Dec 2025 16:16:39 +1000 Subject: [PATCH 9/9] feat(api): merge query Signed-off-by: Adam Setch --- src/renderer/utils/api/client.ts | 22 +- .../utils/api/graphql/discussion.graphql | 14 +- .../utils/api/graphql/generated/gql.ts | 18 +- .../utils/api/graphql/generated/graphql.ts | 285 ++++++++++++++++-- src/renderer/utils/api/graphql/issue.graphql | 13 +- src/renderer/utils/api/graphql/pull.graphql | 14 +- src/renderer/utils/api/graphql/utils.ts | 22 ++ .../utils/notifications/handlers/default.ts | 4 + .../notifications/handlers/discussion.ts | 36 ++- .../utils/notifications/handlers/issue.ts | 29 +- .../notifications/handlers/pullRequest.ts | 27 +- .../utils/notifications/handlers/types.ts | 12 + .../utils/notifications/notifications.ts | 88 +----- 13 files changed, 435 insertions(+), 149 deletions(-) create mode 100644 src/renderer/utils/api/graphql/utils.ts 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/default.ts b/src/renderer/utils/notifications/handlers/default.ts index 7997e4e94..9d2a18685 100644 --- a/src/renderer/utils/notifications/handlers/default.ts +++ b/src/renderer/utils/notifications/handlers/default.ts @@ -12,6 +12,10 @@ import { formatForDisplay } from './utils'; export class DefaultHandler implements NotificationTypeHandler { type?: SubjectType; + mergeQueryConfig() { + return undefined; + } + query(_notification: Notification) { return null; } diff --git a/src/renderer/utils/notifications/handlers/discussion.ts b/src/renderer/utils/notifications/handlers/discussion.ts index fb954ca6c..92aab59c0 100644 --- a/src/renderer/utils/notifications/handlers/discussion.ts +++ b/src/renderer/utils/notifications/handlers/discussion.ts @@ -19,17 +19,43 @@ import { } from '../../../types'; import type { Notification, Subject } from '../../../typesGitHub'; import { fetchDiscussionByNumber } from '../../api/client'; -import type { - CommentFieldsFragment, - DiscussionCommentFieldsFragment, - DiscussionDetailsFragment, +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, @@ -37,7 +63,7 @@ class DiscussionHandler extends DefaultHandler { ): Promise { const discussion = fetchedData ?? - (await fetchDiscussionByNumber(notification)).data.repository?.discussion; + (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 14370a0e4..27b0556b5 100644 --- a/src/renderer/utils/notifications/handlers/issue.ts +++ b/src/renderer/utils/notifications/handlers/issue.ts @@ -16,13 +16,40 @@ import type { } from '../../../types'; import { IconColor } from '../../../types'; import type { Notification, Subject } from '../../../typesGitHub'; -import type { IssueDetailsFragment } from '../../api/graphql/generated/graphql'; +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, _settings: SettingsState, diff --git a/src/renderer/utils/notifications/handlers/pullRequest.ts b/src/renderer/utils/notifications/handlers/pullRequest.ts index 137ced0b5..4ca28ea49 100644 --- a/src/renderer/utils/notifications/handlers/pullRequest.ts +++ b/src/renderer/utils/notifications/handlers/pullRequest.ts @@ -19,16 +19,35 @@ import { } from '../../../types'; import type { Notification, Subject } from '../../../typesGitHub'; import { fetchPullByNumber } from '../../api/client'; -import type { - PullRequestDetailsFragment, - PullRequestReviewFieldsFragment, +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, @@ -36,7 +55,7 @@ class PullRequestHandler extends DefaultHandler { ): Promise { const pr = fetchedData ?? - (await fetchPullByNumber(notification)).data.repository.pullRequest; + (await fetchPullByNumber(notification)).data.nodeINDEX.pullRequest; let prState: GitifyPullRequestState = pr.state; if (pr.isDraft) { diff --git a/src/renderer/utils/notifications/handlers/types.ts b/src/renderer/utils/notifications/handlers/types.ts index 3c3c3be91..29e028591 100644 --- a/src/renderer/utils/notifications/handlers/types.ts +++ b/src/renderer/utils/notifications/handlers/types.ts @@ -5,9 +5,21 @@ import type { OcticonProps } from '@primer/octicons-react'; import type { GitifySubject, Link, SettingsState } from '../../../types'; import type { Notification, Subject, SubjectType } from '../../../typesGitHub'; +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; /** diff --git a/src/renderer/utils/notifications/notifications.ts b/src/renderer/utils/notifications/notifications.ts index 9be90acc8..791dd6c88 100644 --- a/src/renderer/utils/notifications/notifications.ts +++ b/src/renderer/utils/notifications/notifications.ts @@ -10,11 +10,6 @@ import type { import type { Notification } from '../../typesGitHub'; import { listNotificationsForAuthenticatedUser } from '../api/client'; import { determineFailureType } from '../api/errors'; -import { - DiscussionDetailsFragmentDoc, - IssueDetailsFragmentDoc, - PullRequestDetailsFragmentDoc, -} from '../api/graphql/generated/graphql'; import { getHeaders } from '../api/request'; import { getGitHubGraphQLUrl, getNumberFromUrl } from '../api/utils'; import { rendererLogError, rendererLogWarn } from '../logger'; @@ -140,69 +135,6 @@ export async function enrichNotifications( } type NotificationKind = 'PullRequest' | 'Issue' | 'Discussion'; - type QueryConfig = { - aliasPrefix: string; - fragment: string; - extras: Array<{ - name: string; - type: string; - defaultValue: number | boolean; - }>; - selection: (index: number) => string; - }; - - const queryConfigs: Record = { - PullRequest: { - aliasPrefix: 'pr', - fragment: 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 }, - ], - selection: ( - index: number, - ) => `pr${index}: repository(owner: $owner${index}, name: $name${index}) { - pullRequest(number: $number${index}) { - ...PullRequestDetails - } - }`, - }, - Issue: { - aliasPrefix: 'issue', - fragment: IssueDetailsFragmentDoc.toString(), - extras: [ - { name: 'lastComments', type: 'Int', defaultValue: 100 }, - { name: 'firstLabels', type: 'Int', defaultValue: 100 }, - ], - selection: ( - index: number, - ) => `issue${index}: repository(owner: $owner${index}, name: $name${index}) { - issue(number: $number${index}) { - ...IssueDetails - } - }`, - }, - Discussion: { - aliasPrefix: 'discussion', - fragment: 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, - ) => `discussion${index}: repository(owner: $owner${index}, name: $name${index}) { - discussion(number: $number${index}) { - ...DiscussionDetails - } - }`, - }, - }; - const selections: string[] = []; const variableDefinitions: string[] = []; const variableValues: Record = {}; @@ -236,8 +168,9 @@ export async function enrichNotifications( let index = 0; for (const notification of notifications) { + const handler = createNotificationHandler(notification); const kind = notification.subject.type as NotificationKind; - const config = queryConfigs[kind]; + const config = handler.mergeQueryConfig(); if (!config) { continue; @@ -247,9 +180,13 @@ export async function enrichNotifications( const repo = notification.repository.name; const number = getNumberFromUrl(notification.subject.url); - const alias = `${config.aliasPrefix}${index}`; + const alias = `node${index}`; + const queryFragment = config.queryFragment.replaceAll( + 'INDEX', + index.toString(), + ); - selections.push(config.selection(index)); + selections.push(queryFragment); variableDefinitions.push( `$owner${index}: String!, $name${index}: String!, $number${index}: Int!`, ); @@ -265,7 +202,7 @@ export async function enrichNotifications( } } - collectFragments(config.fragment); + collectFragments(config.responseFragment); index += 1; } @@ -287,10 +224,11 @@ export async function enrichNotifications( ].join(', '); const mergedQuery = `query FetchMergedNotifications(${combinedVariableDefinitions}) { -${selections.join('\n')} -} + ${selections.join('\n')} + } -${Array.from(fragments.values()).join('\n')}`; + ${Array.from(fragments.values()).join('\n')} + `; const queryVariables = { ...variableValues,