diff --git a/src/app/components/cards/OrganisationCard.tsx b/src/app/components/cards/OrganisationCard.tsx index b727eac..cc2b53a 100644 --- a/src/app/components/cards/OrganisationCard.tsx +++ b/src/app/components/cards/OrganisationCard.tsx @@ -6,7 +6,6 @@ type OrganisationCardProps = { login: string avatarUrl: string handleClick: (type: string) => void - more: boolean toggleFilter: string | null } @@ -14,20 +13,13 @@ export const OrganisationCard = ({ login, avatarUrl, handleClick, - more, toggleFilter }: OrganisationCardProps) => { return (
handleClick(login)} @@ -52,61 +44,22 @@ export const ProjectsBlock = ({ toggleFilter, handleFilterToggle }: { - projects: Array + projects: Array toggleFilter: string | null handleFilterToggle: (key: string) => void }) => { - const [showMore, setShowMore] = React.useState(false) - return (
{projects.length > 0 && - projects - .slice(0, 3) - .map((x, idx) => ( - - ))} - {projects.length > 3 ? ( -
- - {showMore ? ( -
setShowMore(!showMore)} - > -
-

- projects -

-
- {projects.slice(3).map((x, idx) => ( - - ))} -
-
-
- ) : null} -
- ) : null} + projects.map((x, idx) => ( + + ))}
) } diff --git a/src/app/components/contribution-graph/index.tsx b/src/app/components/contribution-graph/index.tsx new file mode 100644 index 0000000..2fd8db0 --- /dev/null +++ b/src/app/components/contribution-graph/index.tsx @@ -0,0 +1,65 @@ +import { days, months } from "@/helpers/utils" +import { Contribution } from "@/types" +import React from "react" +import ToolTip from "../tool-tip" + +const ContributionGraph = ({ + memoizedGraphValues, + loading, + onClickToolTip +}: { + memoizedGraphValues: Contribution[] + onClickToolTip: (content: Contribution) => void + loading: boolean +}) => { + return ( +
+
+
+ {days.map((d) => ( +

+ {d} +

+ ))} +
+
+
+ {months.map((m) => ( +

+ {m} +

+ ))} +
+
+
+ {memoizedGraphValues.map((day, idx) => ( + + ))} +
+
+
+
+
+
+

Commits

+
+
+
+

Comments

+
+
+
+

Commits & Comments

+
+
+
+
+ ) +} + +export default ContributionGraph diff --git a/src/app/components/search/action.ts b/src/app/components/search/action.ts index da9095f..4098467 100644 --- a/src/app/components/search/action.ts +++ b/src/app/components/search/action.ts @@ -2,8 +2,20 @@ import { auth } from "@/auth" import { getGithubData } from "@/helpers/get-github-data" +import { getIssueCommentsData } from "@/helpers/get-issues-data" +import { getGithubPrsData } from "@/helpers/get-prs-data" -export const fetchIssues = async ({ username }: { username: string }) => { +export const fetchIssues = async ({ + username, + startDate, + endDate, + endCursor +}: { + username: string + startDate?: string + endDate?: string + endCursor: string +}) => { const session = await auth() const token = session?.accessToken @@ -20,7 +32,32 @@ export const fetchIssues = async ({ username }: { username: string }) => { }) ]) + const ranged_response = await Promise.all([ + getGithubPrsData({ + username, + token, + query: "FETCH_RANGED_PRS", + startDate, + endDate + }), + getIssueCommentsData({ + username, + token, + query: "FETCH_RANGED_COMMENTS", + startDate, + endCursor + }) + ]) + const issues = res[0] const prs = res[1] - return { issues, prs } + const ranged_prs = ranged_response[0] + const ranged_issues = ranged_response[1] + + return { + issues, + prs, + ranged_prs, + ranged_issues + } } diff --git a/src/app/components/search/contribution-years.ts b/src/app/components/search/contribution-years.ts new file mode 100644 index 0000000..28221b0 --- /dev/null +++ b/src/app/components/search/contribution-years.ts @@ -0,0 +1,22 @@ +"use server" + +import { auth } from "@/auth" +import { getContributionYears } from "@/helpers/get-contribution-years" + +export const fetchYears = async ({ params }: { params: any }) => { + const session = await auth() + const token = session?.accessToken + + const res = await Promise.all([ + getContributionYears({ + token, + ...params + }) + ]) + + const years = res[0] + + return { + years + } +} diff --git a/src/app/components/search/index.tsx b/src/app/components/search/index.tsx index 6e82da8..59a8415 100644 --- a/src/app/components/search/index.tsx +++ b/src/app/components/search/index.tsx @@ -30,12 +30,12 @@ export default function Search() { setError(null) const { issues, prs } = await fetchIssues({ - username: username as string + username: username as string, + endCursor: "" }) setLoading(false) if (issues.error || prs.error) { - console.error(issues.error, "error") setError(issues.error[0].message || prs.error[0].message) return } diff --git a/src/app/components/years-switch/index.tsx b/src/app/components/years-switch/index.tsx index 04df2ee..e315046 100644 --- a/src/app/components/years-switch/index.tsx +++ b/src/app/components/years-switch/index.tsx @@ -12,7 +12,7 @@ const YearSection = ({ years, currentYear, handleClick }: YearSectionProps) => { const [showYears, setShowYears] = React.useState(false) return ( -
+
{years.map((year) => (
{ {showYears ? ( -
+
{years.slice(1).map((year) => (
{ const searchParams = useSearchParams() @@ -23,11 +24,11 @@ const Page = () => { toggleFilter, yearlyFilter, handleYearlyFilter, - years, memoizedGraphValues, onClickToolTip, goBack } = useGithubIssues() + const { years } = useGetYears() return (
@@ -60,32 +61,11 @@ const Page = () => {
-
-
- {memoizedGraphValues.map((day, idx) => ( - - ))} -
-
-
-
-

Commits

-
-
-
-

Comments

-
-
-
-

Commits & Comments

-
-
-
+ { - const rawComments = data?.issueComments?.nodes.filter( - (comment: any) => comment.issue.author.login === username + data = data ?? [] + + const rawComments = data?.filter( + (comment) => comment.issue.author.login === username ) - const comments: Comment[] = rawComments.map((comment: any) => { + const comments: Comment[] = rawComments?.map((comment) => { return { body: comment.body, repository: comment.repository.url, @@ -34,14 +37,16 @@ export const getOthersComments = ({ data, username }: { - data: any + data: IssueCommentNodes[] username: string }): Comment[] => { - const rawComments = data?.issueComments?.nodes.filter( - (comment: any) => comment.issue.author.login !== username + data = data ?? [] + + const rawComments = data?.filter( + (comment) => comment.issue.author.login !== username ) - const comments: Comment[] = rawComments.map((comment: any) => { + const comments: Comment[] = rawComments?.map((comment) => { return { body: comment.body, repository: comment.repository.url, @@ -60,12 +65,18 @@ export const getOthersComments = ({ return comments } -export const getLongComments = ({ data }: { data: any }): Comment[] => { - const rawComments = data?.issueComments?.nodes.filter( - (comment: any) => comment.body.length > 500 +export const getLongComments = ({ + data +}: { + data: IssueCommentNodes[] +}): Comment[] => { + data = data ?? [] + + const rawComments = data?.filter( + (comment) => comment.body.length > MAX_COMMENT_LENGTH ) - const comments: Comment[] = rawComments.map((comment: any) => { + const comments: Comment[] = rawComments?.map((comment) => { return { body: comment.body, repository: comment.repository.url, diff --git a/src/helpers/get-contribution-years.ts b/src/helpers/get-contribution-years.ts new file mode 100644 index 0000000..a589a6f --- /dev/null +++ b/src/helpers/get-contribution-years.ts @@ -0,0 +1,52 @@ +import { FETCH_CONTRIBUTION_YEARS } from "@/graphql/queries" +import { PrDataType } from "@/types/pull_requests" + +export async function getContributionYears({ + token, + username +}: { + token?: string + username: string +}) { + const tokenFromEnv = process.env.GITHUB_TOKEN + + try { + const res = await fetch("https://api.github.com/graphql", { + method: "POST", + headers: { + Authorization: `Bearer ${token ?? tokenFromEnv}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + query: FETCH_CONTRIBUTION_YEARS, + variables: { + username + } + }) + }) + + if (!res.ok) { + return { error: res.statusText, message: "Failed to fetch API" } + } + + const data = await res.json() + const jsonData: PrDataType = data.data.user + + const contributionYears = + jsonData?.contributionsCollection.contributionYears + + if (data.errors) { + return { error: data.errors, message: "Failed to fetch API" } + } + + return { + data: contributionYears, + error: null + } + } catch (error) { + return { + error: error, + message: "Failed to fetch API" + } + } +} diff --git a/src/helpers/get-github-data.ts b/src/helpers/get-github-data.ts index 39cc1c6..253cd0d 100644 --- a/src/helpers/get-github-data.ts +++ b/src/helpers/get-github-data.ts @@ -38,7 +38,6 @@ export async function getGithubData({ }) }) if (!res.ok) { - console.log("res", res) return { error: res.statusText, message: "Failed to fetch API" } } @@ -52,7 +51,6 @@ export async function getGithubData({ error: null } } catch (error) { - console.log(error) return { error: error, message: "Failed to fetch API" diff --git a/src/helpers/get-issues-data.ts b/src/helpers/get-issues-data.ts new file mode 100644 index 0000000..579902c --- /dev/null +++ b/src/helpers/get-issues-data.ts @@ -0,0 +1,143 @@ +import { FETCH_RANGED_COMMENTS } from "@/graphql/queries" +import { + IssueCommentDataType, + IssueCommentNodes, + IssueComments +} from "@/types/comments" + +export async function getIssueCommentsData({ + username, + token, + query, + startDate, + endCursor +}: { + username: string + token?: string + query: "FETCH_RANGED_COMMENTS" + startDate?: string + endCursor: string | null +}) { + const tokenFromEnv = process.env.GITHUB_TOKEN + let hasNextPage = true + const currentDate = new Date() + const currentYear = currentDate.getFullYear() + const prevYear = currentYear - 1 + const getYearFromDate = startDate?.slice(0, 4) + + endCursor = Number(getYearFromDate) <= prevYear ? endCursor : null + + if ( + getYearFromDate !== currentYear.toString() && + getYearFromDate !== prevYear.toString() + ) { + return { + data: [], + error: null + } + } + + let graphqlQuery = "" + switch (query) { + case "FETCH_RANGED_COMMENTS": + graphqlQuery = FETCH_RANGED_COMMENTS + break + default: + throw new Error("Invalid query") + } + + let startCursorObj: any = {} + let endCursorObj: any = {} + + let allIssueComments: Array = [] + + while (hasNextPage) { + try { + const res = await fetch("https://api.github.com/graphql", { + method: "POST", + headers: { + Authorization: `Bearer ${token ?? tokenFromEnv}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + query: graphqlQuery, + variables: { + username, + endCursor + } + }) + }) + + if (!res.ok) { + return { error: res.statusText, message: "Failed to fetch API" } + } + + const data = await res.json() + const jsonData: IssueCommentDataType = data.data.user + + const nodes_data = jsonData?.issueComments?.nodes + const start_cursor = jsonData?.issueComments?.pageInfo?.startCursor + const isNextPage = jsonData?.issueComments?.pageInfo?.hasNextPage + const end_cursor = jsonData?.issueComments?.pageInfo?.endCursor + + // get previous year from start date of query + const prevYear = Number(getYearFromDate) - 1 + + const findNextYear = nodes_data?.find( + // return first year that is a previous year, we are slicing the createdAt date to get current year of eaxh result + (x) => x.createdAt.slice(0, 4) === prevYear.toString() + ) + + if ( + findNextYear || + (isNextPage === false && findNextYear === undefined) + ) { + // filter previous year from response data, when previous year is found in current query date range + const filter_node_data = nodes_data.filter((x) => { + return x.createdAt.slice(0, 4) === startDate?.slice(0, 4) + }) + + const res = filter_node_data + + allIssueComments.push(...res) + + hasNextPage = false + endCursor = end_cursor + + endCursorObj["end_cursor"] = endCursor + } else { + allIssueComments.push(...nodes_data) + hasNextPage = isNextPage + + endCursor = end_cursor + startCursorObj["start_cursor"] = start_cursor + endCursorObj["end_cursor"] = endCursor + } + + if (data.errors) { + return { error: data.errors, message: "Failed to fetch API" } + } + } catch (error) { + return { + error: error, + message: "Failed to fetch API" + } + } + } + + allIssueComments + .sort( + (a, b) => + new Date(a?.createdAt).getTime() - + new Date(b?.createdAt).getTime() + ) + .filter((x) => x !== undefined) + + return { + data: allIssueComments, + error: null, + startCursorObj, + endCursorObj, + hasNextPage + } +} diff --git a/src/helpers/get-prs-data.ts b/src/helpers/get-prs-data.ts new file mode 100644 index 0000000..4fd7d3f --- /dev/null +++ b/src/helpers/get-prs-data.ts @@ -0,0 +1,94 @@ +import { FETCH_RANGED_PRS } from "@/graphql/queries" +import { PrDataType, PrNodes } from "@/types/pull_requests" + +export async function getGithubPrsData({ + username, + token, + query, + startDate, + endDate +}: { + username: string + token?: string + query: "FETCH_RANGED_PRS" + startDate?: string + endDate?: string +}) { + const tokenFromEnv = process.env.GITHUB_TOKEN + + let graphqlQuery = "" + switch (query) { + case "FETCH_RANGED_PRS": + graphqlQuery = FETCH_RANGED_PRS + break + default: + throw new Error("Invalid query") + } + + let hasNextPage = true + let allPrs: Array = [] + let endCursor = null + + while (hasNextPage) { + try { + const res = await fetch("https://api.github.com/graphql", { + method: "POST", + headers: { + Authorization: `Bearer ${token ?? tokenFromEnv}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + query: graphqlQuery, + variables: { + username, + startDate, + endDate, + endCursor + } + }) + }) + + if (!res.ok) { + return { error: res.statusText, message: "Failed to fetch API" } + } + + const data = await res.json() + const jsonData: PrDataType = data.data.user + + const isNextPage = + jsonData?.contributionsCollection?.pullRequestContributions + ?.pageInfo.hasNextPage + + const nodes_data = + jsonData?.contributionsCollection?.pullRequestContributions + .nodes + + const next_batch = + jsonData?.contributionsCollection?.pullRequestContributions + ?.pageInfo?.endCursor + + allPrs.push(...nodes_data) + hasNextPage = isNextPage + endCursor = next_batch + + if (data.errors) { + return { error: data.errors, message: "Failed to fetch API" } + } + } catch (error) { + return { + error: error, + message: "Failed to fetch API" + } + } + } + + allPrs + .sort( + (a, b) => + new Date(a?.pullRequest.createdAt).getTime() - + new Date(b?.pullRequest.createdAt).getTime() + ) + .filter((x) => x?.pullRequest !== undefined) + + return { data: allPrs, error: null } +} diff --git a/src/helpers/get-pull-requests.ts b/src/helpers/get-pull-requests.ts index e7b591b..251dd6f 100644 --- a/src/helpers/get-pull-requests.ts +++ b/src/helpers/get-pull-requests.ts @@ -1,104 +1,111 @@ import { CURRENT_DAY, DAYS_TO_INACTIVE, ONE_DAY } from "@/config" -import { PullRequests, PR } from "../types/pull_requests" +import { PullRequests, PR, PrNodes } from "../types/pull_requests" export const getPullRequests = ({ data, username }: { - data: any + data: PrNodes[] username: string }): PullRequests => { - const openPRsData = data.pullRequests.edges.filter( - (pr: any) => pr.node.closed === false - ) - const openPRs: PR[] = openPRsData.map((pr: any) => ({ - totalComments: pr.node.totalCommentsCount, + data = data ?? [] + + const openPRsData = data?.filter((pr) => pr?.pullRequest.closed === false) + + const openPRs: PR[] = openPRsData?.map((pr) => ({ + totalComments: pr?.pullRequest.totalCommentsCount, daysOpened: Math.floor( - (CURRENT_DAY - new Date(pr.node.createdAt).getTime()) / ONE_DAY + (CURRENT_DAY - new Date(pr?.pullRequest.createdAt).getTime()) / + ONE_DAY ), - url: pr.node.url, - repoUrl: pr.node.repository.url, - title: pr.node.title, - createdAt: pr.node.createdAt, - avatarUrl: pr.node.author.avatarUrl, - project: pr.node.repository.owner + url: pr?.pullRequest.url, + repoUrl: pr?.pullRequest.repository.url, + title: pr?.pullRequest.title, + createdAt: pr?.pullRequest.createdAt, + avatarUrl: pr?.pullRequest.author.avatarUrl, + project: pr?.pullRequest.repository.owner })) - const openInactivePRsData = openPRsData.filter( - (pr: any) => + const openInactivePRsData = openPRsData?.filter( + (pr) => Math.floor( - (CURRENT_DAY - new Date(pr.node.createdAt).getTime()) / ONE_DAY + (CURRENT_DAY - new Date(pr?.pullRequest.createdAt).getTime()) / + ONE_DAY ) > DAYS_TO_INACTIVE ) - const openInactivePRs: PR[] = openInactivePRsData.map((pr: any) => ({ - totalComments: pr.node.totalCommentsCount, + + const openInactivePRs: PR[] = openInactivePRsData?.map((pr) => ({ + totalComments: pr?.pullRequest.totalCommentsCount, daysOpened: Math.floor( - (CURRENT_DAY - new Date(pr.node.createdAt).getTime()) / ONE_DAY + (CURRENT_DAY - new Date(pr?.pullRequest.createdAt).getTime()) / + ONE_DAY ), - url: pr.node.url, - repoUrl: pr.node.repository.url, - title: pr.node.title, - createdAt: pr.node.createdAt, - avatarUrl: pr.node.author.avatarUrl, - project: pr.node.repository.owner + url: pr?.pullRequest.url, + repoUrl: pr?.pullRequest.repository.url, + title: pr?.pullRequest.title, + createdAt: pr?.pullRequest.createdAt, + avatarUrl: pr?.pullRequest.author.avatarUrl, + project: pr?.pullRequest.repository.owner })) - const closedPRsData = data.pullRequests.edges.filter( - (pr: any) => pr.node.closed === true && pr.node.merged === false + const closedPRsData = data?.filter( + (pr) => + pr?.pullRequest.closed === true && pr?.pullRequest.merged === false ) - const closedPRs: PR[] = closedPRsData.map((pr: any) => ({ - totalComments: pr.node.totalCommentsCount, + + const closedPRs: PR[] = closedPRsData?.map((pr) => ({ + totalComments: pr?.pullRequest.totalCommentsCount, daysOpened: Math.floor( - (new Date(pr.node.closedAt).getTime() - - new Date(pr.node.createdAt).getTime()) / + (new Date(pr?.pullRequest.closedAt).getTime() - + new Date(pr?.pullRequest.createdAt).getTime()) / ONE_DAY ), - url: pr.node.url, - repoUrl: pr.node.repository.url, - title: pr.node.title, - createdAt: pr.node.createdAt, - avatarUrl: pr.node.author.avatarUrl, - project: pr.node.repository.owner + url: pr?.pullRequest.url, + repoUrl: pr?.pullRequest.repository.url, + title: pr?.pullRequest.title, + createdAt: pr?.pullRequest.createdAt, + avatarUrl: pr?.pullRequest.author.avatarUrl, + project: pr?.pullRequest.repository.owner })) - const closedPRsByOthersData = data.pullRequests.edges.filter( - (pr: any) => - pr.node.closed === true && - pr.node.merged === true && - pr.node.mergedBy.login !== username + const closedPRsByOthersData = data?.filter( + (pr) => + pr?.pullRequest.closed === true && + pr?.pullRequest.merged === true && + pr?.pullRequest.mergedBy.login !== username ) - const closedPRsByOthers: PR[] = closedPRsByOthersData.map((pr: any) => ({ - totalComments: pr.node.totalCommentsCount, + + const closedPRsByOthers: PR[] = closedPRsByOthersData?.map((pr) => ({ + totalComments: pr?.pullRequest.totalCommentsCount, daysOpened: Math.floor( - (new Date(pr.node.closedAt).getTime() - - new Date(pr.node.createdAt).getTime()) / + (new Date(pr?.pullRequest.closedAt).getTime() - + new Date(pr?.pullRequest.createdAt).getTime()) / ONE_DAY ), - url: pr.node.url, - repoUrl: pr.node.repository.url, - title: pr.node.title, - createdAt: pr.node.createdAt, - avatarUrl: pr.node.author.avatarUrl, - project: pr.node.repository.owner + url: pr?.pullRequest.url, + repoUrl: pr?.pullRequest.repository.url, + title: pr?.pullRequest.title, + createdAt: pr?.pullRequest.createdAt, + avatarUrl: pr?.pullRequest.author.avatarUrl, + project: pr?.pullRequest.repository.owner })) - const mergedPRData = data.pullRequests.edges.filter( - (pr: any) => pr.node.merged === true - ) - const mergedPRs: PR[] = mergedPRData.map((pr: any) => ({ - totalComments: pr.node.totalCommentsCount, + const mergedPRData = data?.filter((pr) => pr?.pullRequest.merged === true) + + const mergedPRs: PR[] = mergedPRData?.map((pr) => ({ + totalComments: pr?.pullRequest.totalCommentsCount, daysOpened: Math.floor( - (new Date(pr.node.mergedAt).getTime() - - new Date(pr.node.createdAt).getTime()) / + (new Date(pr?.pullRequest.mergedAt).getTime() - + new Date(pr?.pullRequest.createdAt).getTime()) / ONE_DAY ), - url: pr.node.url, - repoUrl: pr.node.repository.url, - title: pr.node.title, - createdAt: pr.node.createdAt, - avatarUrl: pr.node.author.avatarUrl, - project: pr.node.repository.owner + url: pr?.pullRequest.url, + repoUrl: pr?.pullRequest.repository.url, + title: pr?.pullRequest.title, + createdAt: pr?.pullRequest.createdAt, + avatarUrl: pr?.pullRequest.author.avatarUrl, + project: pr?.pullRequest.repository.owner })) openPRs.sort( diff --git a/src/helpers/utils.ts b/src/helpers/utils.ts index f4cb415..619de2d 100644 --- a/src/helpers/utils.ts +++ b/src/helpers/utils.ts @@ -7,8 +7,8 @@ import { GRID_GREEN, GRID_YELLOW } from "@/types" -import { Comment, IssuesObject } from "@/types/comments" -import { Project, PRsObject } from "@/types/pull_requests" +import { Comment, IssueCommentNodes, IssuesObject } from "@/types/comments" +import { PrNodes, Project, PRsObject } from "@/types/pull_requests" export const getIssueNumber = (arg: string) => { const splitUrl = arg.split("#") @@ -42,24 +42,24 @@ export const collapsibleHeader = (repoUrl: string, username: string) => { } export const getOrganisations = ( - prsData: any, - issueData: any, + prsData: PrNodes[], + issueData: IssueCommentNodes[], username: string ) => { - const prOrgs: Array = prsData?.pullRequests?.edges - .filter( - (pr: any) => - pr.node.repository.owner.login.toLowerCase() !== - username.toLowerCase() + const prOrgs: Array = prsData + ?.filter( + (pr) => + pr?.pullRequest?.repository.owner.login.toLowerCase() !== + username.toLowerCase() && pr?.pullRequest !== undefined ) - .map((pr: any) => ({ - login: pr.node.repository.owner.login, - avatarUrl: pr.node.repository.owner.avatarUrl + .map((pr) => ({ + login: pr?.pullRequest?.repository.owner.login, + avatarUrl: pr?.pullRequest?.repository.owner.avatarUrl })) - const issueOrgs: Array = issueData?.issueComments?.nodes - .filter( - (issue: any) => + const issueOrgs: Array = issueData + ?.filter( + (issue) => issue.repository.owner.login.toLowerCase() !== username.toLowerCase() ) @@ -68,11 +68,28 @@ export const getOrganisations = ( avatarUrl: issue.repository.owner.avatarUrl })) - const orgs = [...prOrgs, ...issueOrgs].filter( - (value, index, arr) => - arr.map((x) => x.login).indexOf(value.login) === index + const allOrgs = [...prOrgs, ...issueOrgs].reduce( + (acc, curr) => { + const key = curr.login + + const group = acc[key] ?? [] + return { ...acc, [key]: [...group, curr] } + }, + {} as Record> ) + const addOrgPosition = [] + for (const [key, value] of Object.entries(allOrgs)) { + if (key) { + addOrgPosition.push({ + login: value[0].login, + avatarUrl: value[0].avatarUrl, + position: value.length + }) + } + } + const orgs = addOrgPosition.sort((a, c) => c.position - a.position) + return { orgs } } @@ -96,7 +113,6 @@ export const extractYears = (prsData: PRsObject, issueData: IssuesObject) => { export function filterObject }>( toggleFilter: string | null, - yearlyFilter: string, toolTipKey: string | null, data: Type ): Type { @@ -111,10 +127,7 @@ export function filterObject }>( .slice(1, 3) .join(" ") - return ( - x.createdAt.toString().slice(0, 4) === yearlyFilter && - result_date === toolTipKey - ) + return result_date === toolTipKey }) if (toggleFilter) { @@ -127,8 +140,6 @@ export function filterObject }>( .join(" ") return ( - x.createdAt.toString().slice(0, 4) === - yearlyFilter && result_date === toolTipKey && x.project?.login?.toLowerCase() === toggleFilter?.toLowerCase() @@ -148,30 +159,6 @@ export function filterObject }>( x.project?.login?.toLowerCase() === toggleFilter?.toLowerCase() ) - - if (yearlyFilter) { - for (const [key, value] of Object.entries( - filteredObject as Type - )) { - filteredObject[key] = value.filter( - (x) => - x.createdAt.toString().slice(0, 4) === - yearlyFilter && - x.project?.login?.toLowerCase() === - toggleFilter?.toLowerCase() - ) - } - } - } - - return filteredObject - } - - if (yearlyFilter) { - for (const [key, value] of Object.entries(data)) { - filteredObject[key] = value.filter( - (x) => x.createdAt.toString().slice(0, 4) === yearlyFilter - ) } return filteredObject @@ -195,10 +182,9 @@ export const months = [ "Dec" ] -const days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] +export const days = ["Mon", "Wed", "Fri"] export const getYearlyContributions = ( - year: string, prsData: PRsObject, issueData: IssuesObject ) => { @@ -210,31 +196,23 @@ export const getYearlyContributions = ( const extractPrDates = Object.values(prsData) .map((set) => { - return set - .filter( - (item) => item.createdAt.toString().slice(0, 4) === year - ) - .map((x) => { - return { - date: new Date(x.createdAt).toDateString(), - type: "prs" - } - }) + return set.map((x) => { + return { + date: new Date(x.createdAt).toDateString(), + type: "prs" + } + }) }) .flat() const extractIssueDates = Object.values(issueData) .map((set) => { - return set - .filter( - (item) => item.createdAt.toString().slice(0, 4) === year - ) - .map((x) => { - return { - date: new Date(x.createdAt).toDateString(), - type: "issues" - } - }) + return set.map((x) => { + return { + date: new Date(x.createdAt).toDateString(), + type: "issues" + } + }) }) .flat() @@ -280,7 +258,7 @@ export const getYearlyContributions = ( }) } - return { year, contributions } + return { contributions } } export const createGridSet = (year: string) => { diff --git a/src/hooks/useGetYears.ts b/src/hooks/useGetYears.ts new file mode 100644 index 0000000..02c6cc8 --- /dev/null +++ b/src/hooks/useGetYears.ts @@ -0,0 +1,30 @@ +import React from "react" +import { useSearchParams } from "next/navigation" +import { fetchYears } from "@/app/components/search/contribution-years" + +export const useGetYears = () => { + const searchParams = useSearchParams() + const username = searchParams.get("username") + const [years, setYears] = React.useState([]) + + React.useEffect(() => { + const getContributionByYear = async () => { + const params = { + username: username as string + } + const { years } = await fetchYears({ + params + }) + + const yearsData = + years?.data !== undefined + ? years.data.map((yr) => yr.toString()) + : [] + setYears(yearsData) + } + + getContributionByYear() + }, [username]) + + return { years } +} diff --git a/src/hooks/useGithubIssues.ts b/src/hooks/useGithubIssues.ts index aa29032..6c89fb3 100644 --- a/src/hooks/useGithubIssues.ts +++ b/src/hooks/useGithubIssues.ts @@ -10,7 +10,6 @@ import { Project, PRsObject } from "@/types/pull_requests" import { useRouter, useSearchParams } from "next/navigation" import { createGridSet, - extractYears, filterObject, generateGraphValues, getOrganisations, @@ -28,7 +27,9 @@ export const useGithubIssues = () => { const [loading, setLoading] = useState(false) const [error, setError] = useState(null) - const [projects, setProjects] = useState>([]) + const [projects, setProjects] = useState< + Array + >([]) const [toggleFilter, setToggleFilter] = useState("") const [yearlyFilter, setYearlyFilter] = useState(currentYear) const [toolTipKey, setToolTipKey] = useState("") @@ -45,6 +46,20 @@ export const useGithubIssues = () => { mergedPrs: [] }) + /** + * We are using the Date object to create the startDate and endDate of the query range + * new Date(): creates a date using the Date constructor + * Number(args): Converts the string variable of args to a number + * + * 0, 1, 4 or 12, 0, 4: are parameters passed to the Date contructor to modify the Date object; + * the first Argument: This specifies the month parameter + * the second Argument: This specifies the day parameter + * the first Argument: This specifies the time parameter, using the 24:00hr clock format + */ + + const startDate = new Date(Number(yearlyFilter), 0, 1, 4).toISOString() + const endDate = new Date(Number(yearlyFilter), 12, 0, 4).toISOString() + useEffect(() => { const fetchGithubIssues = async () => { if (!username) { @@ -69,29 +84,51 @@ export const useGithubIssues = () => { mergedPrs: [] })) setProjects([]) - const { issues, prs } = await fetchIssues({ - username: username as string + + const endCursor = localStorage.getItem("end_cursor") as string + + const { ranged_prs, ranged_issues } = await fetchIssues({ + username: username as string, + startDate, + endDate, + endCursor }) + const rangedPrsData = + ranged_prs?.data !== undefined ? ranged_prs.data : [] + const rangedIssuesData = + ranged_issues?.data !== undefined ? ranged_issues.data : [] + + const storedCursor = + Number(yearlyFilter) <= Number(currentYear) - 1 + ? endCursor + : ranged_issues?.endCursorObj?.end_cursor + + localStorage.setItem("end_cursor", storedCursor) + setLoading(false) - if (issues.error || prs.error) { - console.error(issues.error, "error") - setError(issues.error[0].message || prs.error[0].message) + if (ranged_issues.error || ranged_prs.error) { + setError( + ranged_issues?.error[0]?.message || + ranged_prs?.error[0]?.message + ) + setLoading(false) return } const issue = getOwnComments({ - data: issues.data, + data: rangedIssuesData, username: username as string }) const longIssue = getLongComments({ - data: issues.data + data: rangedIssuesData }) const othersIssue = getOthersComments({ - data: issues.data, + data: rangedIssuesData, username: username as string }) + const { closedPRs, closedPRsByOthers, @@ -99,10 +136,14 @@ export const useGithubIssues = () => { openInactivePRs, openPRs } = getPullRequests({ - data: prs.data, + data: rangedPrsData, username: username as string }) - const { orgs } = getOrganisations(prs.data, issues.data, username) + const { orgs } = getOrganisations( + rangedPrsData, + rangedIssuesData, + username + ) setProjects(orgs) @@ -124,26 +165,19 @@ export const useGithubIssues = () => { } fetchGithubIssues() - }, [username]) + }, [currentYear, endDate, startDate, username, yearlyFilter]) const memoizedIssues = useMemo( - () => - filterObject(toggleFilter, yearlyFilter, toolTipKey, issuesObject), - [issuesObject, toggleFilter, toolTipKey, yearlyFilter] + () => filterObject(toggleFilter, toolTipKey, issuesObject), + [issuesObject, toggleFilter, toolTipKey] ) const memoizedPrs = useMemo( - () => filterObject(toggleFilter, yearlyFilter, toolTipKey, prsObject), - [prsObject, toggleFilter, toolTipKey, yearlyFilter] + () => filterObject(toggleFilter, toolTipKey, prsObject), + [prsObject, toggleFilter, toolTipKey] ) - const { years } = extractYears(prsObject, issuesObject) - - const { contributions } = getYearlyContributions( - yearlyFilter, - prsObject, - issuesObject - ) + const { contributions } = getYearlyContributions(prsObject, issuesObject) const { gridSet } = createGridSet(yearlyFilter!) const memoizedGraphValues = useMemo( @@ -156,7 +190,8 @@ export const useGithubIssues = () => { } const handleYearlyFilter = (key: string) => { - setYearlyFilter((prev) => (prev === key ? prev : key)) + if (key === yearlyFilter) return + setYearlyFilter(key) } const onClickToolTip = (content: Contribution) => { @@ -181,8 +216,8 @@ export const useGithubIssues = () => { toggleFilter, yearlyFilter, handleYearlyFilter, - years, memoizedGraphValues, - onClickToolTip, goBack + onClickToolTip, + goBack } } diff --git a/src/types/comments.ts b/src/types/comments.ts index e5e881a..16677d3 100644 --- a/src/types/comments.ts +++ b/src/types/comments.ts @@ -1,3 +1,25 @@ +import { PageInfo, Repository } from '.' + +export type IssueCommentDataType = { + issueComments: IssueComments +} + +export type IssueComments = { + pageInfo: PageInfo + nodes: Array +} + +export type IssueCommentNodes = { + issue: Issue + body: string + repository: Repository + url: string + createdAt: string + author: { + login: string + } +} + export type Comment = { issue: Issue body: string @@ -7,7 +29,6 @@ export type Comment = { project: { login: string; avatarUrl: string } } - export type IssuesObject = { ownIssueComments: Comment[] longIssueComments: Comment[] @@ -19,7 +40,5 @@ export type Issue = { login: string avatarUrl: string } - comments: { - totalCount: number - } + comments: { totalCount: number } } diff --git a/src/types/index.ts b/src/types/index.ts index 6a317a0..0db39b7 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -3,6 +3,20 @@ export type Contributions = Record< Record> > +export type PageInfo = { + hasNextPage: boolean + startCursor: string + endCursor: string +} + +export type Repository = { + url: string + owner: { + login: string + avatarUrl: string + } +} + export type GridSet = { month: string boxes: Array<{ day: number; is_active: boolean }> @@ -21,3 +35,4 @@ export const GRID_YELLOW = "#E7C23E" export const GRID_BLUE = "#0783F5" export const GRID_GRAY = "#EEEEEE" export const GRID_GREEN = "#39D353" +export const MAX_COMMENT_LENGTH = 500 diff --git a/src/types/pull_requests.ts b/src/types/pull_requests.ts index ceada8c..d1c00f4 100644 --- a/src/types/pull_requests.ts +++ b/src/types/pull_requests.ts @@ -1,3 +1,31 @@ +import { PageInfo, Repository } from "." + +export type PrDataType = { + contributionsCollection: { + contributionYears: Array + pullRequestContributions: { + pageInfo: PageInfo + nodes: Array + } + } +} + +export type PrNodes = { + pullRequest: { + author: { avatarUrl: string } + closed: boolean + closedAt: string + createdAt: string + merged: boolean + mergedAt: string + mergedBy: { login: string } + repository: Repository + title: string + totalCommentsCount: number + url: string + } +} + export type PR = { totalComments: number daysOpened: number