Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/core/src/github/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import {
countRepoPRsTotal,
fetchVouchFile,
postVouchComment,
searchUsers as searchUsersApi,
} from "./rest-queries.js";
import { fetchScorecard } from "./scorecard.js";

Expand Down Expand Up @@ -522,5 +523,9 @@ export const createGitBazClient = (options: GitBazClientOptions = {}): GitBazCli
getVouchStatus,
isCollaborator,
postVouchAction,
searchUsers: async (query: string) => {
const items = await searchUsersApi(octokit, query);
return items.map((i) => ({ login: i.login, avatarUrl: i.avatar_url }));
},
};
};
16 changes: 16 additions & 0 deletions packages/core/src/github/rest-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,19 @@ export const postVouchComment = async (
);
return response.data as { id: number; html_url: string };
};

/** Searches for users by login/username */
export const searchUsers = async (
octokit: Octokit,
query: string,
): Promise<{ login: string; avatar_url: string }[]> => {
try {
const response = await octokit.request("GET /search/users", {
q: query,
per_page: 5,
});
return (response.data as { items: { login: string; avatar_url: string }[] }).items;
} catch {
return [];
}
};
6 changes: 6 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ export interface UserStats {
readonly automationReason?: string;
}

export interface UserSearchResult {
readonly login: string;
readonly avatarUrl: string;
}

