Skip to content

Updated user details page #1176

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 29, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions backend/apps/github/graphql/nodes/pull_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class PullRequestNode(BaseNode):
class Meta:
model = PullRequest
fields = (
"author",
"created_at",
"title",
)
Expand Down
12 changes: 8 additions & 4 deletions backend/apps/github/graphql/queries/issue.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""GraphQL queries for handling GitHub issues."""

import graphene
from django.db.models import OuterRef, Subquery

from apps.common.graphql.queries import BaseQuery
from apps.github.graphql.nodes.issue import IssueNode
Expand Down Expand Up @@ -43,12 +44,15 @@ def resolve_recent_issues(root, info, limit=15, distinct=False, login=None):
queryset = queryset.filter(author__login=login)

if distinct:
queryset = queryset.distinct(
"author_id",
"created_at",
latest_issue_per_author = (
queryset.filter(author_id=OuterRef("author_id"))
.order_by("-created_at")
.values("id")[:1]
)
queryset = queryset.filter(
id__in=Subquery(latest_issue_per_author),
).order_by(
"-created_at",
"author_id",
)

return queryset[:limit]
23 changes: 21 additions & 2 deletions backend/apps/github/graphql/queries/pull_request.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Github pull requests GraphQL queries."""

import graphene
from django.db.models import OuterRef, Subquery

from apps.common.graphql.queries import BaseQuery
from apps.github.graphql.nodes.pull_request import PullRequestNode
Expand All @@ -13,13 +14,31 @@ class PullRequestQuery(BaseQuery):
recent_pull_requests = graphene.List(
PullRequestNode,
limit=graphene.Int(default_value=6),
distinct=graphene.Boolean(default_value=False),
login=graphene.String(required=False),
)

def resolve_recent_pull_requests(root, info, limit, login=None):
def resolve_recent_pull_requests(root, info, limit, distinct=False, login=None):
"""Resolve recent pull requests."""
queryset = PullRequest.objects.select_related("author").order_by("-created_at")
queryset = PullRequest.objects.select_related(
"author",
).order_by(
"-created_at",
)

if login:
queryset = queryset.filter(author__login=login)

if distinct:
latest_pull_request_per_author = (
queryset.filter(author_id=OuterRef("author_id"))
.order_by("-created_at")
.values("id")[:1]
)
queryset = queryset.filter(
id__in=Subquery(latest_pull_request_per_author),
).order_by(
"-created_at",
)

return queryset[:limit]
15 changes: 10 additions & 5 deletions backend/apps/github/graphql/queries/release.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""GraphQL queries for handling OWASP releases."""

import graphene
from django.db.models import OuterRef, Subquery

from apps.common.graphql.queries import BaseQuery
from apps.github.graphql.nodes.release import ReleaseNode
Expand Down Expand Up @@ -31,18 +32,22 @@ def resolve_recent_releases(root, info, limit=15, distinct=False):
Queryset containing the filtered list of releases.

"""
query = Release.objects.filter(
queryset = Release.objects.filter(
is_draft=False,
is_pre_release=False,
published_at__isnull=False,
).order_by("-published_at")

if distinct:
query = query.distinct(
"author_id",
"published_at",
latest_release_per_author = (
queryset.filter(author_id=OuterRef("author_id"))
.order_by("-published_at")
.values("id")[:1]
)
queryset = queryset.filter(
id__in=Subquery(latest_release_per_author),
).order_by(
"-published_at",
)

return query[:limit]
return queryset[:limit]
20 changes: 20 additions & 0 deletions frontend/__tests__/unit/data/mockHomeData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,26 @@ export const mockGraphQLData = {
url: 'https://nest.owasp.org/events/event-1',
},
],
recentPullRequests: [
{
createdAt: '2025-03-25T10:00:00Z',
title: 'Fix authentication bug',
author: {
name: 'John Doe',
avatarUrl: 'https://avatars.githubusercontent.com/u/58754215?v=4',
},
url: 'https://github.com/example/repo/pull/1',
},
{
createdAt: '2025-03-24T15:30:00Z',
title: 'Add new feature',
author: {
login: 'jane-smith',
avatarUrl: 'https://avatars.githubusercontent.com/u/58754221?v=4',
},
url: 'https://github.com/example/repo/pull/2',
},
],
}

export const mockAlgoliaData = {
Expand Down
14 changes: 14 additions & 0 deletions frontend/__tests__/unit/pages/Home.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,4 +166,18 @@ describe('Home', () => {
})
})
})

