Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 5 additions & 0 deletions backend/apps/owasp/graphql/nodes/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@
class GenericEntityNode:
"""Base node class for OWASP entities with common fields and resolvers."""

@strawberry.field
def leaders_logins(self) -> list[str]:
"""Resolve leaders logins."""
return [leader.login for leader in self.leaders.all()]

@strawberry.field
def leaders(self) -> list[str]:
"""Resolve leaders."""
Expand Down
54 changes: 4 additions & 50 deletions frontend/src/app/about/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,20 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { Tooltip } from '@heroui/tooltip'
import Image from 'next/image'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { useEffect, useState } from 'react'
import FontAwesomeIconWrapper from 'wrappers/FontAwesomeIconWrapper'
import { ErrorDisplay, handleAppError } from 'app/global-error'
import { GET_PROJECT_METADATA, GET_TOP_CONTRIBUTORS } from 'server/queries/projectQueries'
import { GET_LEADER_DATA } from 'server/queries/userQueries'
import type { Contributor } from 'types/contributor'
import type { Project } from 'types/project'
import type { User } from 'types/user'
import { aboutText, technologies } from 'utils/aboutData'
import { capitalize } from 'utils/capitalize'
import AnchorTitle from 'components/AnchorTitle'
import AnimatedCounter from 'components/AnimatedCounter'
import LeadersListBlock from 'components/LeadersListBlock'
import LoadingSpinner from 'components/LoadingSpinner'
import Markdown from 'components/MarkdownWrapper'
import SecondaryCard from 'components/SecondaryCard'
import TopContributorsList from 'components/TopContributorsList'
import UserCard from 'components/UserCard'

const leaders = {
arkid15r: 'CCSP, CISSP, CSSLP',
Expand Down Expand Up @@ -112,15 +108,9 @@ const About = () => {
))}
</SecondaryCard>

<SecondaryCard icon={faArrowUpRightFromSquare} title={<AnchorTitle title="Leaders" />}>
<div className="flex w-full flex-col items-center justify-around overflow-hidden md:flex-row">
{Object.keys(leaders).map((username) => (
<div key={username}>
<LeaderData username={username} />
</div>
))}
</div>
</SecondaryCard>
{leaders && (
<LeadersListBlock leaders={leaders} icon={faArrowUpRightFromSquare} label="Leaders" />
)}