export interface ScoreComponent {
readonly name: string;
readonly rawValue: number;
Expand Down Expand Up @@ -356,4 +361,5 @@ export interface GitBazClient {
targetUsername: string,
reason?: string,
): Promise<VouchActionResult>;
searchUsers(query: string): Promise<readonly UserSearchResult[]>;
}
11 changes: 11 additions & 0 deletions packages/extension/entrypoints/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,17 @@ export default defineBackground(() => {
error: (error as Error).message,
});
}
} else if (message.type === "SEARCH_USERS") {
try {
const client = await createClient();
const result = await client.searchUsers(message.query);
sendResponse({ type: "SEARCH_USERS_RESULT", result });
} catch (error) {
sendResponse({
type: "SEARCH_USERS_RESULT",
error: (error as Error).message,
});
}
} else if (message.type === "GET_TOKEN") {
const token = await getStoredToken();
sendResponse({ type: "TOKEN_RESULT", token });
Expand Down
26 changes: 25 additions & 1 deletion packages/extension/entrypoints/popup.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,37 @@
button.primary { background: #2da44e; color: #fff; border-color: #2da44e; }
button.primary:hover { background: #218838; }
#result { margin-top: 12px; white-space: pre-wrap; font-size: 12px; }
.search-results {
border: 1px solid #d0d7de;
border-radius: 6px;
margin-top: -4px;
margin-bottom: 8px;
background: #fff;
display: none;
max-height: 200px;
overflow-y: auto;
}
.search-result-item {
padding: 8px 10px;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
}
.search-result-item:hover { background: #f6f8fa; }
.search-result-item img {
width: 20px;
height: 20px;
border-radius: 50%;
}
</style>
</head>
<body>
<h1>GitBaz</h1>
<div id="auth-status" class="status">Checking...</div>
<div id="lookup">
<input type="text" id="username" placeholder="GitHub username" />
<input type="text" id="username" placeholder="GitHub username" autocomplete="off" />
<div id="search-results" class="search-results"></div>
<button class="primary" id="lookup-btn">Get Contributor Score</button>
</div>
<div id="result"></div>
Expand Down
70 changes: 65 additions & 5 deletions packages/extension/entrypoints/popup/main.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import type { ScoreResponse, TokenResponse } from "../../utils/messaging.js";
import type { ScoreResponse, SearchUsersResponse, TokenResponse } from "../../utils/messaging.js";
import type { UserSearchResult } from "@happyhackingspace/gitbaz";

const authStatus = document.getElementById("auth-status") as HTMLDivElement;
const usernameInput = document.getElementById("username") as HTMLInputElement;
const searchResults = document.getElementById("search-results") as HTMLDivElement;
const lookupBtn = document.getElementById("lookup-btn") as HTMLButtonElement;
const resultDiv = document.getElementById("result") as HTMLDivElement;

let searchTimeout: ReturnType<typeof setTimeout>;

const checkAuth = async (): Promise<void> => {
const response: TokenResponse = await chrome.runtime.sendMessage({ type: "GET_TOKEN" });
if (response.token) {
Expand All @@ -15,12 +19,13 @@ const checkAuth = async (): Promise<void> => {
}
};

const lookupUser = async (): Promise<void> => {
const username = usernameInput.value.trim();
const lookupUser = async (usernameToUse?: string): Promise<void> => {
const username = usernameToUse || usernameInput.value.trim();
if (!username) return;

resultDiv.textContent = "Loading...";
lookupBtn.disabled = true;
searchResults.style.display = "none";

const response: ScoreResponse = await chrome.runtime.sendMessage({
type: "GET_SCORE",
Expand All @@ -37,9 +42,64 @@ const lookupUser = async (): Promise<void> => {
lookupBtn.disabled = false;
};

lookupBtn.addEventListener("click", lookupUser);
const handleSearch = async (): Promise<void> => {
const query = usernameInput.value.trim();
if (query.length < 2) {
searchResults.style.display = "none";
return;
}

const response: SearchUsersResponse = await chrome.runtime.sendMessage({
type: "SEARCH_USERS",
query,
});

if (response.result && response.result.length > 0) {
displayResults(response.result);
} else {
searchResults.style.display = "none";
}
};

const displayResults = (users: readonly UserSearchResult[]): void => {
searchResults.innerHTML = "";
searchResults.style.display = "block";

for (const user of users) {
const item = document.createElement("div");
item.className = "search-result-item";
item.innerHTML = `
<img src="${user.avatarUrl}" alt="${user.login}" />
<span>${user.login}</span>
`;
item.addEventListener("click", () => {
usernameInput.value = user.login;
searchResults.style.display = "none";
lookupUser(user.login);
});
searchResults.appendChild(item);
}
};

lookupBtn.addEventListener("click", () => lookupUser());

usernameInput.addEventListener("input", () => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(handleSearch, 300);
});

usernameInput.addEventListener("keydown", (e) => {
if (e.key === "Enter") lookupUser();
if (e.key === "Enter") {
clearTimeout(searchTimeout);
lookupUser();
}
});

// Hide search results when clicking outside
document.addEventListener("click", (e) => {
if (!usernameInput.contains(e.target as Node) && !searchResults.contains(e.target as Node)) {
searchResults.style.display = "none";
}
});

checkAuth();
18 changes: 16 additions & 2 deletions packages/extension/utils/messaging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
RepoContext,
RepositoryContext,
ScoreResult,
UserSearchResult,
VouchAction,
VouchActionResult,
VouchLookupResult,
Expand Down Expand Up @@ -157,6 +158,17 @@ export interface OpenSettingsRequest {
type: "OPEN_SETTINGS";
}

export interface SearchUsersRequest {
type: "SEARCH_USERS";
query: string;
}

export interface SearchUsersResponse {
type: "SEARCH_USERS_RESULT";
result?: readonly UserSearchResult[];
error?: string;
}

export type ExtensionMessage =
| ScoreRequest
| PullRequestRequest
Expand All @@ -170,7 +182,8 @@ export type ExtensionMessage =
| VouchActionRequest
| BusFactorRequest
| BlameAnalysisRequest
| OpenSettingsRequest;
| OpenSettingsRequest
| SearchUsersRequest;

export type ExtensionResponse =
| ScoreResponse
Expand All @@ -184,4 +197,5 @@ export type ExtensionResponse =
| CollaboratorResponse
| VouchActionResponse
| BusFactorResponse
| BlameAnalysisResponse;
| BlameAnalysisResponse
| SearchUsersResponse;