diff --git a/backend/apps/github/graphql/nodes/pull_request.py b/backend/apps/github/graphql/nodes/pull_request.py index f2819e6c02..2ce4223652 100644 --- a/backend/apps/github/graphql/nodes/pull_request.py +++ b/backend/apps/github/graphql/nodes/pull_request.py @@ -14,6 +14,7 @@ class PullRequestNode(BaseNode): class Meta: model = PullRequest fields = ( + "author", "created_at", "title", ) diff --git a/backend/apps/github/graphql/queries/issue.py b/backend/apps/github/graphql/queries/issue.py index 606ebd204e..3379fb8747 100644 --- a/backend/apps/github/graphql/queries/issue.py +++ b/backend/apps/github/graphql/queries/issue.py @@ -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 @@ -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] diff --git a/backend/apps/github/graphql/queries/pull_request.py b/backend/apps/github/graphql/queries/pull_request.py index ec60d99dd0..19c01d1e0e 100644 --- a/backend/apps/github/graphql/queries/pull_request.py +++ b/backend/apps/github/graphql/queries/pull_request.py @@ -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 @@ -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] diff --git a/backend/apps/github/graphql/queries/release.py b/backend/apps/github/graphql/queries/release.py index 56cc85a26f..7be89a1d90 100644 --- a/backend/apps/github/graphql/queries/release.py +++ b/backend/apps/github/graphql/queries/release.py @@ -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 @@ -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] diff --git a/frontend/__tests__/unit/data/mockHomeData.ts b/frontend/__tests__/unit/data/mockHomeData.ts index ec62a67334..ca816da480 100644 --- a/frontend/__tests__/unit/data/mockHomeData.ts +++ b/frontend/__tests__/unit/data/mockHomeData.ts @@ -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 = { diff --git a/frontend/__tests__/unit/pages/Home.test.tsx b/frontend/__tests__/unit/pages/Home.test.tsx index d5dfbabec2..4e72fc16f9 100644 --- a/frontend/__tests__/unit/pages/Home.test.tsx +++ b/frontend/__tests__/unit/pages/Home.test.tsx @@ -166,4 +166,18 @@ describe('Home', () => { }) }) }) + + test('renders Recent Pull Requests section', async () => { + render() + + 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() + }) + }) + }) }) diff --git a/frontend/src/api/queries/homeQueries.ts b/frontend/src/api/queries/homeQueries.ts index 2d276edbf6..cd9ac60500 100644 --- a/frontend/src/api/queries/homeQueries.ts +++ b/frontend/src/api/queries/homeQueries.ts @@ -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 diff --git a/frontend/src/components/ItemCardList.tsx b/frontend/src/components/ItemCardList.tsx index c4fd6ef3c6..2a6dd17f79 100644 --- a/frontend/src/components/ItemCardList.tsx +++ b/frontend/src/components/ItemCardList.tsx @@ -18,6 +18,11 @@ const ItemCardList = ({ commentsCount: number publishedAt: string tagName: string + author: { + avatarUrl: string + login: string + name: string + } }) => JSX.Element }) => ( diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index 6e4cbbb5ce..16a142fd29 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -272,18 +272,69 @@ export default function Home() { )} /> ( - {formatDate(item.publishedAt)} - - {item.tagName} + {formatDate(item.createdAt)} + {item?.author.name || item?.author.login ? ( + <> + + {item.author.name || item.author.login} + > + ) : null} )} /> + + {data.recentReleases && data.recentReleases.length > 0 ? ( + + {data.recentReleases.map((item, index) => ( + + + + + + + + + + {item.name} + + + + + + + {formatDate(item.publishedAt)} + + {item.tagName} + + + + + ))} + + ) : ( + No recent releases. + )} + {data.recentPosts.map((post) => ( diff --git a/frontend/src/types/home.ts b/frontend/src/types/home.ts index 65eb4db63a..8664b9dedb 100644 --- a/frontend/src/types/home.ts +++ b/frontend/src/types/home.ts @@ -7,6 +7,7 @@ export type MainPageData = { recentIssues: ProjectIssuesType[] recentReleases: ProjectReleaseType[] upcomingEvents: EventType[] + recentPullRequests: PullRequestsType[] recentChapters: { createdAt: string key: string @@ -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 +}
No recent releases.