Skip to content

Commit 721cd96

Browse files
Octokit bugs (#10)
* fix cache busting and allow short hashing * fix bypassCache for both API calls * add Combobox for commit drop down selection * cleanup
1 parent 06ace52 commit 721cd96

File tree

2 files changed

+166
-77
lines changed

2 files changed

+166
-77
lines changed

server/app/query/create/page.tsx

Lines changed: 130 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
"use client";
2-
import { useState, FormEvent, useEffect, Fragment } from "react";
2+
import { useState, useRef, FormEvent, useEffect, Fragment } from "react";
33
import clsx from "clsx";
4-
import { Listbox, Transition } from "@headlessui/react";
5-
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/react/20/solid";
4+
import { Listbox, Transition, Combobox } from "@headlessui/react";
5+
import {
6+
CheckIcon,
7+
ChevronUpDownIcon,
8+
ArrowPathIcon,
9+
} from "@heroicons/react/20/solid";
610
import { useRouter } from "next/navigation";
711
import { ExclamationCircleIcon } from "@heroicons/react/20/solid";
812
import QueryStartedAlert from "@/app/alert";
@@ -12,7 +16,7 @@ import {
1216
RemoteServersType,
1317
} from "@/app/query/servers";
1418
import NewQueryId from "@/app/query/haikunator";
15-
import { Branch, Branches, isValidCommitHash } from "@/app/query/github";
19+
import { Branch, Branches, Commits } from "@/app/query/github";
1620

1721
export default function Page() {
1822
const [queryId, setQueryId] = useState<string | null>(null);
@@ -110,24 +114,32 @@ function IPAForm({
110114
}: {
111115
handleIPAFormSubmit: (event: FormEvent<HTMLFormElement>) => void;
112116
}) {
117+
enum CommitSpecifier {
118+
COMMIT_HASH,
119+
BRANCH,
120+
}
113121
const owner = "private-attribution";
114122
const repo = "ipa";
115123
const [branches, setBranches] = useState<Branch[]>([]);
124+
const [commitHashes, setCommitHashes] = useState<string[]>([]);
116125
const branchNames = branches.map((branch) => branch.name);
117126
const [selectedBranchName, setSelectedBranchName] = useState<string>("main");
118127
const [selectedCommitHash, setSelectedCommitHash] = useState<string>("");
119128
const [validCommitHash, setValidCommitHash] = useState<boolean>(true);
120-
121-
enum CommitSpecifier {
122-
COMMIT_HASH,
123-
BRANCH,
124-
}
129+
const commitHashInputRef = useRef<HTMLInputElement>(null);
125130
const [commitSpecifier, setCommitSpecifier] = useState<CommitSpecifier>(
126131
CommitSpecifier.BRANCH,
127132
);
128-
129133
const disableBranch = commitSpecifier != CommitSpecifier.BRANCH;
130134
const disableCommitHash = commitSpecifier != CommitSpecifier.COMMIT_HASH;
135+
const filteredCommitHashes =
136+
selectedCommitHash === ""
137+
? []
138+
: commitHashes.filter((commit) => {
139+
return commit
140+
.toLowerCase()
141+
.startsWith(selectedCommitHash.toLowerCase());
142+
});
131143

132144
useEffect(() => {
133145
const branch = branches.find(
@@ -146,29 +158,48 @@ function IPAForm({
146158

147159
useEffect(() => {
148160
const branch = branches.find(
149-
(branch) => branch.name === selectedCommitHash,
161+
(branch) => branch.commitHash === selectedCommitHash,
150162
);
163+
151164
const fetchCommitIsValid = async () => {
152-
const _valid = await isValidCommitHash(owner, repo, selectedCommitHash);
165+
const _valid = filteredCommitHashes.length > 0;
153166
setValidCommitHash(_valid);
154167
};
155168
if (branch) {
156169
setSelectedBranchName(branch.name);
157170
setValidCommitHash(true);
158171
} else if (commitSpecifier != CommitSpecifier.BRANCH) {
159-
setSelectedBranchName("N/A");
172+
setSelectedBranchName("[Specific commit]");
160173
fetchCommitIsValid().catch(console.error);
161174
}
162-
}, [selectedCommitHash, commitSpecifier, CommitSpecifier.COMMIT_HASH]);
175+
}, [
176+
selectedCommitHash,
177+
commitSpecifier,
178+
CommitSpecifier.BRANCH,
179+
branches,
180+
commitHashes,
181+
]);
163182

164183
useEffect(() => {
165184
const fetchBranches = async () => {
166-
const _branches = await Branches(owner, repo);
185+
const _branches = await Branches(owner, repo, false);
167186
setBranches(_branches);
168187
};
188+
const fetchCommitHashes = async () => {
189+
const _commitHashes = await Commits(owner, repo, false);
190+
setCommitHashes(_commitHashes);
191+
};
169192
fetchBranches().catch(console.error);
193+
fetchCommitHashes().catch(console.error);
170194
}, []);
171195

196+
const refreshBranches = async (selectedCommitHash: string) => {
197+
const _branches = await Branches(owner, repo, true);
198+
setBranches(_branches);
199+
const _commitHashes = await Commits(owner, repo, true);
200+
setCommitHashes(_commitHashes);
201+
};
202+
172203
return (
173204
<form
174205
onSubmit={handleIPAFormSubmit}
@@ -177,19 +208,40 @@ function IPAForm({
177208
<h2 className="text-2xl mb-2 font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl sm:tracking-tight">
178209
IPA Query
179210
</h2>
180-
<div onClick={() => setCommitSpecifier(CommitSpecifier.BRANCH)}>
181-
<PassedStateSelectMenu
182-
id="branch"
183-
label="Branch"
184-
options={branchNames}
185-
selected={selectedBranchName}
186-
setSelected={setSelectedBranchName}
187-
disabled={disableBranch}
188-
/>
211+
<div className="flex items-end">
212+
<div
213+
className="flex-grow"
214+
onClick={() => {
215+
setCommitSpecifier(CommitSpecifier.BRANCH);
216+
}}
217+
>
218+
<PassedStateSelectMenu
219+
id="branch"
220+
label="Branch"
221+
options={branchNames}
222+
selected={selectedBranchName}
223+
setSelected={setSelectedBranchName}
224+
disabled={disableBranch}
225+
/>
226+
</div>
227+
<button
228+
className="relative cursor-default rounded-md bg-white py-2.5 px-3 ml-1 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-600 sm:text-sm sm:leading-6"
229+
onClick={(e) => {
230+
e.preventDefault();
231+
refreshBranches(selectedCommitHash);
232+
}}
233+
>
234+
<ArrowPathIcon className="h-4 w-4" />
235+
</button>
189236
</div>
190237
<div
191238
className="relative mt-2 rounded-md shadow-sm"
192-
onClick={() => setCommitSpecifier(CommitSpecifier.COMMIT_HASH)}
239+
onClick={() => {
240+
setCommitSpecifier(CommitSpecifier.COMMIT_HASH);
241+
if (commitHashInputRef.current) {
242+
commitHashInputRef.current.select();
243+
}
244+
}}
193245
>
194246
<label
195247
htmlFor="commit_hash"
@@ -200,21 +252,37 @@ function IPAForm({
200252
>
201253
Commit Hash
202254
</label>
203-
<input
204-
type="string"
205-
name="commit_hash"
206-
id="commit_hash"
207-
className={clsx(
208-
"block w-full rounded-md border-0 py-1.5 pl-3 text-gray-900 ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-sm sm:leading-6",
209-
!validCommitHash &&
210-
"text-red-900 ring-red-300 placeholder:text-red-300 focus:ring-red-500",
211-
disableCommitHash && "opacity-25",
212-
)}
213-
value={selectedCommitHash}
214-
onChange={(e) => setSelectedCommitHash(e.target.value)}
215-
aria-invalid="true"
216-
aria-describedby="email-error"
217-
/>
255+
<Combobox value={selectedCommitHash} onChange={setSelectedCommitHash}>
256+
<Combobox.Input
257+
onChange={(event) => setSelectedCommitHash(event.target.value)}
258+
type="string"
259+
name="commit_hash"
260+
id="commit_hash"
261+
ref={commitHashInputRef}
262+
className={clsx(
263+
"block w-full rounded-md border-0 py-1.5 pl-3 text-gray-900 ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-sm sm:leading-6",
264+
!validCommitHash &&
265+
"text-red-900 ring-red-300 placeholder:text-red-300 focus:ring-red-500",
266+
disableCommitHash && "opacity-25",
267+
)}
268+
/>
269+
<Combobox.Options className="absolute z-10 w-full mt-1 overflow-auto max-h-60 text-gray-900 bg-white shadow-lg rounded-md border border-gray-300 divide-y divide-gray-200 ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
270+
{filteredCommitHashes.map((commit) => (
271+
<Combobox.Option key={commit} value={commit} as={Fragment}>
272+
{({ active, selected }) => (
273+
<li
274+
className={clsx(
275+
"py-2 px-2.5",
276+
active ? "bg-blue-500 text-white" : "text-black",
277+
)}
278+
>
279+
{commit}
280+
</li>
281+
)}
282+
</Combobox.Option>
283+
))}
284+
</Combobox.Options>
285+
</Combobox>
218286

219287
{!validCommitHash && (
220288
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3 pt-10">
@@ -343,15 +411,28 @@ function PassedStateSelectMenu({
343411
disabled && "opacity-25",
344412
)}
345413
>
346-
<Listbox.Button className="relative w-full cursor-default rounded-md bg-white py-1.5 pl-3 pr-10 text-left text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-600 sm:text-sm sm:leading-6">
347-
<span className="block truncate">{selected}</span>
348-
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
349-
<ChevronUpDownIcon
350-
className="h-5 w-5 text-gray-400"
351-
aria-hidden="true"
352-
/>
353-
</span>
354-
</Listbox.Button>
414+
{disabled ? (
415+
// Listbox.Button overrides the onClick, but we only need that to reactivate.
416+
<div className="relative w-full cursor-default rounded-md bg-white py-1.5 pl-3 pr-10 text-left text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-600 sm:text-sm sm:leading-6">
417+
<span className="block truncate">{selected}</span>
418+
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
419+
<ChevronUpDownIcon
420+
className="h-5 w-5 text-gray-400"
421+
aria-hidden="true"
422+
/>
423+
</span>
424+
</div>
425+
) : (
426+
<Listbox.Button className="relative w-full cursor-default rounded-md bg-white py-1.5 pl-3 pr-10 text-left text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:outline-none focus:ring-2 focus:ring-indigo-600 sm:text-sm sm:leading-6">
427+
<span className="block truncate">{selected}</span>
428+
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
429+
<ChevronUpDownIcon
430+
className="h-5 w-5 text-gray-400"
431+
aria-hidden="true"
432+
/>
433+
</span>
434+
</Listbox.Button>
435+
)}
355436

356437
<Transition
357438
show={open}

server/app/query/github.tsx

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
import { Octokit } from "octokit";
44

5-
const octokit = new Octokit({ userAgent: "draft/v0.0.1" });
6-
75
export interface Branch {
86
name: string;
97
commitHash: string;
@@ -16,46 +14,65 @@ if (process.env.OCTOKIT_GITHUB_API_KEY === undefined) {
1614
);
1715
}
1816

19-
export async function Branches(owner: string, repo: string): Promise<Branch[]> {
17+
const octokit = new Octokit({
18+
userAgent: "draft/v0.0.1",
19+
auth: process.env.OCTOKIT_GITHUB_API_KEY,
20+
});
21+
22+
export async function Branches(
23+
owner: string,
24+
repo: string,
25+
bypassCache: boolean,
26+
): Promise<Branch[]> {
27+
const requestParams: any = {
28+
owner: owner,
29+
repo: repo,
30+
per_page: 100,
31+
request: {
32+
cache: bypassCache ? "reload" : "default",
33+
},
34+
timestamp: new Date().getTime(),
35+
};
2036
const branchesIter = octokit.paginate.iterator(
2137
octokit.rest.repos.listBranches,
22-
{
23-
owner: owner,
24-
repo: repo,
25-
per_page: 100,
26-
auth: process.env.OCTOKIT_GITHUB_API_KEY,
27-
},
38+
requestParams,
2839
);
2940

3041
let branchesArray: Branch[] = [];
3142
for await (const { data: branches } of branchesIter) {
3243
for (const branch of branches) {
3344
branchesArray.push({
3445
name: branch.name,
35-
commitHash: branch.commit.sha,
46+
commitHash: branch.commit.sha.substring(0, 7),
3647
});
3748
}
3849
}
39-
4050
const mainBranchIndex = branchesArray.findIndex(
4151
(branch) => branch.name === "main",
4252
);
4353
if (mainBranchIndex != -1) {
4454
branchesArray.unshift(branchesArray.splice(mainBranchIndex, 1)[0]);
4555
}
46-
branchesArray.unshift({ name: "N/A", commitHash: "" });
4756
return branchesArray;
4857
}
4958

50-
export async function Commits(owner: string, repo: string): Promise<string[]> {
59+
export async function Commits(
60+
owner: string,
61+
repo: string,
62+
bypassCache: boolean,
63+
): Promise<string[]> {
64+
const requestParams: any = {
65+
owner: owner,
66+
repo: repo,
67+
per_page: 100,
68+
request: {
69+
cache: bypassCache ? "reload" : "default",
70+
},
71+
timestamp: new Date().getTime(),
72+
};
5173
const commitsIter = octokit.paginate.iterator(
5274
octokit.rest.repos.listCommits,
53-
{
54-
owner: owner,
55-
repo: repo,
56-
per_page: 100,
57-
auth: process.env.OCTOKIT_GITHUB_API_KEY,
58-
},
75+
requestParams,
5976
);
6077

6178
let commitsArray: string[] = [];
@@ -66,12 +83,3 @@ export async function Commits(owner: string, repo: string): Promise<string[]> {
6683
}
6784
return commitsArray;
6885
}
69-
70-
export async function isValidCommitHash(
71-
owner: string,
72-
repo: string,
73-
commitHash: string,
74-
): Promise<boolean> {
75-
const commits = await Commits(owner, repo);
76-
return commits.includes(commitHash);
77-
}

0 commit comments

Comments
 (0)