mirror of
https://github.com/wavetermdev/docusaurus-og.git
synced 2026-08-05 13:46:35 -07:00
implements github challenges list
This commit is contained in:
@@ -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'
|
||||
|
||||
+1
-6
@@ -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;
|
||||
}
|
||||
|
||||
+53
-29
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
+88
-38
@@ -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">
|
||||
|
||||
+26
-23
@@ -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',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
-74
@@ -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
|
||||
Reference in New Issue
Block a user