Skip to content
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

feat: add PR velocity to profile page #111

Merged
merged 10 commits into from
May 25, 2023
20 changes: 17 additions & 3 deletions npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"dependencies": {
"@types/chrome": "^0.0.231",
"@types/node-emoji": "^1.8.2",
"date-fns": "^2.30.0",
"gpt-tokenizer": "^1.0.5",
"node-emoji": "^1.11.0",
"react": "^18.0.0",
Expand Down
13 changes: 9 additions & 4 deletions src/pages/profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import { AiOutlineReload } from "react-icons/ai";
import { SiC, SiCplusplus, SiCsharp, SiGoland, SiJavascript, SiPhp, SiPython, SiReact, SiRuby, SiRust, SiTypescript } from "react-icons/si";
import { DiJava } from "react-icons/di";
import OpenSaucedLogo from "../assets/opensauced-logo.svg";
import { getUserData, getUserPRData, getUserHighlightsData } from "../utils/fetchOpenSaucedApiData";
import { getUserData, getUserPRData, getUserHighlightsData, getContributorPullRequestVelocity } from "../utils/fetchOpenSaucedApiData";
import { emojify } from "node-emoji";
import { goBack } from "react-chrome-extension-router";
import { getRelativeDays } from "../utils/dateUtils";

const interestIcon = {
python: <SiPython />,
Expand All @@ -28,18 +29,20 @@ const interestIcon = {

type InterestIconKeys = keyof typeof interestIcon;

export const Profile = ({ username }: {username: string}) => {
export const Profile = ({ username }: { username: string }) => {
const [user, setUser] = useState<null | { id: string, user_name: string, bio: string, created_at: string, linkedin_url: string, twitter_username: string, blog: string, interests: string, open_issues: number }>(null);
const [userPR, setUserPR] = useState<null | { meta: { itemCount: number } }>(null);
const [userHighlights, setUserHighlights] = useState<null | { meta: { itemCount: number } }>(null);
const [userPRVelocity, setUserPRVelocity] = useState<number>(0);

useEffect(() => {
const fetchUserData = async () => {
const [userData, userPRData, userHighlightsData] = await Promise.all([getUserData(username), getUserPRData(username), getUserHighlightsData(username)]);
const [userData, userPRData, userHighlightsData, userPRVelocity] = await Promise.all([getUserData(username), getUserPRData(username), getUserHighlightsData(username), getContributorPullRequestVelocity(username)]);

setUser(userData);
setUserPR(userPRData);
setUserHighlights(userHighlightsData);
setUserPRVelocity(userPRVelocity);
};

void fetchUserData();
Expand Down Expand Up @@ -163,7 +166,9 @@ export const Profile = ({ username }: {username: string}) => {
<div className="flex flex-col items-center justify-center p-2 text-xs">
<p>Avg PRs Velocity</p>

<p className="font-medium text-5xl">-</p>
<p className="font-medium text-5xl">
{getRelativeDays(userPRVelocity)}
</p>
</div>

<div className="flex flex-col items-center justify-center p-2 text-xs">
Expand Down
15 changes: 15 additions & 0 deletions src/utils/dateUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const getRelativeDays = (days: number) => {
if (days === 0) {
return "-";
}

if (days >= 365) {
return `${Math.floor(days / 365)}y`;
}

if (days > 30 && days < 365) {
return `${Math.floor(days / 30)}mo`;
}

return `${days}d`;
};
bdougie marked this conversation as resolved.
Show resolved Hide resolved
37 changes: 36 additions & 1 deletion src/utils/fetchOpenSaucedApiData.ts
bdougie marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import differenceInDays from "date-fns/differenceInDays";
import { cachedFetch } from "./cache";
import { OPEN_SAUCED_USERS_ENDPOINT, OPEN_SAUCED_SESSION_ENDPOINT, OPEN_SAUCED_REPOS_ENDPOINT, OPEN_SAUCED_USER_INSIGHTS_ENDPOINT } from "../constants";
import {
OPEN_SAUCED_USERS_ENDPOINT,
OPEN_SAUCED_SESSION_ENDPOINT,
OPEN_SAUCED_REPOS_ENDPOINT,
OPEN_SAUCED_USER_INSIGHTS_ENDPOINT,
} from "../constants";
import { IInsight } from "../ts/InsightDto";

export const isOpenSaucedUser = async (username: string) => {
Expand Down Expand Up @@ -171,3 +177,32 @@ export const updateInsight = async (userToken: string, repoId: string, checked:

return response.status === 200;
};

export const getContributorPullRequestVelocity = async (
userName: string,
forceRefresh: boolean = false,
) =>
cachedFetch(`${OPEN_SAUCED_USERS_ENDPOINT}/${userName}/prs`, {
expireInSeconds: 2 * 60 * 60,
forceRefresh,
headers: { Accept: "application/json" },
})
.then(async resp => {
if (!resp?.ok) {
console.log("error getting user info");
}
return resp?.json();
})
.then(({ data }) => {
const mergedPRs = data.filter(prState => prState.state.toLowerCase() === "merged");

const totalDays = mergedPRs.reduce((total, pr) => {
const daysBetween = differenceInDays(new Date(pr.closed_at), new Date(pr.created_at));

return (total += daysBetween);
}, 0);

const averageVelocity: number = mergedPRs.length > 0 ? Math.round(totalDays / mergedPRs.length) : 0;

return averageVelocity;
});