Skip to content

Commit c6f1c51

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

File tree

3 files changed

+109
-55
lines changed

3 files changed

+109
-55
lines changed
Lines changed: 80 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,90 @@
1-
export function getQueryFragmentBody(doc: string): string | null {
2-
// Find fragment on Query pattern
3-
const fragmentMatch = doc.match(/fragment\s+\w+\s+on\s+Query\s+\{/);
4-
if (!fragmentMatch) {
5-
return null;
6-
}
1+
// Shared patterns for fragment parsing
2+
const FRAGMENT_BLOCK_REGEX =
3+
/fragment\s+[A-Za-z0-9_]+\s+on\s+[A-Za-z0-9_]+\s*\{/g;
4+
const FRAGMENT_NAME_REGEX = /fragment\s+([A-Za-z0-9_]+)\s+on\s+([A-Za-z0-9_]+)/;
75

8-
const start = fragmentMatch.index + fragmentMatch[0].length - 1; // Position of opening brace
9-
let depth = 0;
6+
type ParsedFragment = {
7+
name: string;
8+
onType: string;
9+
content: string; // inside the outermost braces
10+
full: string; // full fragment text including header and braces
11+
};
1012

11-
for (let i = start; i < doc.length; i++) {
12-
if (doc[i] === '{') {
13+
function sliceBraceBlock(doc: string, startIndex: number): string | null {
14+
let depth = 0;
15+
for (let i = startIndex; i < doc.length; i++) {
16+
const ch = doc[i];
17+
if (ch === '{') {
1318
depth++;
14-
} else if (doc[i] === '}') {
19+
} else if (ch === '}') {
1520
depth--;
1621
if (depth === 0) {
17-
return doc.slice(start + 1, i).trim();
22+
// Include braces
23+
return doc.slice(startIndex, i + 1);
1824
}
1925
}
2026
}
2127
return null;
2228
}
29+
30+
export function parseFragments(doc: string): ParsedFragment[] {
31+
const results: ParsedFragment[] = [];
32+
// Find each fragment header position
33+
const indices: number[] = [];
34+
for (const m of doc.matchAll(FRAGMENT_BLOCK_REGEX)) {
35+
if (m.index !== undefined) {
36+
indices.push(m.index);
37+
}
38+
}
39+
40+
for (const idx of indices) {
41+
const header = doc.slice(idx, idx + 200); // small window for name/type parse
42+
const nt = header.match(FRAGMENT_NAME_REGEX);
43+
if (!nt) {
44+
continue;
45+
}
46+
const name = nt[1];
47+
const onType = nt[2];
48+
// Find the opening brace for this header
49+
const bracePos = doc.indexOf('{', idx);
50+
if (bracePos === -1) {
51+
continue;
52+
}
53+
const block = sliceBraceBlock(doc, bracePos);
54+
if (!block) {
55+
continue;
56+
}
57+
const full = doc.slice(idx, bracePos) + block;
58+
const content = block.slice(1, block.length - 1).trim();
59+
results.push({ name, onType, content, full: full.trim() });
60+
}
61+
return results;
62+
}
63+
64+
export function getQueryFragmentBody(doc: string): string | null {
65+
const frags = parseFragments(doc);
66+
const qFrag = frags.find((f) => f.onType === 'Query');
67+
return qFrag?.content ?? null;
68+
}
69+
70+
export function extractFragments(doc: string): Map<string, string> {
71+
const frags = parseFragments(doc);
72+
const map = new Map<string, string>();
73+
for (const f of frags) {
74+
if (!map.has(f.name)) {
75+
map.set(f.name, f.full);
76+
}
77+
}
78+
return map;
79+
}
80+
81+
// Helper to compose a merged query given selections, fragments and variable defs
82+
export function composeMergedQuery(
83+
selections: string[],
84+
fragmentMap: Map<string, string>,
85+
variableDefinitions: string[],
86+
): string {
87+
const vars = variableDefinitions.join(', ');
88+
const frags = Array.from(fragmentMap.values()).join('\n');
89+
return `query FetchMergedNotifications(${vars}) {\n${selections.join('\n')}\n}\n\n${frags}\n`;
90+
}

src/renderer/utils/notifications/filters/search.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ describe('renderer/utils/notifications/filters/search.ts', () => {
2222

2323
it('matches each known qualifier by its exact prefix and additional value', () => {
2424
for (const q of ALL_SEARCH_QUALIFIERS) {
25-
const token = q.prefix + 'someValue';
25+
const token = `${q.prefix}someValue`;
2626
const parsed = parseSearchInput(token);
2727
expect(parsed).not.toBeNull();
2828
expect(parsed?.qualifier).toBe(q);

src/renderer/utils/notifications/notifications.ts

Lines changed: 28 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type {
1010
import type { Notification } from '../../typesGitHub';
1111
import { listNotificationsForAuthenticatedUser } from '../api/client';
1212
import { determineFailureType } from '../api/errors';
13+
import { composeMergedQuery, extractFragments } from '../api/graphql/utils';
1314
import { getHeaders } from '../api/request';
1415
import { getGitHubGraphQLUrl, getNumberFromUrl } from '../api/utils';
1516
import { rendererLogError, rendererLogWarn } from '../logger';
@@ -147,19 +148,10 @@ export async function enrichNotifications(
147148
}> = [];
148149

149150
const collectFragments = (doc: string) => {
150-
const fragmentRegex =
151-
/fragment\s+[A-Za-z0-9_]+\s+on[\s\S]*?(?=(?:fragment\s+[A-Za-z0-9_]+\s+on)|$)/g;
152-
const nameRegex = /fragment\s+([A-Za-z0-9_]+)\s+on/;
153-
154-
const matches = doc.match(fragmentRegex) ?? [];
155-
for (const match of matches) {
156-
const nameMatch = match.match(nameRegex);
157-
if (!nameMatch) {
158-
continue;
159-
}
160-
const name = nameMatch[1];
151+
const found = extractFragments(doc);
152+
for (const [name, frag] of found.entries()) {
161153
if (!fragments.has(name)) {
162-
fragments.set(name, match.trim());
154+
fragments.set(name, frag);
163155
}
164156
}
165157
};
@@ -201,9 +193,17 @@ export async function enrichNotifications(
201193
if (selections.length === 0) {
202194
// No handlers with mergeQueryConfig, just enrich individually
203195
return Promise.all(
204-
notifications.map((notification) =>
205-
enrichNotification(notification, settings),
206-
),
196+
notifications.map(async (notification) => {
197+
const handler = createNotificationHandler(notification);
198+
const details = await handler.enrich(notification, settings);
199+
return {
200+
...notification,
201+
subject: {
202+
...notification.subject,
203+
...details,
204+
},
205+
};
206+
}),
207207
);
208208
}
209209

@@ -212,14 +212,13 @@ export async function enrichNotifications(
212212
...Array.from(extraVariableDefinitions.entries()).map(
213213
([name, type]) => `$${name}: ${type}`,
214214
),
215-
].join(', ');
215+
];
216216

217-
const mergedQuery = `query FetchMergedNotifications(${combinedVariableDefinitions}) {
218-
${selections.join('\n')}
219-
}
220-
221-
${Array.from(fragments.values()).join('\n')}
222-
`;
217+
const mergedQuery = composeMergedQuery(
218+
selections,
219+
fragments,
220+
combinedVariableDefinitions,
221+
);
223222

224223
const queryVariables = {
225224
...variableValues,
@@ -259,12 +258,14 @@ export async function enrichNotifications(
259258
const enrichedNotifications = await Promise.all(
260259
notifications.map(async (notification: Notification) => {
261260
const target = targets.find((item) => item.notification === notification);
261+
const handler =
262+
target?.handler ?? createNotificationHandler(notification);
263+
264+
let fragment: unknown;
262265
if (mergedData && target) {
263-
// Try to find the first defined property in the repoData (pullRequest, issue, discussion, etc.)
264266
const repoData = mergedData[target.alias] as
265267
| Record<string, unknown>
266268
| undefined;
267-
let fragment: unknown;
268269
if (repoData) {
269270
for (const value of Object.values(repoData)) {
270271
if (value !== undefined) {
@@ -273,29 +274,14 @@ export async function enrichNotifications(
273274
}
274275
}
275276
}
276-
if (fragment) {
277-
const details = await target.handler.enrich(
278-
notification,
279-
settings,
280-
fragment,
281-
);
282-
return {
283-
...notification,
284-
subject: {
285-
...notification.subject,
286-
...details,
287-
},
288-
};
289-
}
290277
}
291-
// fallback
292-
const handler = createNotificationHandler(notification);
293-
const fetchedDetails = await handler.enrich(notification, settings);
278+
279+
const details = await handler.enrich(notification, settings, fragment);
294280
return {
295281
...notification,
296282
subject: {
297283
...notification.subject,
298-
...fetchedDetails,
284+
...details,
299285
},
300286
};
301287
}),

0 commit comments

Comments
 (0)