{topContributors && (
<TopContributorsList
Expand Down Expand Up @@ -241,40 +231,4 @@ const About = () => {
)
}

const LeaderData = ({ username }: { username: string }) => {
const { data, loading, error } = useQuery(GET_LEADER_DATA, {
variables: { key: username },
})
const router = useRouter()

if (loading) return <p>Loading {username}...</p>
if (error) return <p>Error loading {username}'s data</p>

const user = data?.user

if (!user) {
return <p>No data available for {username}</p>
}

const handleButtonClick = (user: User) => {
router.push(`/members/${user.login}`)
}

return (
<UserCard
avatar={user.avatarUrl}
button={{
icon: <FontAwesomeIconWrapper icon="fa-solid fa-right-to-bracket" />,
label: 'View Profile',
onclick: () => handleButtonClick(user),
}}
className="h-64 w-40 bg-inherit"
company={user.company}
description={leaders[user.login]}
location={user.location}
name={user.name || username}
/>
)
}

export default About
1 change: 1 addition & 0 deletions frontend/src/app/projects/[projectKey]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ const ProjectDetailsPage = () => {
stats={projectStats}
summary={project.summary}
title={project.name}
leadersLogins={project.leadersLogins}
topContributors={topContributors}
topics={project.topics}
type="project"
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/components/CardDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import Link from 'next/link'
import type { DetailsCardProps } from 'types/card'
import { LeadersListBlockProps } from 'types/leaders'
import { capitalize } from 'utils/capitalize'
import { IS_PROJECT_HEALTH_ENABLED } from 'utils/credentials'
import { getSocialIcon } from 'utils/urlIconMappings'
Expand All @@ -19,6 +20,7 @@ import ChapterMapWrapper from 'components/ChapterMapWrapper'
import HealthMetrics from 'components/HealthMetrics'
import InfoBlock from 'components/InfoBlock'
import LeadersList from 'components/LeadersList'
import LeadersListBlock from 'components/LeadersListBlock'
import Milestones from 'components/Milestones'
import RecentIssues from 'components/RecentIssues'
import RecentPullRequests from 'components/RecentPullRequests'
Expand Down Expand Up @@ -49,6 +51,7 @@ const DetailsCard = ({
stats,
summary,
title,
leadersLogins,
topContributors,
topics,
type,
Expand Down Expand Up @@ -197,6 +200,15 @@ const DetailsCard = ({
)}
</div>
)}
{leadersLogins && (
<LeadersListBlock
label="Leaders"
leaders={leadersLogins.reduce((acc, login) => {
acc[login] = ''
return acc
}, {} as LeadersListBlockProps)}
/>
)}
{topContributors && (
<TopContributorsList
icon={faUsers}
Expand Down
71 changes: 71 additions & 0 deletions frontend/src/components/LeadersListBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
'use client'
import { useQuery } from '@apollo/client'
import { IconProp } from '@fortawesome/fontawesome-svg-core'
import { useRouter } from 'next/navigation'
import FontAwesomeIconWrapper from 'wrappers/FontAwesomeIconWrapper'
import { GET_LEADER_DATA } from 'server/queries/userQueries'
import { LeadersListBlockProps } from 'types/leaders'
import { User } from 'types/user'
import AnchorTitle from 'components/AnchorTitle'
import SecondaryCard from 'components/SecondaryCard'
import UserCard from 'components/UserCard'

const LeadersListBlock = ({
leaders,
label = 'Leaders',
icon,
}: {
leaders: LeadersListBlockProps
label?: string
icon?: IconProp
}) => {
const LeaderData = ({ username }: { username: string }) => {
const { data, loading, error } = useQuery(GET_LEADER_DATA, {
variables: { key: username },
})
const router = useRouter()

if (loading) return <p>Loading {username}...</p>
if (error) return <p>Error loading {username}'s data</p>

const user = data?.user

if (!user) {
return <p>No data available for {username}</p>
}

const handleButtonClick = (user: User) => {
router.push(`/members/${user.login}`)
}

return (
<UserCard
avatar={user.avatarUrl}
button={{
icon: <FontAwesomeIconWrapper icon="fa-solid fa-right-to-bracket" />,
label: 'View Profile',
onclick: () => handleButtonClick(user),
}}
className="h-64 w-40 bg-inherit"
company={user.company}
description={leaders[user.login]}
location={user.location}
name={user.name || username}
/>
)
}

return (
<SecondaryCard icon={icon} title={<AnchorTitle title={label} />}>
<div className="flex w-full flex-col items-center justify-around overflow-hidden md:flex-row">
{Object.keys(leaders).map((username) => (
<div key={username}>
<LeaderData username={username} />
</div>
))}
</div>
</SecondaryCard>
)
}

export default LeadersListBlock
1 change: 1 addition & 0 deletions frontend/src/server/queries/projectQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const GET_PROJECT_DATA = gql`
key
languages
leaders
leadersLogins
level
name
healthMetrics(limit: 30) {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/types/card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export interface DetailsCardProps {
stats?: Stats[]
summary?: string
title?: string
leadersLogins?: string[]
topContributors?: Contributor[]
topics?: string[]
type: string
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/types/leaders.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
export type LeadersListProps = {
leaders: string
}

// Maps username to certification strings (e.g., { arkid15r: 'CCSP, CISSP, CSSLP' })
export type LeadersListBlockProps = {
[key: string]: string
}
1 change: 1 addition & 0 deletions frontend/src/types/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export type Project = {
key: string
languages: string[]
leaders: string[]
leadersLogins: string[]
level: string
name: string
openIssuesCount?: number
Expand Down