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: connect contributors page to API #1113

Merged
merged 2 commits into from
Apr 20, 2023
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ const CardHorizontalBarChart = ({ languageList, withDescription }: CardHorizonta
// used this state to calculate thte percentage of each language
const [percentage, setPercentage] = useState<any>(0);

const [descriptText, setDescriptText] = useState(sortedLangArray[0].languageName);
const [descriptText, setDescriptText] = useState(sortedLangArray[0]?.languageName || "javascript");

const handleChangeDescriptText = (descriptText: string) => {
setDescriptText(descriptText);
};

useEffect(() => {
if (sortedLangArray.length === 0) return;

const totalSumOfFirstFivePercentage = sortedLangArray
.slice(0, 4)
.map((lang) => lang.percentageUsed)
Expand Down
5 changes: 3 additions & 2 deletions components/molecules/PullRequestTable/pull-request-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ interface CardTableProps {
repositories?: number[];
limit?: number;
isHoverCard?: boolean;
range?: number;
}

const PullRequestTable = ({ contributor, topic, repositories, limit, isHoverCard }: CardTableProps): JSX.Element => {
const { data, isLoading } = useContributorPullRequests(contributor, topic, repositories, limit);
const PullRequestTable = ({ contributor, topic, repositories, limit, isHoverCard, range }: CardTableProps): JSX.Element => {
const { data, isLoading } = useContributorPullRequests(contributor, topic, repositories, limit, range);

return data.length > 0 ? (
<>
Expand Down
4 changes: 3 additions & 1 deletion components/molecules/RepoRow/repo-row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { getAvatarByUsername } from "lib/utils/github";
import useRepositoryPullRequests from "lib/hooks/api/useRepositoryPullRequests";
import getPullRequestsToDays from "lib/utils/get-prs-to-days";
import getPullRequestsContributors from "lib/utils/get-pr-contributors";
import useStore from "lib/store";

interface RepoProps {
repo: RepositoriesRows;
Expand Down Expand Up @@ -92,7 +93,8 @@ const RepoRow = ({ repo, topic, userPage, selected, handleOnSelectRepo }: RepoPr
const ownerAvatar = getAvatarByUsername(fullName.split("/")[0]);

const { user } = useSupabaseAuth();
const { data: repositoryPullRequests } = useRepositoryPullRequests(repo.full_name, 100);
const range = useStore((state) => state.range);
const { data: repositoryPullRequests } = useRepositoryPullRequests(repo.full_name, 100, range);
const totalPrs = getTotalPrs(openPrsCount, mergedPrsCount, closedPrsCount, draftPrsCount);
const prsMergedPercentage = getPercent(totalPrs, mergedPrsCount || 0);
const spamPrsPercentage = getPrsSpam(totalPrs, spamPrsCount || 0);
Expand Down
45 changes: 34 additions & 11 deletions components/organisms/ContributorCard/contributor-card.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { useState } from "react";

import { useContributorPullRequestsChart } from "lib/hooks/useContributorPullRequestsChart";

import Card from "components/atoms/Card/card";
import Text from "components/atoms/Typography/text";
import CardHorizontalBarChart, {
Expand All @@ -12,46 +10,71 @@ import CardProfile from "components/molecules/CardProfile/card-profile";
import CardRepoList, { RepoList } from "components/molecules/CardRepoList/card-repo-list";
import PullRequestTable from "components/molecules/PullRequestTable/pull-request-table";

import { useContributorPullRequestsChart } from "lib/hooks/useContributorPullRequestsChart";
import color from "lib/utils/color.json";
import { getAvatarByUsername } from "lib/utils/github";
import useRepositories from "lib/hooks/api/useRepositories";

const colorKeys = Object.keys(color);

export interface ContributorObject {
profile: {
githubAvatar: string;
githubName: string;
totalPRs: number;
dateOfFirstPR: string;
};
repoList: RepoList[];
languageList: LanguageObject[];
}

interface ContributorCardProps {
className?: string;
contributor: ContributorObject;
topic: string;
repositories?: number[];
range?: number;
}

const ContributorCard = ({ className, contributor, topic, repositories }: ContributorCardProps) => {
const { profile, repoList, languageList } = contributor;
const ContributorCard = ({ className, contributor, topic, repositories, range }: ContributorCardProps) => {
const { profile } = contributor;

const [showPRs, setShowPRs] = useState(false);
const { chart } = useContributorPullRequestsChart(profile.githubName, topic, repositories);
const { chart, data, meta } = useContributorPullRequestsChart(profile.githubName, topic, repositories, range);
const repoList: RepoList[] = Array.from(new Set(data.map(prData => prData.full_name))).map(repo => {
const [repoOwner, repoName] = repo.split("/");

return {
repoName: repoName,
repoIcon: getAvatarByUsername(repoOwner)
};
});
const repoIds = data.map(pr => pr.repo_id);
const { data: repoData } = useRepositories(repoIds);
const contributorLanguageList = Array.from(new Set(repoData.map(repo => repo.language).filter(language => !!language)));
const languageList: LanguageObject[] = contributorLanguageList
.map((language) => {
const preparedLanguageKey = colorKeys.find((key) => key.toLowerCase() === language.toLowerCase());

return {
languageName: preparedLanguageKey ? preparedLanguageKey.toLowerCase() : language,
percentageUsed: Math.round((1 / contributorLanguageList.length) * 100)
};
});

return (
<Card className={className && className}>
<div className="flex flex-col gap-3">
<div className="flex w-full justify-between items-center gap-2">
<CardProfile {...profile} />
<CardProfile {...profile} totalPRs={meta.itemCount} />
<div>
<CardHorizontalBarChart withDescription={false} languageList={languageList} />
</div>
</div>
<div className="h-[110px] overflow-hidden">
<CardLineChart lineChartOption={chart} />
</div>
<CardRepoList repoList={repoList} />
<CardRepoList repoList={repoList} total={repoList.length} />

{showPRs ? (
<PullRequestTable contributor={profile.githubName} topic={topic} repositories={repositories} />
<PullRequestTable contributor={profile.githubName} topic={topic} repositories={repositories} range={range} />
) : null}

<div className="flex w-full justify-center">
Expand Down
46 changes: 16 additions & 30 deletions components/organisms/Contributors/contributors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,11 @@ import TableHeader from "components/molecules/TableHeader/table-header";
import Select from "components/atoms/Select/custom-select";

import { calcDistanceFromToday } from "lib/utils/date-utils";
import color from "lib/utils/color.json";
import { useTopicContributions } from "lib/hooks/useTopicContributions";

import ContributorCard from "../ContributorCard/contributor-card";
import SkeletonWrapper from "components/atoms/SkeletonLoader/skeleton-wrapper";

const colorKeys = Object.keys(color);
import useContributors from "lib/hooks/api/useContributors";
import { getAvatarByUsername } from "lib/utils/github";

interface ContributorProps {
repositories?: number[];
Expand All @@ -23,41 +21,28 @@ const Contributors = ({ repositories }: ContributorProps): JSX.Element => {
const router = useRouter();
const { filterName } = router.query;
const topic = filterName as string;
const { data, setLimit, meta, setPage, page, isError, isLoading } = useTopicContributions(10, repositories);
const range = useStore((state) => state.range);
const store = useStore();
const range = useStore((state) => state.range);
const { data, meta, setPage, setLimit, isError, isLoading } = useContributors(10, repositories, range);

const contributors = data.map(pr => {
return {
host_login: pr.author_login,
first_commit_time: pr.created_at
};
});

const contributorArray = isError
? []
: data?.map((contributor) => {
const timeSinceFirstCommit = calcDistanceFromToday(new Date(parseInt(contributor.first_commit_time)));
const contributorLanguageList = (contributor.langs || "").split(",");
const repoList = (contributor.recent_repo_list || "").split(",").map((repo) => {
const [repoOwner, repoName] = repo.split("/");

return {
repoName,
repoIcon: `https://www.github.com/${repoOwner ?? "github"}.png?size=460`
};
});
const languageList = contributorLanguageList.map((language) => {
const preparedLanguageKey = colorKeys.find((key) => key.toLowerCase() === language.toLowerCase());

return {
languageName: preparedLanguageKey ? preparedLanguageKey : language,
percentageUsed: Math.round((1 / contributorLanguageList.length) * 100)
};
});
: contributors.map((contributor) => {
const timeSinceFirstCommit = calcDistanceFromToday(new Date(contributor.first_commit_time));

return {
profile: {
githubAvatar: `https://www.github.com/${contributor.host_login}.png?size=60`,
githubAvatar: getAvatarByUsername(contributor.host_login),
githubName: contributor.host_login,
totalPRs: contributor.recent_pr_total,
dateOfFirstPR: timeSinceFirstCommit
},
languageList,
repoList
}
};
});

Expand All @@ -83,6 +68,7 @@ const Contributors = ({ repositories }: ContributorProps): JSX.Element => {
contributor={{ ...contributor }}
topic={topic}
repositories={repositories}
range={range}
/>
))}
</div>
Expand Down
4 changes: 2 additions & 2 deletions components/organisms/Dashboard/dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ interface DashboardProps {

const Dashboard = ({ repositories }: DashboardProps): JSX.Element => {
const { data: insightsData, isLoading } = useInsights(repositories);
const { data: prData, isError: prError } = usePullRequests(undefined, repositories);
const { data: prData, meta: prMeta, isError: prError } = usePullRequests(undefined, repositories);
const [showBots, setShowBots] = useState(false);
const isMobile = useMediaQuery("(max-width:720px)");
const [prStateFilter, setPrStateFilter] = useState<PrStatusFilter>("all");
Expand Down Expand Up @@ -99,7 +99,7 @@ const Dashboard = ({ repositories }: DashboardProps): JSX.Element => {
metricIncreases={compare1.allContributors - compare2.allContributors >= 0}
increased={compare1.allContributors - compare2.allContributors >= 0}
numChanged={humanizeNumber(Math.abs(compare1.allContributors - compare2.allContributors), "abbreviation")}
value={humanizeNumber(compare1.allContributors, "comma")}
value={humanizeNumber(prMeta.itemCount, "comma")}
contributors={contributorData}
isLoading={isLoading}
/>
Expand Down
4 changes: 2 additions & 2 deletions components/organisms/Repositories/repositories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import TableHeader from "components/molecules/TableHeader/table-header";

import useRepositories from "lib/hooks/api/useRepositories";
import useSupabaseAuth from "lib/hooks/useSupabaseAuth";
import useStore from "lib/store";

import RepositoriesTable, { classNames, RepositoriesRows } from "../RepositoriesTable/repositories-table";
import RepoNotIndexed from "./repository-not-indexed";
import Checkbox from "components/atoms/Checkbox/checkbox";
import Button from "components/atoms/Button/button";
import useStore from "lib/store";

interface RepositoriesProps {
repositories?: number[];
Expand All @@ -36,7 +36,7 @@ const Repositories = ({ repositories }: RepositoriesProps): JSX.Element => {
isLoading: repoListIsLoading,
setPage,
setLimit
} = useRepositories(repositories);
} = useRepositories(repositories, range);
const filteredRepoNotIndexed = selectedFilter && !repoListIsLoading && !repoListIsError && repoListData.length === 0;
const [selectedRepos, setSelectedRepos] = useState<DbRepo[]>([]);

Expand Down
69 changes: 69 additions & 0 deletions lib/hooks/api/useContributors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { useState } from "react";
import useSWR, { Fetcher } from "swr";
import { useRouter } from "next/router";

import publicApiFetcher from "lib/utils/public-api-fetcher";
import getFilterQuery from "lib/utils/get-filter-query";

interface PaginatedResponse {
readonly data: DbRepoPR[];
readonly meta: Meta;
}

/**
* Fetch contributors based on pull requests.
* Replace with contributors API endpoint when available.
*
* @param intialLimit
* @param repoIds
* @param range
* @returns
*/
const useContributors = (intialLimit = 10, repoIds: number[] = [], range = 30) => {
const router = useRouter();
const [page, setPage] = useState(1);
const [limit, setLimit] = useState(intialLimit);
const { filterName, selectedFilter } = router.query;
const topic = filterName as string;
const filterQuery = getFilterQuery(selectedFilter);
const query = new URLSearchParams(filterQuery);

if (Number.isNaN(Number(topic))) {
query.set("topic", topic);
}

if (page) {
query.set("page", `${page}`);
}

if (limit) {
query.set("limit", `${limit}`);
}

if (repoIds?.length > 0) {
query.set("repoIds", repoIds.join(","));
}

query.set("range", `${range}`);

const baseEndpoint = "prs/search";
const endpointString = `${baseEndpoint}?${query.toString()}`;

const { data, error, mutate } = useSWR<PaginatedResponse, Error>(
endpointString,
publicApiFetcher as Fetcher<PaginatedResponse, Error>
);

return {
data: data?.data ?? [],
meta: data?.meta ?? { itemCount: 0, limit: 0, page: 0, hasNextPage: false, hasPreviousPage: false, pageCount: 0 },
isLoading: !error && !data,
isError: !!error,
mutate,
page,
setPage,
setLimit
};
};

export default useContributors;
8 changes: 6 additions & 2 deletions lib/hooks/api/usePullRequests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ interface PaginatedResponse {
readonly meta: Meta;
}

const usePullRequests = (limit = 1000, repoIds: number[] = []) => {
const usePullRequests = (intialLimit = 1000, repoIds: number[] = [], range = 30) => {
const router = useRouter();
const [page, setPage] = useState(1);
const [limit, setLimit] = useState(intialLimit);
const { filterName, selectedFilter } = router.query;
const topic = filterName as string;
const filterQuery = getFilterQuery(selectedFilter);
Expand All @@ -34,6 +35,8 @@ const usePullRequests = (limit = 1000, repoIds: number[] = []) => {
query.set("repoIds", repoIds.join(","));
}

query.set("range", `${range}`);

const baseEndpoint = "prs/search";
const endpointString = `${baseEndpoint}?${query.toString()}`;

Expand All @@ -49,7 +52,8 @@ const usePullRequests = (limit = 1000, repoIds: number[] = []) => {
isError: !!error,
mutate,
page,
setPage
setPage,
setLimit
};
};

Expand Down
6 changes: 5 additions & 1 deletion lib/hooks/api/useRepositories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface PaginatedResponse {
readonly meta: Meta;
}

const useRepositories = (repoIds: number[] = []) => {
const useRepositories = (repoIds: number[] = [], range = 30) => {
const router = useRouter();
const [page, setPage] = useState(1);
const [limit, setLimit] = useState(10);
Expand All @@ -31,6 +31,10 @@ const useRepositories = (repoIds: number[] = []) => {
query.set("limit", `${limit}`);
}

if (range) {
query.set("range", `${range}`);
}

if (repoIds?.length > 0) {
query.set("repoIds", repoIds.join(","));
}
Expand Down
3 changes: 2 additions & 1 deletion lib/hooks/api/useRepositoryPullRequests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ interface PaginatedResponse {
readonly meta: Meta;
}

const useRepositoryPullRequests = (fullName: string, limit = 10) => {
const useRepositoryPullRequests = (fullName: string, limit = 10, range = 30) => {
const router = useRouter();
const { filterName } = router.query;
const topic = filterName as string;
Expand All @@ -21,6 +21,7 @@ const useRepositoryPullRequests = (fullName: string, limit = 10) => {
query.set("repo", fullName);
query.set("page", "1");
query.set("limit", `${limit}`);
query.set("range", `${range}`);

const baseEndpoint = "prs/search";
const endpointString = `${baseEndpoint}?${query.toString()}`;
Expand Down
Loading