implements github challenges list

This commit is contained in:
jongomez
2023-11-15 16:45:53 +00:00
committed by Jon
parent 7ff2562415
commit 2ec869ba45
9 changed files with 191 additions and 172 deletions
@@ -45,6 +45,9 @@ const config = {
routeBasePath: '/',
path: 'docs',
},
// Uncomment to fetch new generated data.
/*
generated: {
challenges: {
repoArray: [
@@ -57,6 +60,7 @@ const config = {
jobBoard: 'waku',
},
},
*/
}),
],
],
@@ -22,6 +22,7 @@ import {
GithubChallenges,
} from '../components/mdx'
import * as jobData from '/generated/jobs.json'
import * as challengesData from '/generated/challenges.json'
<Hero size="large">
<HeroInfo>
@@ -355,5 +356,12 @@ import * as jobData from '/generated/jobs.json'
Build on Waku
</CallToActionButton>
</Box>
<JobsPerDepartment jobData={jobData} />
<GithubChallenges org="codex" useDummyData />
<JobsPerDepartment jobData={jobData} useDummyData />
<GithubChallenges
challengesData={challengesData}
subheaderText="Lorem ipsum dolor sit amet consectetur. Enim magna urna fames mattis
tincidunt nibh mi ornare. Sed amet morbi mauris pellentesque fusce ut.
Bibendum vestibulum Lorem ipsum dolor sit amet consectetur. Enim magna
urna fames mattis tincidunt nibh mi ornare. Sed amet morbi mauris
pellentesque fusce ut. Bibendum vestibulum"
/>
@@ -48,6 +48,14 @@ const fetchChallenges = async ({
}: {
out: string
} & ChallengeConfig) => {
if (!githubAccessToken) {
console.log(
'No github access token provided, creating empty challenges file',
)
await fsp.writeFile(out, '{}')
return
}
const issues = await fetchGithubIssues(repoArray, githubAccessToken)
await fsp.writeFile(out, JSON.stringify(issues))
@@ -7,3 +7,4 @@ export type SiteConfig = Partial<Config>
export * from './preset'
export * from './status'
export * from './themes'
export * from './github'
@@ -53,6 +53,7 @@
}
.mdx-ghc__participant-photo {
width: 24px;
border-radius: 100%;
margin-left: -4px;
border: 1px solid rgb(var(--lsd-border-secondary));
@@ -85,9 +86,3 @@
grid-template-columns: 82px 1fr;
}
}
.mdx-ghc__achievement {
display: flex;
align-items: center;
padding-left: 4px;
}
@@ -1,7 +1,8 @@
import { Typography } from '@acid-info/lsd-react'
import { SingleGithubChallenge } from './SingleGithubChallenge'
import React from 'react'
import useFetchGithubIssues, { GithubIssue } from './useFetchGithubIssues'
import { dummyGithubIssue } from './githubChallengesDummyData'
import { SingleGithubChallenge } from './SingleGithubChallenge'
import { GithubIssue } from '@acid-info/logos-docusaurus-preset/src/types/github'
type GithubChallengesHeaderProps = {
message?: string
@@ -22,50 +23,73 @@ const GithubChallengesHeader: React.FC<GithubChallengesHeaderProps> = ({
}
const hasChallenges = (githubChallenges: GithubIssue[]): boolean => {
return githubChallenges.length > 0
return Array.isArray(githubChallenges) && !!githubChallenges.length
}
const getIssuesFromRawData = (rawData: any): GithubIssue[] => {
let issues: GithubIssue[] = []
Object.keys(rawData).forEach((key) => {
if (!isNaN(Number(key))) {
// Extract the array of issues for each repository
const repoIssues = Object.values(rawData[key])[0] // each object should have only one key-value pair
if (Array.isArray(repoIssues)) {
issues = issues.concat(repoIssues)
}
}
})
return issues
}
type GithubChallengesProps = React.HTMLAttributes<HTMLDivElement> & {
org: string
challengesData: any
useDummyData?: boolean
subheaderText?: string
filterByName?: string // A string that represents a regex pattern
}
export const GithubChallenges: React.FC<GithubChallengesProps> = ({
org,
challengesData,
useDummyData,
subheaderText,
filterByName,
...props
}) => {
const [data, error, loading] = useFetchGithubIssues(org, useDummyData)
let challengesArray: GithubIssue[] | undefined | null = useDummyData
? [dummyGithubIssue]
: getIssuesFromRawData(challengesData)
if (loading) {
return <GithubChallengesHeader />
}
if (error) {
return <GithubChallengesHeader message="Error fetching data" />
}
if (!data || !hasChallenges(data)) {
if (!challengesArray || !hasChallenges(challengesArray)) {
return <GithubChallengesHeader message="No challenges to show" />
}
if (filterByName) {
try {
const regex = new RegExp(filterByName, 'i') // 'i' for case-insensitive
challengesArray = challengesArray?.filter((issue) =>
regex.test(issue.title),
)
} catch (error) {
console.error('Invalid regex pattern:', error)
}
}
return (
<div {...props}>
<GithubChallengesHeader />
<Typography
variant="body1"
className="mdx-ghc-subheader-text"
component="div"
>
Lorem ipsum dolor sit amet consectetur. Enim magna urna fames mattis
tincidunt nibh mi ornare. Sed amet morbi mauris pellentesque fusce ut.
Bibendum vestibulum Lorem ipsum dolor sit amet consectetur. Enim magna
urna fames mattis tincidunt nibh mi ornare. Sed amet morbi mauris
pellentesque fusce ut. Bibendum vestibulum
</Typography>
{data.map((issue) => {
return <SingleGithubChallenge key={issue.id} issue={issue} />
})}
{!!subheaderText && (
<Typography
variant="body1"
className="mdx-ghc-subheader-text"
component="div"
>
{subheaderText}
</Typography>
)}
{challengesArray.map((issue) => (
<SingleGithubChallenge key={issue.id} issue={issue} />
))}
</div>
)
}
@@ -3,7 +3,54 @@ import React from 'react'
import { IconExternalLink } from '@logos-theme/components/Icon'
import './GithubChallenges.scss'
import Link from '@docusaurus/Link'
import { GithubIssue } from './useFetchGithubIssues'
import { GithubIssue } from '@acid-info/logos-docusaurus-preset/src/types/github'
const getProjectNames = (issue: GithubIssue): string => {
const projectNames = issue.projects.map((project) => project.name)
if (projectNames.length === 0) {
return ''
}
return Array.from(new Set(projectNames)).join(', ')
}
type Participant = {
name: string
avatarUrl: string
}
const extractUniqueParticipants = (issue: GithubIssue): Participant[] => {
const participants = new Map<string, Participant>()
// Add the user who created the issue
participants.set(issue.user.login, {
name: issue.user.login,
avatarUrl: issue.user.avatarUrl,
})
// Add all assignees
issue.assignees.forEach((assignee) => {
participants.set(assignee.login, {
name: assignee.login,
avatarUrl: assignee.avatarUrl,
})
})
// Skipping comment authors for now, because they don't have avatarUrl.
/*
// Add all comment authors
issue.comments.forEach((comment) => {
// Assuming comment.author is the login name of the commenter
participants.set(comment.author, {
name: comment.author,
avatarUrl: '', // Placeholder, as avatarUrl is not available directly in GithubComment type
})
})
*/
return Array.from(participants.values())
}
function addSizeToAvatarUrl(avatarUrl: string, size: number = 24): string {
const url = new URL(avatarUrl)
@@ -25,6 +72,39 @@ type SingleGithubChallengeProps = {
export const SingleGithubChallenge: React.FC<SingleGithubChallengeProps> = ({
issue,
}) => {
const participants = extractUniqueParticipants(issue)
const projectsConst = getProjectNames(issue)
const hasProjects = projectsConst !== ''
const hasMilestone = !!issue.milestone
const elementsToRender: React.ReactNode[] = []
if (hasProjects) {
elementsToRender.push(
<>
<Typography variant="body3" className="mdx-ghc__label">
{projectsConst.includes(', ') ? 'Projects' : 'Project'}
</Typography>
<Typography variant="body3" className="mdx-ghc__project-name">
{projectsConst}
</Typography>
</>,
)
}
if (hasMilestone) {
elementsToRender.push(
<>
<Typography variant="body3" className="mdx-ghc__label">
Milestone
</Typography>
<Typography variant="body3" className="mdx-ghc__milestone-text">
{issue.milestone}
</Typography>
</>,
)
}
return (
<div className="mdx-ghc__container">
<Link href={issue.url} className="mdx-ghc__issue-title-link">
@@ -40,7 +120,7 @@ export const SingleGithubChallenge: React.FC<SingleGithubChallengeProps> = ({
key={index}
className="mdx-ghc__challenge-label"
>
{label.name}
{label}
</Typography>
))}
</div>
@@ -60,50 +140,20 @@ export const SingleGithubChallenge: React.FC<SingleGithubChallengeProps> = ({
>
{issue.comments.length}
</Typography>
{issue.participants.map((participant, index) => (
{participants.map((participant, index) => (
<img
key={index}
className="mdx-ghc__participant-photo"
src={addSizeToAvatarUrl(participant.avatarUrl)}
alt={participant.login}
alt={participant.name}
/>
))}
</div>
{/* 1st row 3rd item */}
<Typography variant="body3" className="mdx-ghc__label">
Project
</Typography>
{/* 1st row 4th item */}
<Typography variant="body3" className="mdx-ghc__project-name">
{issue.project ? issue.project.name : ''}
</Typography>
{/* 2nd row 1st item */}
<Typography variant="body3" className="mdx-ghc__label">
Achievement
</Typography>
{/* 2nd row 2nd item */}
<div className="mdx-ghc__achievement">
<img
className="mdx-ghc__participant-photo"
src={addSizeToAvatarUrl(
'https://avatars.githubusercontent.com/u/8811422?u=b4aec0f11a78abe3b71c42e4adfba3ebd61f34aa&v=4',
)}
/>
</div>
{/* 2nd row 3rd item */}
<Typography variant="body3" className="mdx-ghc__label">
Milestone
</Typography>
{/* 2nd row 4th item */}
<Typography variant="body3" className="mdx-ghc__milestone-text">
{issue.milestone || ''}
</Typography>
{/* Render Projects and/or Milestone */}
{elementsToRender.map((element, index) => (
<React.Fragment key={index}>{element}</React.Fragment>
))}
</div>
<Link href={issue.url} className="mdx-ghc__view-on-github-link">
@@ -1,4 +1,4 @@
import { GithubIssue } from './useFetchGithubIssues'
import { GithubIssue } from '@acid-info/logos-docusaurus-preset/src/types/github'
// Only for dev purposes - prevents making multiple requests to the API while developing.
export const dummyGithubIssue: GithubIssue = {
@@ -6,45 +6,48 @@ export const dummyGithubIssue: GithubIssue = {
title: 'Bug in pagination',
body: 'When navigating to the second page, the first item repeats.',
url: 'https://github.com/user/repo/issues/12345',
labels: [
{
name: 'bug',
},
{
name: 'frontend',
},
],
user: {
login: 'user123',
avatarUrl: 'https://avatars.githubusercontent.com/u/8811422?v=4',
},
labels: ['bug', 'frontend'],
commentCount: 3,
comments: [
{
id: 'c1',
author: 'alice123',
body: 'I have also noticed this issue. Working on a fix now.',
createdAt: '2021-01-01T12:00:00Z',
},
{
id: 'c2',
author: 'bob456',
body: 'Any updates on this?',
createdAt: '2021-01-02T15:30:00Z',
},
{
id: 'c3',
author: 'jaquim',
body: 'I like turtles.',
createdAt: '2021-01-03T09:45:00Z',
},
],
participants: [
assignees: [
{
login: 'alice123',
avatarUrl:
'https://avatars.githubusercontent.com/u/8811422?u=b4aec0f11a78abe3b71c42e4adfba3ebd61f34aa&v=4',
avatarUrl: 'https://avatars.githubusercontent.com/u/8811422?v=4',
},
{
login: 'bob456',
avatarUrl:
'https://avatars.githubusercontent.com/u/8811422?u=b4aec0f11a78abe3b71c42e4adfba3ebd61f34aa&v=4',
},
{
login: 'jaquim',
avatarUrl:
'https://avatars.githubusercontent.com/u/8811422?u=b4aec0f11a78abe3b71c42e4adfba3ebd61f34aa&v=4',
avatarUrl: 'https://avatars.githubusercontent.com/u/8811422?v=4',
},
],
project: {
name: 'Awesome Project',
},
achievement: 'Solved critical frontend bug',
milestone: 'v1.0.0',
created_at: '2020-12-31T11:00:00Z',
updated_at: '2021-01-04T13:00:00Z',
projects: [
{
name: 'Awesome Project',
},
],
}
@@ -1,74 +0,0 @@
import { useEffect, useState } from 'react'
import { dummyGithubIssue } from './githubChallengesDummyData'
type GithubIssueParticipant = {
login: string
avatarUrl: string
}
type GithubProject = {
name: string
}
type GithubMilestone = string
type GithubIssueComment = {
body: string
}
type GithubIssueLabel = {
name: string
}
export type GithubIssue = {
id: string
title: string
body: string
url: string
labels: GithubIssueLabel[]
comments: GithubIssueComment[]
participants: GithubIssueParticipant[]
project?: GithubProject
milestone?: GithubMilestone
achievement: string
}
function useFetchGithubIssues(
org: string,
useDummyData: boolean = false,
): [GithubIssue[] | null, string | null, boolean] {
const [data, setData] = useState<GithubIssue[] | null>(null)
const [errorMessage, setErrorMessage] = useState<string | null>(null)
const [loading, setLoading] = useState<boolean>(true)
useEffect(() => {
if (useDummyData) {
// Mocking an API call with setTimeout
setTimeout(() => {
try {
// Success scenario:
setData([dummyGithubIssue, dummyGithubIssue, dummyGithubIssue])
setLoading(false)
// Uncomment the below lines to simulate an error scenario:
// throw new Error("Failed to fetch data");
} catch (error) {
let errorMessage = 'An error occurred'
if (error instanceof Error) {
errorMessage = error.message
} else if (typeof error === 'string') {
errorMessage = error
}
console.error(errorMessage)
setErrorMessage(errorMessage)
setLoading(false)
}
}, 2000) // Mock delay of 2 seconds
}
}, [])
return [data, errorMessage, loading]
}
export default useFetchGithubIssues