Skip to content

Commit ef6ecf8

Browse files
authored
feat(api): replaces REST search with GraphQL search for issues and PRs (#14)
* feat(api): replaces REST search with GraphQL search for issues and PRs * fix(api): guards against malformed nameWithOwner and null endCursor * chore(api): updates stale JSDoc, removes orphaned fixtures * fix(api): extracts partial GraphQL data, fixes notification sources * fix(api): hoists fork types, removes dead code, adds missing tests * fix(api): stops pagination after partial GraphQL error * test(api): adds coverage for GraphQL partial error extraction * fix(api): adds catch-all for unexpected response shapes in pagination * test(api): covers nameWithOwner, endCursor, fork fallback * fix(api): addresses PR review findings for search and tests * fix(api): adds missing tests and fork partial-error recovery
1 parent 46e56b9 commit ef6ecf8

File tree

21 files changed

+1569
-2068
lines changed

21 files changed

+1569
-2068
lines changed

e2e/settings.spec.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,19 @@ async function setupAuth(page: Page) {
1616
},
1717
})
1818
);
19-
await page.route("https://api.github.com/search/issues*", (route) =>
20-
route.fulfill({
21-
status: 200,
22-
json: { total_count: 0, incomplete_results: false, items: [] },
23-
})
24-
);
2519
await page.route("https://api.github.com/notifications*", (route) =>
2620
route.fulfill({ status: 200, json: [] })
2721
);
2822
await page.route("https://api.github.com/graphql", (route) =>
29-
route.fulfill({ status: 200, json: { data: {} } })
23+
route.fulfill({
24+
status: 200,
25+
json: {
26+
data: {
27+
search: { issueCount: 0, pageInfo: { hasNextPage: false, endCursor: null }, nodes: [] },
28+
rateLimit: { remaining: 5000, resetAt: new Date(Date.now() + 3600000).toISOString() },
29+
},
30+
},
31+
})
3032
);
3133

3234
await page.addInitScript(() => {

e2e/smoke.spec.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,6 @@ async function setupAuth(page: Page) {
1717
},
1818
})
1919
);
20-
await page.route("https://api.github.com/search/issues*", (route) =>
21-
route.fulfill({
22-
status: 200,
23-
json: { total_count: 0, incomplete_results: false, items: [] },
24-
})
25-
);
2620
await page.route(
2721
"https://api.github.com/repos/*/actions/runs*",
2822
(route) =>
@@ -35,7 +29,15 @@ async function setupAuth(page: Page) {
3529
route.fulfill({ status: 200, json: [] })
3630
);
3731
await page.route("https://api.github.com/graphql", (route) =>
38-
route.fulfill({ status: 200, json: { data: {} } })
32+
route.fulfill({
33+
status: 200,
34+
json: {
35+
data: {
36+
search: { issueCount: 0, pageInfo: { hasNextPage: false, endCursor: null }, nodes: [] },
37+
rateLimit: { remaining: 5000, resetAt: new Date(Date.now() + 3600000).toISOString() },
38+
},
39+
},
40+
})
3941
);
4042

4143
// Seed localStorage with auth token and config before the page loads
@@ -104,10 +106,15 @@ test("OAuth callback flow completes and redirects", async ({ page }) => {
104106
);
105107
});
106108
// Also intercept downstream dashboard API calls
107-
await page.route("https://api.github.com/search/issues*", (route) =>
109+
await page.route("https://api.github.com/graphql", (route) =>
108110
route.fulfill({
109111
status: 200,
110-
json: { total_count: 0, incomplete_results: false, items: [] },
112+
json: {
113+
data: {
114+
search: { issueCount: 0, pageInfo: { hasNextPage: false, endCursor: null }, nodes: [] },
115+
rateLimit: { remaining: 5000, resetAt: new Date(Date.now() + 3600000).toISOString() },
116+
},
117+
},
111118
})
112119
);
113120
await page.route("https://api.github.com/notifications*", (route) =>

src/app/components/dashboard/DashboardPage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { clearAuth, user, onAuthCleared, DASHBOARD_STORAGE_KEY } from "../../sto
1414

1515
// ── Shared dashboard store (module-level to survive navigation) ─────────────
1616

17-
const CACHE_VERSION = 1;
17+
const CACHE_VERSION = 2;
1818

1919
interface DashboardStore {
2020
issues: Issue[];

src/app/components/dashboard/PullRequestsTab.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ export default function PullRequestsTab(props: PullRequestsTabProps) {
349349
createdAt={pr.createdAt}
350350
url={pr.htmlUrl}
351351
labels={pr.labels}
352-
commentCount={pr.comments + pr.reviewComments}
352+
commentCount={pr.comments + pr.reviewThreads}
353353
onIgnore={() => handleIgnore(pr)}
354354
density={config.viewDensity}
355355
>

src/app/components/layout/Header.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createSignal, Show } from "solid-js";
22
import { useNavigate } from "@solidjs/router";
33
import { user, clearAuth } from "../../stores/auth";
4-
import { getCoreRateLimit, getSearchRateLimit } from "../../services/github";
4+
import { getCoreRateLimit, getGraphqlRateLimit } from "../../services/github";
55
import { getUnreadCount, markAllAsRead } from "../../lib/errors";
66
import NotificationDrawer from "../shared/NotificationDrawer";
77
import ToastContainer from "../shared/ToastContainer";
@@ -27,7 +27,7 @@ export default function Header() {
2727
const unreadCount = () => getUnreadCount();
2828

2929
const coreRL = () => getCoreRateLimit();
30-
const searchRL = () => getSearchRateLimit();
30+
const graphqlRL = () => getGraphqlRateLimit();
3131

3232
function formatLimit(remaining: number, limit: number, unit: string): string {
3333
const k = limit >= 1000 ? `${limit / 1000}k` : String(limit);
@@ -43,7 +43,7 @@ export default function Header() {
4343

4444
<div class="flex-1" />
4545

46-
<Show when={coreRL() || searchRL()}>
46+
<Show when={coreRL() || graphqlRL()}>
4747
<div class="flex items-center gap-2 shrink-0">
4848
<span class="text-xs font-medium text-gray-400 dark:text-gray-500">Rate Limits</span>
4949
<div class="flex flex-col items-end text-xs tabular-nums leading-tight gap-0.5">
@@ -57,13 +57,13 @@ export default function Header() {
5757
</span>
5858
)}
5959
</Show>
60-
<Show when={searchRL()}>
60+
<Show when={graphqlRL()}>
6161
{(rl) => (
6262
<span
63-
class={rl().remaining < 5 ? "text-amber-600 dark:text-amber-400" : "text-gray-500 dark:text-gray-400"}
64-
title={`Search rate limit resets at ${rl().resetAt.toLocaleTimeString()}`}
63+
class={rl().remaining < 500 ? "text-amber-600 dark:text-amber-400" : "text-gray-500 dark:text-gray-400"}
64+
title={`GraphQL rate limit resets at ${rl().resetAt.toLocaleTimeString()}`}
6565
>
66-
{formatLimit(rl().remaining, 30, "min")}
66+
GraphQL {formatLimit(rl().remaining, 5000, "hr")}
6767
</span>
6868
)}
6969
</Show>

0 commit comments

Comments
 (0)