Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d88ac24
Add contributions_count field to User model and update related logic.
ahmedxgouda Mar 29, 2025
64f8a2c
Refactor User model to calculate contributions_count during data upda…
ahmedxgouda Mar 30, 2025
b893bfc
Add contributionsCount to user queries and update UserDetails page.
ahmedxgouda Mar 30, 2025
68effca
Merge branch 'main' into feature/extend-user-model
ahmedxgouda Mar 30, 2025
ad1fe32
Update backend tests.
ahmedxgouda Mar 30, 2025
798dbf5
Update frontend tests.
ahmedxgouda Mar 30, 2025
c6e5a38
Merge branch 'origin/main' into feature/extend-user-model
ahmedxgouda Mar 31, 2025
7f037e9
Resolve conflicts
ahmedxgouda Mar 31, 2025
2ecf778
Merge rbranch 'origin/main' into feature/extend-user-model
ahmedxgouda Mar 31, 2025
afd46dd
Merge branch 'main' into feature/extend-user-model
ahmedxgouda Mar 31, 2025
50e6cb4
Merge branch 'main' into feature/extend-user-model
ahmedxgouda Mar 31, 2025
e0e9875
Merge branch 'main' into feature/extend-user-model
ahmedxgouda Apr 4, 2025
69b042d
Merge branch 'main' into feature/extend-user-model
ahmedxgouda Apr 5, 2025
1dc1932
Merge branch 'main' into feature/extend-user-model
ahmedxgouda Apr 6, 2025
ac783a3
Merge branch 'main' into feature/extend-user-model
ahmedxgouda Apr 7, 2025
0bb56d3
Add contributions_count field to user model in migrations
ahmedxgouda Apr 7, 2025
9441988
Refactor migration file for user contributions count to use consisten…
ahmedxgouda Apr 7, 2025
28de9e5
Merge branch 'main' into feature/extend-user-model
ahmedxgouda Apr 9, 2025
e94b231
Resolve conflicts
ahmedxgouda Apr 17, 2025
0e67617
Merge migrations
ahmedxgouda Apr 17, 2025
8ef9c6e
Refactor migration dependencies formatting
ahmedxgouda Apr 17, 2025
abbc3bb
Merge branch 'main' into feature/extend-user-model
ahmedxgouda Apr 17, 2025
6515c19
Merge branch 'main' into pr/ahmedxgouda/1209
arkid15r Apr 20, 2025
66c1723
Update code
arkid15r Apr 20, 2025
e087f58
Optimize code
arkid15r Apr 20, 2025
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
6 changes: 6 additions & 0 deletions backend/apps/github/graphql/nodes/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class UserNode(BaseNode):
releases_count = graphene.Int()
updated_at = graphene.Float()
url = graphene.String()
contributions_count = graphene.Int()

class Meta:
model = User
Expand All @@ -59,6 +60,7 @@ class Meta:
"login",
"name",
"public_repositories_count",
"contributions_count",
)

def resolve_created_at(self, info):
Expand Down Expand Up @@ -88,3 +90,7 @@ def resolve_updated_at(self, info):
def resolve_url(self, info):
"""Resolve URL."""
return self.url

def resolve_contributions_count(self, info):
"""Resolve contributions count."""
return self.idx_contributions_count
17 changes: 17 additions & 0 deletions backend/apps/github/migrations/0020_user_contributions_count.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 5.1.7 on 2025-03-29 04:43

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("github", "0019_issue_github_issu_created_4afbcc_idx_and_more"),
]

operations = [
migrations.AddField(
model_name="user",
name="contributions_count",
field=models.IntegerField(default=0, verbose_name="Contributions count"),
),
]
11 changes: 1 addition & 10 deletions backend/apps/github/models/mixins/user.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
"""GitHub user model mixins for index-related functionality."""

from django.db.models import Sum

ISSUES_LIMIT = 6
RELEASES_LIMIT = 6
TOP_REPOSITORY_CONTRIBUTORS_LIMIT = 6
Expand Down Expand Up @@ -108,14 +106,7 @@ def idx_contributions(self):
@property
def idx_contributions_count(self):
"""Return contributions count for indexing."""
from apps.github.models.repository_contributor import RepositoryContributor

return (
RepositoryContributor.objects.by_humans()
.filter(user=self)
.aggregate(total_contributions=Sum("contributions_count"))["total_contributions"]
or 0
)
return self.contributions_count

