Skip to content
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
5 changes: 5 additions & 0 deletions backend/apps/github/graphql/nodes/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
class IssueNode(BaseNode):
"""GitHub issue node."""

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

class Meta:
Expand All @@ -21,6 +22,10 @@ class Meta:
"url",
)

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

def resolve_repository_name(self, info):
"""Resolve the repository name."""
return self.repository.name
5 changes: 5 additions & 0 deletions backend/apps/github/graphql/nodes/pull_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
class PullRequestNode(BaseNode):
"""GitHub pull request node."""

organization_name = graphene.String()
repository_name = graphene.String()
url = graphene.String()

Expand All @@ -20,6 +21,10 @@ class Meta:
"title",
)

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

def resolve_repository_name(self, info):
"""Resolve repository name."""
return self.repository.name
Expand Down
5 changes: 5 additions & 0 deletions backend/apps/github/graphql/nodes/release.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class ReleaseNode(BaseNode):
"""GitHub release node."""

author = graphene.Field(UserNode)
organization_name = graphene.String()
project_name = graphene.String()
repository_name = graphene.String()
url = graphene.String()
Expand All @@ -26,6 +27,10 @@ class Meta:
"tag_name",
)

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

def resolve_project_name(self, info):
"""Return project name."""
return self.repository.project.name.lstrip(OWASP_ORGANIZATION_NAME)
Expand Down
1 change: 1 addition & 0 deletions backend/apps/github/graphql/nodes/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class Meta:
"license",
"name",
"open_issues_count",
"organization",
"size",
"stars_count",
"subscribers_count",
Expand Down
11 changes: 8 additions & 3 deletions backend/apps/github/graphql/queries/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class RepositoryQuery(BaseQuery):

repository = graphene.Field(
RepositoryNode,
organization_key=graphene.String(required=True),
repository_key=graphene.String(required=True),
)

Expand All @@ -21,20 +22,24 @@ class RepositoryQuery(BaseQuery):
limit=graphene.Int(default_value=12),
)

def resolve_repository(root, info, repository_key):
def resolve_repository(root, info, organization_key, repository_key):
"""Resolve repository by key.

Args:
root (Any): The root query object.
info (ResolveInfo): The GraphQL execution context.
organization_key (str): The login of the organization.
repository_key (str): The unique key of the repository.

Returns:
Repository or None: The repository object if found, otherwise None.

"""
try:
return Repository.objects.get(key=repository_key)
return Repository.objects.select_related("organization").get(
key__iexact=repository_key,
organization__login__iexact=organization_key,
)
except Repository.DoesNotExist:
return None

