Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 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
17 changes: 13 additions & 4 deletions backend/apps/github/graphql/nodes/milestone.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,33 @@ class MilestoneNode(BaseNode):
"""Github Milestone Node."""

organization_name = graphene.String()
progress = graphene.Float()
repository_name = graphene.String()

class Meta:
model = Milestone

fields = (
"author",
"body",
"created_at",
"title",
"open_issues_count",
"closed_issues_count",
"url",
)

def resolve_repository_name(self, info):
"""Resolve repository name."""
return self.repository.name

def resolve_organization_name(self, info):
"""Return organization name."""
return self.repository.organization.login if self.repository.organization else None

def resolve_progress(self, info):
"""Return milestone progress."""
total_issues_count = self.closed_issues_count + self.open_issues_count
if not total_issues_count:
return 0.0
return round((self.closed_issues_count / total_issues_count) * 100, 2)

def resolve_repository_name(self, info):
"""Resolve repository name."""
return self.repository.name
2 changes: 2 additions & 0 deletions backend/tests/apps/github/graphql/nodes/milestone_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ def test_meta_configuration(self):
assert MilestoneNode._meta.model == Milestone
expected_fields = {
"author",
"body",
"closed_issues_count",
"created_at",
"open_issues_count",
"organization_name",
"progress",
"repository_name",
"title",
"url",
Expand Down
5 changes: 4 additions & 1 deletion frontend/__tests__/e2e/pages/About.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ test.describe('About Page', () => {

test('loads roadmap items correctly', async ({ page }) => {
await expect(page.getByRole('heading', { name: 'Roadmap' })).toBeVisible()
expect(await page.locator('li').count()).toBeGreaterThan(0)
for (const milestone of mockAboutData.project.recentMilestones) {
await expect(page.getByText(milestone.title)).toBeVisible()
await expect(page.getByText(milestone.body)).toBeVisible()
}
})

test('displays animated counters with correct values', async ({ page }) => {
Expand Down
20 changes: 20 additions & 0 deletions frontend/__tests__/unit/data/mockAboutData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,26 @@ export const mockAboutData = {
issuesCount: 40,
forksCount: 60,
starsCount: 890,
recentMilestones: [
{
title: 'NestBot title',
body: 'NestBot Idea',
url: 'http/github.com/milestones/5',
progress: 58,
},
{
title: 'Contribution Hub title',
body: 'Contribution Hub Idea',
url: 'http/github.com/milestones/8',
progress: 75,
},
{
title: 'Project Dashboard title',
body: 'Project Dashboard Idea',
url: 'http/github.com/milestones/10',
progress: 80,
},
],
},
topContributors: Array.from({ length: 15 }, (_, i) => ({
avatarUrl: `https://avatars.githubusercontent.com/avatar${i + 1}.jpg`,
Expand Down
36 changes: 11 additions & 25 deletions frontend/__tests__/unit/pages/About.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,6 @@ jest.mock('utils/aboutData', () => ({
'This is a test paragraph about the project.',
'This is another paragraph about the project history.',
],
roadmap: [
{ title: 'Feature 1', issueLink: 'https://github.com/owasp/test/issues/1' },
{ title: 'Feature 2', issueLink: 'https://github.com/owasp/test/issues/2' },
{ title: 'Feature 3', issueLink: 'https://github.com/owasp/test/issues/3' },
],
technologies: [
{
section: 'Backend',
Expand Down Expand Up @@ -129,13 +124,7 @@ describe('About Component', () => {
return mockTopContributorsData
}
} else if (query === GET_LEADER_DATA) {
if (key === 'arkid15r') {
return mockUserData('arkid15r')
} else if (key === 'kasya') {
return mockUserData('kasya')
} else if (key === 'mamicidal') {
return mockUserData('mamicidal')
}
return mockUserData(key)
}

return { loading: true }
Expand Down Expand Up @@ -183,10 +172,8 @@ describe('About Component', () => {
return mockProjectData
} else if (options?.variables?.key === 'arkid15r') {
return { data: null, loading: false, error: mockError }
} else if (options?.variables?.key === 'kasya') {
return mockUserData('kasya')
} else if (options?.variables?.key === 'mamicidal') {
return mockUserData('mamicidal')
} else if (options?.variables?.key === 'kasya' || options?.variables?.key === 'mamicidal') {
return mockUserData(options?.variables?.key)
}
return { loading: true }
})
Expand Down Expand Up @@ -273,18 +260,17 @@ describe('About Component', () => {

const roadmapSection = screen.getByRole('heading', { name: 'Roadmap' }).closest('div')
expect(roadmapSection).toBeInTheDocument()

const roadmapItems = within(roadmapSection).getAllByRole('listitem')
expect(roadmapItems).toHaveLength(3)

expect(screen.getByText('Feature 1')).toBeInTheDocument()
expect(screen.getByText('Feature 2')).toBeInTheDocument()
expect(screen.getByText('Feature 3')).toBeInTheDocument()

const roadmapData = mockAboutData.project.recentMilestones
const links = within(roadmapSection)
.getAllByRole('link')
.filter((link) => link.getAttribute('href') !== '#roadmap')
expect(links[0].getAttribute('href')).toBe('https://github.com/owasp/test/issues/1')

for (let i = 0; i < roadmapData.length; i++) {
const milestone = roadmapData[i]
expect(screen.getByText(milestone.title)).toBeInTheDocument()
expect(screen.getByText(milestone.body)).toBeInTheDocument()
expect(links[i].getAttribute('href')).toBe(milestone.url)
}
})

test('renders project stats cards correctly', async () => {
Expand Down
64 changes: 50 additions & 14 deletions frontend/src/app/about/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
'use client'
import { useQuery } from '@apollo/client'
import {
faCircleCheck,
faClock,
faUserGear,
faMapSigns,
faScroll,
faUsers,
faTools,
faArrowUpRightFromSquare,
} from '@fortawesome/free-solid-svg-icons'
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'
Expand All @@ -18,7 +23,7 @@ import { GET_LEADER_DATA } from 'server/queries/userQueries'
import { TopContributorsTypeGraphql } from 'types/contributor'
import { ProjectTypeGraphql } from 'types/project'
import { User } from 'types/user'
import { aboutText, roadmap, technologies } from 'utils/aboutData'
import { aboutText, technologies } from 'utils/aboutData'
import AnchorTitle from 'components/AnchorTitle'
import AnimatedCounter from 'components/AnimatedCounter'
import LoadingSpinner from 'components/LoadingSpinner'
Expand Down Expand Up @@ -159,20 +164,51 @@ const About = () => {
</SecondaryCard>

<SecondaryCard icon={faMapSigns} title={<AnchorTitle title="Roadmap" />}>
<ul>
{roadmap.map((item) => (
<li key={item.title} className="mb-4 flex flex-row items-center gap-2 pl-4 md:pl-6">
<div className="h-2 w-2 flex-shrink-0 rounded-full bg-gray-600 dark:bg-gray-300"></div>
<Link
href={item.issueLink}
target="_blank"
className="text-gray-600 hover:underline dark:text-gray-300"
<div className="grid gap-4">
{[...projectMetadata.recentMilestones]
.sort((a, b) => (a.title > b.title ? 1 : -1))
.map((milestone, index) => (
<div
key={index}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
className="flex items-center gap-4 overflow-hidden rounded-lg bg-gray-200 p-6 dark:bg-gray-700"
>
{item.title}
</Link>
</li>
))}
</ul>
<div className="flex-1">
<Link href={milestone.url} target="_blank" className="inline-block">
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
<h3 className="mb-2 text-xl font-semibold text-blue-400">
{milestone.title}
<Tooltip
closeDelay={100}
content={
milestone.progress === 100
? 'Completed'
: milestone.progress > 0
? 'In Progress'
: 'Not Started'
}
id={`level-tooltip-progress`}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
delay={100}
placement="top"
showArrow
>
<span className="ml-4 inline-block text-gray-400">
<FontAwesomeIcon
icon={
milestone.progress === 100
? faCircleCheck
: milestone.progress > 0
? faUserGear
: faClock
}
/>
</span>
</Tooltip>
</h3>
</Link>
<p className="text-gray-600 dark:text-gray-300">{milestone.body}</p>
</div>
</div>
))}
</div>
</SecondaryCard>

<div className="grid gap-6 md:grid-cols-4">
Expand Down
90 changes: 7 additions & 83 deletions frontend/src/app/members/[memberKey]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import Image from 'next/image'
import Link from 'next/link'
import { useParams } from 'next/navigation'
import React, { useState, useEffect, useRef, useMemo } from 'react'
import React, { useState, useEffect, useRef } from 'react'
import { handleAppError, ErrorDisplay } from 'app/global-error'
import { GET_USER_DATA } from 'server/queries/userQueries'
import type {
Expand All @@ -18,7 +18,7 @@ import type {
ProjectReleaseType,
RepositoryCardProps,
} from 'types/project'
import type { ItemCardPullRequests, PullRequestsType, UserDetailsProps } from 'types/user'
import type { ItemCardPullRequests, UserDetailsProps } from 'types/user'
import { formatDate } from 'utils/dateFormatter'
import { drawContributions, fetchHeatmapData, HeatmapData } from 'utils/helpers/githubHeatmap'
import DetailsCard from 'components/CardDetailsPage'
Expand All @@ -30,7 +30,7 @@ const UserDetailsPage: React.FC = () => {
const [issues, setIssues] = useState<ProjectIssuesType[]>([])
const [topRepositories, setTopRepositories] = useState<RepositoryCardProps[]>([])
const [milestones, setMilestones] = useState<ProjectMilestonesType[]>([])
const [pullRequests, setPullRequests] = useState<PullRequestsType[]>([])
const [pullRequests, setPullRequests] = useState<ItemCardPullRequests[]>([])
const [releases, setReleases] = useState<ProjectReleaseType[]>([])
const [data, setData] = useState<HeatmapData>({} as HeatmapData)
const [isLoading, setIsLoading] = useState<boolean>(true)
Expand Down Expand Up @@ -107,82 +107,6 @@ const UserDetailsPage: React.FC = () => {
return <span key={index}>{word} </span>
})

const formattedIssues: ProjectIssuesType[] = useMemo(() => {
return (
issues?.map((issue) => ({
author: {
avatarUrl: user?.avatarUrl || '',
key: user?.login || '',
login: user?.login || '',
name: user?.name || user?.login || '',
},
createdAt: issue.createdAt,
organizationName: issue.organizationName,
repositoryName: issue.repositoryName,
title: issue.title,
url: issue.url,
})) || []
)
}, [user, issues])

const formattedPullRequest: ItemCardPullRequests[] = useMemo(() => {
return (
pullRequests?.map((pullRequest) => ({
author: {
avatarUrl: user?.avatarUrl || '',
key: user?.login || '',
login: user?.login || '',
name: user?.name || user?.login || '',
},
createdAt: pullRequest.createdAt,
organizationName: pullRequest.organizationName,
repositoryName: pullRequest.repositoryName,
title: pullRequest.title,
url: pullRequest.url,
})) || []
)
}, [pullRequests, user])

const formattedReleases: ProjectReleaseType[] = useMemo(() => {
return (
releases?.map((release) => ({
author: {
avatarUrl: user?.avatarUrl || '',
key: user?.login || '',
login: user?.login || '',
name: user?.name || user?.login || '',
},
isPreRelease: release.isPreRelease,
name: release.name,
organizationName: release.organizationName,
publishedAt: release.publishedAt,
repositoryName: release.repositoryName,
tagName: release.tagName,
url: release.url,
})) || []
)
}, [releases, user])

const formattedMilestones: ProjectMilestonesType[] = useMemo(() => {
return (
milestones?.map((milestone) => ({
author: {
avatarUrl: user?.avatarUrl || '',
key: user?.login || '',
login: user?.login || '',
name: user?.name || user?.login || '',
},
createdAt: milestone.createdAt,
openIssuesCount: milestone.openIssuesCount,
closedIssuesCount: milestone.closedIssuesCount,
organizationName: milestone.organizationName,
repositoryName: milestone.repositoryName,
title: milestone.title,
url: milestone.url,
})) || []
)
}, [milestones, user])

if (isLoading) {
return <LoadingSpinner />
}
Expand Down Expand Up @@ -270,10 +194,10 @@ const UserDetailsPage: React.FC = () => {
<DetailsCard
details={userDetails}
heatmap={isPrivateContributor ? undefined : <Heatmap />}
pullRequests={formattedPullRequest}
recentIssues={formattedIssues}
recentMilestones={formattedMilestones}
recentReleases={formattedReleases}
pullRequests={pullRequests}
recentIssues={issues}
recentMilestones={milestones}
recentReleases={releases}
repositories={topRepositories}
showAvatar={false}
stats={userStats}
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/server/queries/projectQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@ export const GET_PROJECT_METADATA = gql`
name
starsCount
summary
recentMilestones {
title
url
body
progress
}
}
}
`
Expand Down
Loading