@property
def idx_issues(self):
Expand Down
20 changes: 20 additions & 0 deletions backend/apps/github/models/user.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Github app user model."""

from django.db import models
from django.db.models import Sum

from apps.common.models import TimestampedModel
from apps.github.constants import GITHUB_GHOST_USER_LOGIN, OWASP_FOUNDATION_LOGIN
Expand All @@ -22,6 +23,8 @@ class Meta:

is_bot = models.BooleanField(verbose_name="Is bot", default=False)

contributions_count = models.IntegerField(verbose_name="Contributions count", default=0)

def __str__(self):
"""User human readable representation."""
return f"{self.name or self.login}"
Expand Down Expand Up @@ -67,13 +70,30 @@ def get_non_indexable_logins():
def update_data(gh_user, save=True):
"""Update GitHub user data."""
user_node_id = User.get_node_id(gh_user)
# We will use this because we will do filtering.
# If we tried to filter a user that is created but not saved yet,
# it would raise an exception.
does_exist = False
try:
user = User.objects.get(node_id=user_node_id)
does_exist = True
except User.DoesNotExist:
user = User(node_id=user_node_id)

user.from_github(gh_user)
if save:
if does_exist:
from apps.github.models.repository_contributor import RepositoryContributor

contributions_count = (
RepositoryContributor.objects.by_humans()
.filter(user=user)
.aggregate(total_contributions=Sum("contributions_count"))[
"total_contributions"
]
or 0
)
user.contributions_count = contributions_count
user.save()

return user
1 change: 1 addition & 0 deletions backend/tests/apps/github/graphql/nodes/user_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,6 @@ def test_meta_configuration(self):
"releases",
"updated_at",
"url",
"contributions_count",
}
assert set(UserNode._meta.fields) == expected_fields
3 changes: 2 additions & 1 deletion backend/tests/apps/github/models/user_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ def test_from_github(self):
assert user.is_hireable
assert user.twitter_username == "twitter"

@patch("apps.github.models.repository_contributor.RepositoryContributor.objects")
@patch("apps.github.models.user.User.objects.get")
@patch("apps.github.models.user.User.save")
def test_update_data(self, mock_save, mock_get):
def test_update_data(self, mock_save, mock_get, mock_repo_contributor):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Test should utilize the mock_repo_contributor parameter.

While you've correctly added the parameter corresponding to the new mock, the test doesn't actually use this mock anywhere in its implementation. For proper test coverage of the new contribution counting functionality, you should:

  1. Set up appropriate return values for the mock
  2. Add assertions that verify the contributions_count is updated correctly
def test_update_data(self, mock_save, mock_get, mock_repo_contributor):
    gh_user_mock = Mock()
    gh_user_mock.raw_data = {"node_id": "12345"}
    gh_user_mock.bio = "Bio"
    gh_user_mock.hireable = True
    gh_user_mock.twitter_username = "twitter"

    mock_user = Mock(spec=User)
    mock_user.node_id = "12345"
    mock_get.return_value = mock_user

+   # Set up mock for repository contributor
+   mock_repo_contributor.filter.return_value.count.return_value = 5

    updated_user = User.update_data(gh_user_mock, save=True)

    mock_get.assert_called_once_with(node_id="12345")
    assert updated_user.node_id == "12345"
+   # Verify the contributions count is updated
+   mock_repo_contributor.filter.assert_called_once_with(user=mock_user)
+   assert updated_user.contributions_count == 5
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_update_data(self, mock_save, mock_get, mock_repo_contributor):
def test_update_data(self, mock_save, mock_get, mock_repo_contributor):
gh_user_mock = Mock()
gh_user_mock.raw_data = {"node_id": "12345"}
gh_user_mock.bio = "Bio"
gh_user_mock.hireable = True
gh_user_mock.twitter_username = "twitter"
mock_user = Mock(spec=User)
mock_user.node_id = "12345"
mock_get.return_value = mock_user
# Set up mock for repository contributor
mock_repo_contributor.filter.return_value.count.return_value = 5
updated_user = User.update_data(gh_user_mock, save=True)
mock_get.assert_called_once_with(node_id="12345")
assert updated_user.node_id == "12345"
# Verify the contributions count is updated
mock_repo_contributor.filter.assert_called_once_with(user=mock_user)
assert updated_user.contributions_count == 5

gh_user_mock = Mock()
gh_user_mock.raw_data = {"node_id": "12345"}
gh_user_mock.bio = "Bio"
Expand Down
1 change: 1 addition & 0 deletions frontend/__tests__/e2e/pages/UserDetails.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ test.describe('User Details Page', () => {
await expect(page.getByText('10 Followers')).toBeVisible()
await expect(page.getByText('5 Following')).toBeVisible()
await expect(page.getByText('3 Repositories')).toBeVisible()
await expect(page.getByText('100 Contributions')).toBeVisible()
})

test('should have user issues', async ({ page }) => {
Expand Down
1 change: 1 addition & 0 deletions frontend/__tests__/unit/data/mockUserDetails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const mockUserDetailsData = {
followingCount: 5,
publicRepositoriesCount: 3,
createdAt: 1723002473,
contributionsCount: 100,
issues: [
{
title: 'Test Issue',
Expand Down
3 changes: 3 additions & 0 deletions frontend/__tests__/unit/pages/UserDetails.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,9 @@ describe('UserDetailsPage', () => {

const repositoriesCount = screen.getByText('3 Repositories')
expect(repositoriesCount).toBeInTheDocument()

const contributionsCount = screen.getByText('100 Contributions')
expect(contributionsCount).toBeInTheDocument()
})
})

Expand Down
1 change: 1 addition & 0 deletions frontend/src/api/queries/userQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const GET_USER_DATA = gql`
}
releasesCount
url
contributionsCount
}
}
`
6 changes: 6 additions & 0 deletions frontend/src/pages/UserDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
faUser,
faFileCode,
faBookmark,
faCodeMerge,
} from '@fortawesome/free-solid-svg-icons'
import { GET_USER_DATA } from 'api/queries/userQueries'
import millify from 'millify'
Expand Down Expand Up @@ -214,6 +215,11 @@ const UserDetailsPage: React.FC = () => {
value: `${user.releasesCount ? millify(user.releasesCount, { precision: 1 }) : 'No'}
${pluralize(user.releasesCount, 'Release')}`,
},
{
icon: faCodeMerge,
value: `${user.contributionsCount ? millify(user.contributionsCount, { precision: 1 }) : 'No'}
${pluralize(user.contributionsCount, 'Contribution')}`,
},
]

const Heatmap = () => (
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/types/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type user = {
name: string
objectID: string
public_repositories_count: number
contributions_count: number
}

export type RepositoryDetails = {
Expand Down Expand Up @@ -57,6 +58,7 @@ export type User = {
releases?: Release[]
releases_count?: number
url: string
contributions_count: number
}

export interface UserDetailsProps {
Expand All @@ -77,6 +79,7 @@ export interface UserDetailsProps {
releasesCount: number
topRepositories: RepositoryCardProps[]
url: string
contributionsCount: number
}

export interface PullRequestsType {
Expand Down