Expand All @@ -56,7 +61,7 @@ def resolve_repositories(root, info, organization, limit):
"organization",
)
.filter(
organization__login=organization,
organization__login__iexact=organization,
)
.order_by("-stars_count")[:limit]
)
1 change: 1 addition & 0 deletions backend/tests/apps/github/graphql/nodes/issue_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def test_meta_configuration(self):
expected_fields = {
"author",
"created_at",
"organization_name",
"repository_name",
"state",
"title",
Expand Down
1 change: 1 addition & 0 deletions backend/tests/apps/github/graphql/nodes/release_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def test_meta_configuration(self):
"author",
"is_pre_release",
"name",
"organization_name",
"project_name",
"published_at",
"repository_name",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def test_meta_configuration(self):
"license",
"name",
"open_issues_count",
"organization",
"owner_key",
"latest_release",
"releases",
Expand Down
40 changes: 31 additions & 9 deletions backend/tests/apps/github/graphql/queries/repository_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Test cases for RepositoryQuery."""

from unittest.mock import Mock, patch
from unittest.mock import MagicMock, Mock, patch

import pytest

Expand All @@ -23,24 +23,46 @@ def mock_repository(self):

def test_resolve_repository_existing(self, mock_repository, mock_info):
"""Test resolving an existing repository."""
with patch("apps.github.models.repository.Repository.objects.get") as mock_get:
mock_get.return_value = mock_repository
mock_queryset = MagicMock()
mock_queryset.get.return_value = mock_repository

with patch(
"apps.github.models.repository.Repository.objects.select_related",
return_value=mock_queryset,
) as mock_select_related:
result = RepositoryQuery.resolve_repository(
None, mock_info, repository_key="test-repo"
None,
mock_info,
organization_key="test-org",
repository_key="test-repo",
)

assert result == mock_repository
mock_get.assert_called_once_with(key="test-repo")
mock_select_related.assert_called_once_with("organization")
mock_queryset.get.assert_called_once_with(
organization__login__iexact="test-org",
key__iexact="test-repo",
)

def test_resolve_repository_not_found(self, mock_info):
"""Test resolving a non-existent repository."""
with patch("apps.github.models.repository.Repository.objects.get") as mock_get:
mock_get.side_effect = Repository.DoesNotExist
mock_queryset = MagicMock()
mock_queryset.get.side_effect = Repository.DoesNotExist

with patch(
"apps.github.models.repository.Repository.objects.select_related",
return_value=mock_queryset,
) as mock_select_related:
result = RepositoryQuery.resolve_repository(
None, mock_info, repository_key="non-existent-repo"
None,
mock_info,
organization_key="non-existent-org",
repository_key="non-existent-repo",
)

assert result is None
mock_get.assert_called_once_with(key="non-existent-repo")
mock_select_related.assert_called_once_with("organization")
mock_queryset.get.assert_called_once_with(
organization__login__iexact="non-existent-org",
key__iexact="non-existent-repo",
)
2 changes: 1 addition & 1 deletion frontend/__tests__/e2e/pages/ProjectDetails.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,6 @@ test.describe('Project Details Page', () => {
await expect(page.getByText('Issues3', { exact: true })).toBeVisible()

await page.getByText('Repo One').click()
await expect(page).toHaveURL('repositories/repo-1')
await expect(page).toHaveURL('organizations/OWASP/repositories/repo-1')
})
})
2 changes: 1 addition & 1 deletion frontend/__tests__/e2e/pages/RepositoryDetails.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ test.describe('Repository Details Page', () => {
path: '/',
},
])
await page.goto('/repositories/test-repository')
await page.goto('organizations/OWASP/repositories/test-repository')
})

test('should have a heading and summary', async ({ page }) => {
Expand Down
6 changes: 6 additions & 0 deletions frontend/__tests__/unit/data/mockProjectDetailsData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ export const mockProjectDetailsData = {
key: 'repo-1',
name: 'Repo One',
openIssuesCount: 6,
organization: {
login: 'OWASP',
},
starsCount: 95,
subscribersCount: 15,
url: 'https://github.com/example-project/repo-1',
Expand All @@ -65,6 +68,9 @@ export const mockProjectDetailsData = {
key: 'repo-2',
name: 'Repo Two',
openIssuesCount: 3,
organization: {
login: 'OWASP',
},
starsCount: 60,
subscribersCount: 10,
url: 'https://github.com/example-project/repo-2',
Expand Down
2 changes: 1 addition & 1 deletion frontend/__tests__/unit/pages/RepositoryDetails.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { addToast } from '@heroui/toast'
import { act, fireEvent, screen, waitFor, within } from '@testing-library/react'
import { mockRepositoryData } from '@unit/data/mockRepositoryData'
import { render } from 'wrappers/testUtil'
import RepositoryDetailsPage from 'app/repositories/[repositoryKey]/page'
import RepositoryDetailsPage from 'app/organizations/[organizationKey]/repositories/[repositoryKey]/page'

jest.mock('@apollo/client', () => ({
...jest.requireActual('@apollo/client'),
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/app/members/[memberKey]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ const UserDetailsPage: React.FC = () => {
name: user?.name || user?.login || '',
},
createdAt: issue.createdAt,
organizationName: issue.organizationName,
repositoryName: issue.repositoryName,
title: issue.title,
url: issue.url,
Expand All @@ -129,6 +130,7 @@ const UserDetailsPage: React.FC = () => {
name: user?.name || user?.login || '',
},
createdAt: pullRequest.createdAt,
organizationName: pullRequest.organizationName,
repositoryName: pullRequest.repositoryName,
title: pullRequest.title,
url: pullRequest.url,
Expand All @@ -147,6 +149,7 @@ const UserDetailsPage: React.FC = () => {
},
isPreRelease: release.isPreRelease,
name: release.name,
organizationName: release.organizationName,
publishedAt: release.publishedAt,
repositoryName: release.repositoryName,
tagName: release.tagName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ import LoadingSpinner from 'components/LoadingSpinner'
import { handleAppError, ErrorDisplay } from 'app/global-error'

const RepositoryDetailsPage = () => {
const { repositoryKey } = useParams()
const { repositoryKey, organizationKey } = useParams()
const [repository, setRepository] = useState(null)
const [isLoading, setIsLoading] = useState<boolean>(true)
const { data, error: graphQLRequestError } = useQuery(GET_REPOSITORY_DATA, {
variables: { repositoryKey: repositoryKey },
variables: { repositoryKey: repositoryKey, organizationKey: organizationKey },
})
useEffect(() => {
if (data) {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/ItemCardList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const ItemCardList = ({
renderDetails: (item: {
createdAt: string
commentsCount: number
organizationName: string
publishedAt: string
repositoryName: string
tagName: string
Expand Down
18 changes: 12 additions & 6 deletions frontend/src/components/RecentIssues.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { faCalendar, faFileCode, faCircleExclamation } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import React from 'react'
import { ProjectIssuesType } from 'types/project'
import { formatDate } from 'utils/dateFormatter'
Expand All @@ -12,6 +12,8 @@ interface RecentIssuesProps {
}

const RecentIssues: React.FC<RecentIssuesProps> = ({ data, showAvatar = true }) => {
const router = useRouter()

return (
<ItemCardList
title="Recent Issues"
Expand All @@ -27,12 +29,16 @@ const RecentIssues: React.FC<RecentIssuesProps> = ({ data, showAvatar = true })
{item?.repositoryName && (
<div className="item-center flex">
<FontAwesomeIcon icon={faFileCode} className="ml-4 mr-2 h-4 w-4" />
<Link
className="text-gray-600 hover:underline dark:text-gray-400"
href={`/repositories/${item?.repositoryName ? item.repositoryName.toLowerCase() : ''}`}
<button
className="cursor-pointer text-gray-600 hover:underline dark:text-gray-400"
onClick={() =>
router.push(
`/organizations/${item.organizationName}/repositories/${item.repositoryName || ''}`
)
}
>
<span>{item.repositoryName}</span>
</Link>{' '}
{item.repositoryName}
</button>
</div>
)}
</div>
Expand Down
18 changes: 12 additions & 6 deletions frontend/src/components/RecentPullRequests.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { faCalendar, faCodePullRequest, faFileCode } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import React from 'react'
import { PullRequestsType } from 'types/home'
import { ItemCardPullRequests } from 'types/user'
Expand All @@ -13,6 +13,8 @@ interface RecentPullRequestsProps {
}

const RecentPullRequests: React.FC<RecentPullRequestsProps> = ({ data, showAvatar = true }) => {
const router = useRouter()

return (
<ItemCardList
title="Recent Pull Requests"
Expand All @@ -29,12 +31,16 @@ const RecentPullRequests: React.FC<RecentPullRequestsProps> = ({ data, showAvata
{item?.repositoryName && (
<div className="item-center flex">
<FontAwesomeIcon icon={faFileCode} className="ml-4 mr-2 h-4 w-4" />
<Link
className="text-gray-600 hover:underline dark:text-gray-400"
href={`/repositories/${item?.repositoryName ? item.repositoryName.toLowerCase() : ''}`}
<button
className="cursor-pointer text-gray-600 hover:underline dark:text-gray-400"
onClick={() =>
router.push(
`/organizations/${item.organizationName}/repositories/${item.repositoryName || ''}`
)
}
>
<span>{item.repositoryName}</span>
</Link>{' '}
{item.repositoryName}
</button>
</div>
)}
</div>
Expand Down
Loading