Skip to content

Commit 2ab9f68

Browse files
committed
feat(api): merge query
Signed-off-by: Adam Setch <[email protected]>
1 parent caf1476 commit 2ab9f68

File tree

1 file changed

+140
-48
lines changed

1 file changed

+140
-48
lines changed

src/renderer/utils/notifications/notifications.ts

Lines changed: 140 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ import type {
1010
import type { Notification } from '../../typesGitHub';
1111
import { listNotificationsForAuthenticatedUser } from '../api/client';
1212
import { determineFailureType } from '../api/errors';
13-
import { PullRequestDetailsFragmentDoc } from '../api/graphql/generated/graphql';
13+
import {
14+
DiscussionDetailsFragmentDoc,
15+
IssueDetailsFragmentDoc,
16+
PullRequestDetailsFragmentDoc,
17+
} from '../api/graphql/generated/graphql';
1418
import { getHeaders } from '../api/request';
1519
import { getGitHubGraphQLUrl, getNumberFromUrl } from '../api/utils';
1620
import { rendererLogError, rendererLogWarn } from '../logger';
@@ -134,36 +138,131 @@ export async function enrichNotifications(
134138
if (!settings.detailedNotifications) {
135139
return notifications;
136140
}
141+
type NotificationKind = 'PullRequest' | 'Issue' | 'Discussion';
142+
143+
type QueryConfig = {
144+
aliasPrefix: string;
145+
fragment: string;
146+
extras: Array<{
147+
name: string;
148+
type: string;
149+
defaultValue: number | boolean;
150+
}>;
151+
selection: (index: number) => string;
152+
};
137153

138-
// Build combined query for pull requests
139-
let mergedQuery = '';
140-
let i = 0;
141-
const args: Array<{ org: string; repo: string; number: number }> = [];
142-
143-
for (const notification of notifications) {
144-
if (notification.subject.type === 'PullRequest') {
145-
const org = notification.repository.owner.login;
146-
const repo = notification.repository.name;
147-
const number = getNumberFromUrl(notification.subject.url);
148-
149-
args.push({
150-
org,
151-
repo,
152-
number,
153-
});
154-
155-
mergedQuery += `pr${i}: repository(owner: $owner${i}, name: $name${i}) {
156-
pullRequest(number: $number${i}) {
154+
const queryConfigs: Record<NotificationKind, QueryConfig> = {
155+
PullRequest: {
156+
aliasPrefix: 'pr',
157+
fragment: PullRequestDetailsFragmentDoc.toString(),
158+
extras: [
159+
{ name: 'firstLabels', type: 'Int', defaultValue: 100 },
160+
{ name: 'lastComments', type: 'Int', defaultValue: 100 },
161+
{ name: 'lastReviews', type: 'Int', defaultValue: 100 },
162+
{ name: 'firstClosingIssues', type: 'Int', defaultValue: 100 },
163+
],
164+
selection: (
165+
index: number,
166+
) => `pr${index}: repository(owner: $owner${index}, name: $name${index}) {
167+
pullRequest(number: $number${index}) {
157168
...PullRequestDetails
158169
}
159-
}\n`;
170+
}`,
171+
},
172+
Issue: {
173+
aliasPrefix: 'issue',
174+
fragment: IssueDetailsFragmentDoc.toString(),
175+
extras: [
176+
{ name: 'lastComments', type: 'Int', defaultValue: 100 },
177+
{ name: 'firstLabels', type: 'Int', defaultValue: 100 },
178+
],
179+
selection: (
180+
index: number,
181+
) => `issue${index}: repository(owner: $owner${index}, name: $name${index}) {
182+
issue(number: $number${index}) {
183+
...IssueDetails
184+
}
185+
}`,
186+
},
187+
Discussion: {
188+
aliasPrefix: 'discussion',
189+
fragment: DiscussionDetailsFragmentDoc.toString(),
190+
extras: [
191+
{ name: 'lastComments', type: 'Int', defaultValue: 100 },
192+
{ name: 'lastReplies', type: 'Int', defaultValue: 100 },
193+
{ name: 'firstLabels', type: 'Int', defaultValue: 100 },
194+
{ name: 'includeIsAnswered', type: 'Boolean!', defaultValue: true },
195+
],
196+
selection: (
197+
index: number,
198+
) => `discussion${index}: repository(owner: $owner${index}, name: $name${index}) {
199+
discussion(number: $number${index}) {
200+
...DiscussionDetails
201+
}
202+
}`,
203+
},
204+
};
205+
206+
const selections: string[] = [];
207+
const variableDefinitions: string[] = [];
208+
const variableValues: Record<string, string | number | boolean> = {};
209+
const extraVariableDefinitions = new Map<string, string>();
210+
const extraVariableValues: Record<string, number | boolean> = {};
211+
const fragments = new Map<string, string>();
212+
213+
const collectFragments = (doc: string) => {
214+
const fragmentRegex =
215+
/fragment\s+[A-Za-z0-9_]+\s+on[\s\S]*?(?=(?:fragment\s+[A-Za-z0-9_]+\s+on)|$)/g;
216+
const nameRegex = /fragment\s+([A-Za-z0-9_]+)\s+on/;
217+
218+
const matches = doc.match(fragmentRegex) ?? [];
219+
for (const match of matches) {
220+
const nameMatch = match.match(nameRegex);
221+
if (!nameMatch) {
222+
continue;
223+
}
224+
const name = nameMatch[1];
225+
if (!fragments.has(name)) {
226+
fragments.set(name, match.trim());
227+
}
228+
}
229+
};
160230

161-
i += 1;
231+
let index = 0;
232+
233+
for (const notification of notifications) {
234+
const kind = notification.subject.type as NotificationKind;
235+
const config = queryConfigs[kind];
236+
237+
if (!config) {
238+
continue;
239+
}
240+
241+
const org = notification.repository.owner.login;
242+
const repo = notification.repository.name;
243+
const number = getNumberFromUrl(notification.subject.url);
244+
245+
selections.push(config.selection(index));
246+
variableDefinitions.push(
247+
`$owner${index}: String!, $name${index}: String!, $number${index}: Int!`,
248+
);
249+
variableValues[`owner${index}`] = org;
250+
variableValues[`name${index}`] = repo;
251+
variableValues[`number${index}`] = number;
252+
253+
for (const extra of config.extras) {
254+
if (!extraVariableDefinitions.has(extra.name)) {
255+
extraVariableDefinitions.set(extra.name, extra.type);
256+
extraVariableValues[extra.name] = extra.defaultValue;
257+
}
162258
}
259+
260+
collectFragments(config.fragment);
261+
262+
index += 1;
163263
}
164264

165-
// If no pull requests, return early
166-
if (args.length === 0) {
265+
if (selections.length === 0) {
167266
const enrichedNotifications = await Promise.all(
168267
notifications.map(async (notification: Notification) => {
169268
return enrichNotification(notification, settings);
@@ -172,33 +271,26 @@ export async function enrichNotifications(
172271
return enrichedNotifications;
173272
}
174273

175-
let queryArgs = '';
176-
const queryArgsVariables: Record<string, string | number> = {};
274+
const combinedVariableDefinitions = [
275+
...variableDefinitions,
276+
...Array.from(extraVariableDefinitions.entries()).map(
277+
([name, type]) => `$${name}: ${type}`,
278+
),
279+
].join(', ');
177280

178-
for (let idx = 0; idx < args.length; idx++) {
179-
const arg = args[idx];
180-
if (idx > 0) {
181-
queryArgs += ', ';
182-
}
183-
queryArgs += `$owner${idx}: String!, $name${idx}: String!, $number${idx}: Int!`;
184-
queryArgsVariables[`owner${idx}`] = arg.org;
185-
queryArgsVariables[`name${idx}`] = arg.repo;
186-
queryArgsVariables[`number${idx}`] = arg.number;
187-
}
281+
const mergedQuery = `query FetchMergedNotifications(${combinedVariableDefinitions}) {
282+
${selections.join('\n')}
283+
}
188284
189-
// Add variables from PullRequestDetailsFragment
190-
queryArgs +=
191-
', $firstLabels: Int, $lastComments: Int, $lastReviews: Int, $firstClosingIssues: Int';
192-
queryArgsVariables['firstLabels'] = 100;
193-
queryArgsVariables['lastComments'] = 100;
194-
queryArgsVariables['lastReviews'] = 100;
195-
queryArgsVariables['firstClosingIssues'] = 100;
285+
${Array.from(fragments.values()).join('\n')}`;
196286

197-
const fragmentQuery = PullRequestDetailsFragmentDoc.toString();
198-
mergedQuery = `query FetchMergedPullRequests(${queryArgs}) {\n${mergedQuery}}\n\n${fragmentQuery}`;
287+
const queryVariables = {
288+
...variableValues,
289+
...extraVariableValues,
290+
};
199291

200292
console.log('MERGED QUERY ', JSON.stringify(mergedQuery, null, 2));
201-
console.log('MERGED ARGS ', JSON.stringify(queryArgsVariables, null, 2));
293+
console.log('MERGED ARGS ', JSON.stringify(queryVariables, null, 2));
202294

203295
try {
204296
const url = getGitHubGraphQLUrl(
@@ -213,14 +305,14 @@ export async function enrichNotifications(
213305
url,
214306
data: {
215307
query: mergedQuery,
216-
variables: queryArgsVariables,
308+
variables: queryVariables,
217309
},
218310
headers: headers,
219311
}).then((response) => {
220312
console.log('MERGED RESPONSE ', JSON.stringify(response, null, 2));
221313
});
222314
} catch (err) {
223-
console.error('Failed to fetch merged pull request details', err);
315+
console.error('Failed to fetch merged notification details', err);
224316
}
225317

226318
const enrichedNotifications = await Promise.all(

0 commit comments

Comments
 (0)