-
Notifications
You must be signed in to change notification settings - Fork 136
feat: add GitHub username autocomplete suggestions #331
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -78,6 +78,7 @@ const Home: React.FC = () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [selectedRepo, setSelectedRepo] = useState(""); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [startDate, setStartDate] = useState(""); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [endDate, setEndDate] = useState(""); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const [suggestions, setSuggestions] = useState<{ id: number; login: string }[]>([]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Fetch data when username, tab, or page changes | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -157,6 +158,28 @@ const Home: React.FC = () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return <IssueOpenedIcon size={16} className="icon-issue-open" />; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!username) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setSuggestions([]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const fetchUsers = async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const res = await fetch( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `https://api.github.com/search/users?q=${username}` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const data = await res.json(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setSuggestions(data.items?.slice(0, 5) || []); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log(err); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fetchUsers(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [username]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Current data and filtered data according to tab and filters | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const currentRawData = tab === 0 ? issues : prs; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -168,14 +191,61 @@ const Home: React.FC = () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {/* Auth Form */} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Paper elevation={1} sx={{ p: 2, mb: 4, backgroundColor: theme.palette.background.paper }}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <form onSubmit={handleSubmit}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Box sx={{ display: "flex", gap: 2, flexWrap: "wrap" }}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <TextField | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| label="GitHub Username" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value={username} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onChange={(e) => setUsername(e.target.value)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| required | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sx={{ flex: 1, minWidth: 150 }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Box sx={{ display: "flex", flexWrap: "wrap", gap: 2, alignItems: "flex-start" }}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Box | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sx={{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| position: "relative", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| flex: 1, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| minWidth: 250, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <TextField | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fullWidth | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| label="GitHub Username" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value={username} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onChange={(e) => setUsername(e.target.value)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| required | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {suggestions.length > 0 && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Paper | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| elevation={3} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sx={{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| position: "absolute", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| top: "60px", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| left: 0, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| right: 0, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| backgroundColor: "#fff", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| zIndex: 9999, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| borderRadius: 1, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| maxHeight: 220, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| overflowY: "auto", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {suggestions.map((user) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Box | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key={user.id} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setUsername(user.login); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setSuggestions([]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sx={{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| px: 2, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| py: 1.5, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cursor: "pointer", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| borderBottom: "1px solid #eee", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "&:hover": { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| backgroundColor: "#f5f5f5", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+214
to
+239
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, check if the file exists and examine lines 214-239
head -n 250 src/pages/Tracker/Tracker.tsx | tail -n +200Repository: GitMetricsLab/github_tracker Length of output: 1652 🏁 Script executed: # Check the imports and beginning of the file
head -n 30 src/pages/Tracker/Tracker.tsxRepository: GitMetricsLab/github_tracker Length of output: 522 🏁 Script executed: # Check for useTheme import and usage in the file
grep -n "useTheme\|theme\|Theme" src/pages/Tracker/Tracker.tsx | head -20Repository: GitMetricsLab/github_tracker Length of output: 675 🌐 Web query:
💡 Result: You can theme MUI’s palette.action.hover (and related action.* tokens) via theme.palette.action, but it won’t automatically affect Divider’s “hover” behavior because Divider is not a “hover state” component; its appearance is controlled by divider-related palette tokens (theme.palette.divider) and/or component styling. 1) Overriding theme.palette.action.hover - MUI exposes palette.action.* tokens (including action.hover) that you can override in your theme palette. The Dark mode docs show the default value pattern for palette.action.hover as RGBA values (different for light/dark). [1] - You override it the same way as other palette entries, by providing an action object under palette.action in createTheme / theme definition. A working example is to set palette.action.hover in a light color scheme. [2] Example (MUI v5/v6 style): - createTheme({ colorSchemes: { light: { palette: { action: { hover: ' Citations:
Use theme palette for dropdown colors instead of hardcoded light values. The hardcoded colors Proposed fix sx={{
position: "absolute",
top: "60px",
left: 0,
right: 0,
- backgroundColor: "`#fff`",
+ backgroundColor: theme.palette.background.paper,
zIndex: 9999,
borderRadius: 1,
maxHeight: 220,
overflowY: "auto",
}}
...
sx={{
px: 2,
py: 1.5,
cursor: "pointer",
- borderBottom: "1px solid `#eee`",
+ borderBottom: `1px solid ${theme.palette.divider}`,
"&:hover": {
- backgroundColor: "`#f5f5f5`",
+ backgroundColor: theme.palette.action.hover,
},
}}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {user.login} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Box> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+225
to
+244
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❓ Verification inconclusiveScript executed: # First, let's verify the file exists and check the relevant lines
if [ -f "src/pages/Tracker/Tracker.tsx" ]; then
echo "File exists. Checking line count and content around lines 225-244:"
wc -l src/pages/Tracker/Tracker.tsx
echo "---"
sed -n '220,250p' src/pages/Tracker/Tracker.tsx
else
echo "File does not exist"
find . -name "Tracker.tsx" -type f
fiRepository: GitMetricsLab/github_tracker Repository: GitMetricsLab/github_tracker stdout: Script executed: # Get more context around the suggestions implementation
sed -n '190,250p' src/pages/Tracker/Tracker.tsx | cat -nRepository: GitMetricsLab/github_tracker Repository: GitMetricsLab/github_tracker stdout: Script executed: # Check if MUI Autocomplete is being used elsewhere in the codebase
rg "Autocomplete" src/ --type tsx --type tsRepository: GitMetricsLab/github_tracker Repository: GitMetricsLab/github_tracker stderr: Script executed: # Check if MUI Autocomplete is being used elsewhere in the codebase
rg "Autocomplete" src/ -A 2Repository: GitMetricsLab/github_tracker Repository: GitMetricsLab/github_tracker Script executed: # Check the imports and structure of the Tracker component
head -50 src/pages/Tracker/Tracker.tsxRepository: GitMetricsLab/github_tracker Repository: GitMetricsLab/github_tracker stdout: Script executed: # Check if there's any keyboard event handling in the Tracker component
rg "onKeyDown|onKeyUp|onKeyPress" src/pages/Tracker/Tracker.tsx -B 2 -A 2Repository: GitMetricsLab/github_tracker Repository: GitMetricsLab/github_tracker Script executed: # Get the state management around suggestions to understand the full flow
sed -n '40,100p' src/pages/Tracker/Tracker.tsx | cat -nRepository: GitMetricsLab/github_tracker Repository: GitMetricsLab/github_tracker stdout: Script executed: # Search for where suggestions are being set (API calls or logic)
rg "setSuggestions" src/pages/Tracker/Tracker.tsx -B 5 -A 5Repository: GitMetricsLab/github_tracker Repository: GitMetricsLab/github_tracker stdout: Make suggestion rows keyboard-accessible. The suggestion rows currently use a The proposed fix converting to a button element with Enter and Space key support addresses the core issue. Consider also implementing arrow key navigation between suggestions, Escape key to close the dropdown, and appropriate ARIA attributes for a complete solution. Proposed fix- {suggestions.map((user) => (
+ {suggestions.map((user) => (
<Box
+ component="button"
+ type="button"
key={user.id}
onClick={() => {
setUsername(user.login);
setSuggestions([]);
}}
+ onKeyDown={(e) => {
+ if (e.key === "Enter" || e.key === " ") {
+ e.preventDefault();
+ setUsername(user.login);
+ setSuggestions([]);
+ }
+ }}
sx={{
+ width: "100%",
+ textAlign: "left",
px: 2,
py: 1.5,
cursor: "pointer",
+ border: "none",
+ background: "transparent",
borderBottom: "1px solid `#eee`",
"&:hover": {
backgroundColor: "`#f5f5f5`",
},
}}
>
{user.login}
</Box>
))}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Paper> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Box> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <TextField | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| label="Personal Access Token" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value={token} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -202,7 +272,8 @@ const Home: React.FC = () => { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Button type="submit" variant="contained" sx={{ minWidth: "120px" }}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Button type="submit" variant="contained" sx={{ minWidth: "120px",height: "56px",whiteSpace: "nowrap", }}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Fetch Data | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Box> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
What are the current GitHub Search Users API rate limits (authenticated vs unauthenticated), and what client-side patterns are recommended for autocomplete requests?💡 Result:
The GitHub Search API enforces custom rate limits that are separate from the primary REST API rate limits [1][2]. These limits are designed to prevent abuse and ensure service availability [3]. GitHub Search API Rate Limits: - Authenticated requests: 30 requests per minute for most search endpoints [1][4]. The Search code endpoint is more restrictive, limited to 9 requests per minute [1][4]. - Unauthenticated requests: 10 requests per minute [1][4]. Note that these are distinct from the primary REST API rate limits (e.g., 5,000 requests per hour for authenticated users) [5][6]. You can monitor your remaining search quota by checking the rate limit response headers or by querying the GET /rate_limit endpoint [3][2]. Recommended Client-Side Patterns for Autocomplete: Because the Search API has strict per-minute limits, it is not suitable for raw, keystroke-level autocomplete without significant client-side optimization [7]. To implement autocomplete effectively while respecting these limits, the following patterns are recommended: 1. Debouncing: Implement a debounce function (typically 300ms) to delay API requests until the user has stopped typing for a specified duration [8][9]. This prevents flooding the API with requests for every single keystroke [10]. 2. Minimum Input Length: Require a minimum number of characters (e.g., 2 or 3) before triggering a search request to avoid overly broad or irrelevant queries [8]. 3. Caching: Store the results of recent queries in a local cache (e.g., a Map or object) to avoid re-fetching data for the same input [8]. 4. Request Cancellation: Ensure that if a new request is initiated, any pending or in-flight requests are cancelled to avoid race conditions and unnecessary consumption of your rate limit [11]. 5. Official Component Usage: For web implementations, consider using the official
@github/auto-complete-element, which includes built-in handling for debouncing, accessibility (ARIA), and request management [12][9]. If your application requires high-frequency autocomplete, consider building a local index or a dedicated, lightweight search service that periodically syncs with GitHub data, rather than querying the GitHub Search API directly for every user interaction [7].Citations:
🏁 Script executed:
Repository: GitMetricsLab/github_tracker
Length of output: 88
🏁 Script executed:
sed -n '161,183p' src/pages/Tracker/Tracker.tsxRepository: GitMetricsLab/github_tracker
Length of output: 477
Debounce and cancel username lookups to prevent API throttling and stale suggestions.
This effect fires on every keystroke and does not cancel in-flight requests. With the GitHub Search API rate-limited to 10 requests/minute for unauthenticated requests (30 for authenticated), rapid keystroke input will quickly exhaust the quota. Additionally, without request cancellation, older responses can overwrite newer input, causing race conditions.
Proposed fix
useEffect(() => { if (!username) { setSuggestions([]); return; } - const fetchUsers = async () => { + const controller = new AbortController(); + const timeoutId = window.setTimeout(async () => { try { const res = await fetch( - `https://api.github.com/search/users?q=${username}` + `https://api.github.com/search/users?q=${encodeURIComponent(username)}`, + { signal: controller.signal } ); + if (!res.ok) { + setSuggestions([]); + return; + } const data = await res.json(); - setSuggestions(data.items?.slice(0, 5) || []); + setSuggestions((data.items ?? []).slice(0, 5)); } catch (err) { - console.log(err); + if ((err as Error).name !== "AbortError") { + console.error(err); + } } - }; - - fetchUsers(); + }, 300); + + return () => { + controller.abort(); + window.clearTimeout(timeoutId); + }; }, [username]);🤖 Prompt for AI Agents