Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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/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 @@ -13,6 +13,7 @@ class RepositoryQuery(BaseQuery):
repository = graphene.Field(
RepositoryNode,
repository_key=graphene.String(required=True),
organization_key=graphene.String(required=True),
)

repositories = graphene.List(
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]
)
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
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
2 changes: 1 addition & 1 deletion frontend/src/components/RecentIssues.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const RecentIssues: React.FC<RecentIssuesProps> = ({ data, showAvatar = true })
<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() : ''}`}
href={`/repositories/${item?.repositoryName ? item.repositoryName : ''}`}
>
<span>{item.repositoryName}</span>
</Link>{' '}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/RecentPullRequests.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const RecentPullRequests: React.FC<RecentPullRequestsProps> = ({ data, showAvata
<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() : ''}`}
href={`/repositories/${item?.repositoryName ? item.repositoryName : ''}`}
>
<span>{item.repositoryName}</span>
</Link>{' '}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/RecentReleases.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ const RecentReleases: React.FC<RecentReleasesProps> = ({
<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() : ''}`}
href={`/repositories/${item?.repositoryName ? item.repositoryName : ''}`}
>
<span>{item.repositoryName}</span>
</Link>
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/components/RepositoriesCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@ const RepositoriesCard: React.FC<RepositoriesCardProps> = ({ repositories }) =>
const RepositoryItem = ({ details }: { details: RepositoryCardProps }) => {
const router = useRouter()
const handleClick = () => {
router.push('/repositories/' + details?.key)
router.push(`/organizations/${details.organization.login}/repositories/${details.key}`)
}

return (
<div className="h-46 flex w-full flex-col gap-3 rounded-lg border p-4 shadow-sm ease-in-out hover:shadow-md dark:border-gray-700 dark:bg-gray-800">
<button
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/server/queries/organizationQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ export const GET_ORGANIZATION_DATA = gql`
key
name
openIssuesCount
organization {
login
}
starsCount
url
}
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/server/queries/projectQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ export const GET_PROJECT_DATA = gql`
key
name
openIssuesCount
organization {
login
}
starsCount
subscribersCount
url
Expand Down
7 changes: 5 additions & 2 deletions frontend/src/server/queries/repositoryQueries.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { gql } from '@apollo/client'

export const GET_REPOSITORY_DATA = gql`
query GetRepository($repositoryKey: String!) {
repository(repositoryKey: $repositoryKey) {
query GetRepository($repositoryKey: String!, $organizationKey: String!) {
repository(repositoryKey: $repositoryKey, organizationKey: $organizationKey) {
commitsCount
contributorsCount
createdAt
Expand All @@ -23,6 +23,9 @@ export const GET_REPOSITORY_DATA = gql`
license
name
openIssuesCount
organization {
login
}
releases {
author {
avatarUrl
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/server/queries/userQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export const GET_USER_DATA = gql`
key
name
openIssuesCount
organization {
login
}
starsCount
subscribersCount
url
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/types/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ export interface RepositoryCardProps {
key?: string
name: string
openIssuesCount: number
organization?: {
login: string
}
starsCount: number
subscribersCount: number
url: string
Expand Down