test('renders Recent Pull Requests section', async () => {
render(<Home />)

await waitFor(() => {
expect(screen.getByText('Recent Pull Requests')).toBeInTheDocument()
mockGraphQLData.recentPullRequests.forEach((pullRequest) => {
expect(screen.getByText(pullRequest.title)).toBeInTheDocument()
expect(
screen.getByText(pullRequest.author.name || pullRequest.author.login)
).toBeInTheDocument()
})
})
})
})
10 changes: 10 additions & 0 deletions frontend/src/api/queries/homeQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ export const GET_MAIN_PAGE_DATA = gql`
name
}
}
recentPullRequests(limit: 5, distinct: $distinct) {
author {
avatarUrl
login
name
}
createdAt
title
url
}
recentReleases(limit: 5, distinct: $distinct) {
author {
avatarUrl
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/components/ItemCardList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ const ItemCardList = ({
commentsCount: number
publishedAt: string
tagName: string
author: {
avatarUrl: string
login: string
name: string
}
}) => JSX.Element
}) => (
<SecondaryCard title={title}>
Expand Down
61 changes: 56 additions & 5 deletions frontend/src/pages/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -272,18 +272,69 @@ export default function Home() {
)}
/>
<ItemCardList
title="Recent Releases"
data={data.recentReleases}
title="Recent Pull Requests"
data={data.recentPullRequests}
renderDetails={(item) => (
<div className="mt-2 flex items-center text-sm text-gray-600 dark:text-gray-400">
<FontAwesomeIcon icon={faCalendar} className="mr-2 h-4 w-4" />
<span>{formatDate(item.publishedAt)}</span>
<FontAwesomeIcon icon={faTag} className="ml-4 mr-2 h-4 w-4" />
<span>{item.tagName}</span>
<span>{formatDate(item.createdAt)}</span>
{item?.author.name || item?.author.login ? (
<>
<FontAwesomeIcon icon={faUser} className="ml-4 mr-2 h-4 w-4" />
<span>{item.author.name || item.author.login}</span>
</>
) : null}
</div>
)}
/>
</div>
<SecondaryCard title="Recent Releases">
{data.recentReleases && data.recentReleases.length > 0 ? (
<div className="grid gap-4 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
{data.recentReleases.map((item, index) => (
<div
key={index}
className="mb-4 w-full rounded-lg bg-gray-200 p-4 dark:bg-gray-700"
>
<div className="flex w-full flex-col justify-between">
<div className="flex w-full items-center">
<a
className="flex-shrink-0 text-blue-400 hover:underline dark:text-blue-200"
href={`/community/users/${item?.author?.login}`}
>
<img
src={item?.author?.avatarUrl}
alt={item?.author?.name}
className="mr-2 h-6 w-6 rounded-full"
/>
</a>

<h3 className="flex-1 overflow-hidden text-ellipsis whitespace-nowrap font-semibold">
<a
className="text-blue-500 hover:underline dark:text-blue-400"
href={item?.url}
target="_blank"
>
{item.name}
</a>
</h3>
</div>
<div className="ml-0.5 w-full">
<div className="mt-2 flex items-center text-sm text-gray-600 dark:text-gray-400">
<FontAwesomeIcon icon={faCalendar} className="mr-2 h-4 w-4" />
<span>{formatDate(item.publishedAt)}</span>
<FontAwesomeIcon icon={faTag} className="ml-4 mr-2 h-4 w-4" />
<span>{item.tagName}</span>
</div>
</div>
</div>
</div>
))}
</div>
) : (
<p>No recent releases.</p>
)}
</SecondaryCard>
<SecondaryCard title="Recent News & Opinions" className="overflow-hidden">
<div className="grid gap-4 sm:grid-cols-1 md:grid-cols-2">
{data.recentPosts.map((post) => (
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/types/home.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type MainPageData = {
recentIssues: ProjectIssuesType[]
recentReleases: ProjectReleaseType[]
upcomingEvents: EventType[]
recentPullRequests: PullRequestsType[]
recentChapters: {
createdAt: string
key: string
Expand Down Expand Up @@ -45,3 +46,14 @@ export type SponsorType = {
sponsorType: string
url: string
}

export type PullRequestsType = {
author: {
avatarUrl: string
login: string
name: string
}
createdAt: string
title: string
url: string
}