Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions backend/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ github-update-project-related-repositories:
@echo "Updating OWASP project related GitHub repositories"
@CMD="python manage.py github_update_project_related_repositories" $(MAKE) exec-backend-command

github-update-users:
@echo "Updating GitHub users"
@CMD="python manage.py github_update_users" $(MAKE) exec-backend-command

index-data:
@echo "Indexing Nest data"
@CMD="python manage.py algolia_reindex" $(MAKE) exec-backend-command
Expand Down Expand Up @@ -155,6 +159,7 @@ update-data: \
owasp-scrape-committees \
owasp-scrape-projects \
github-update-project-related-repositories \
github-update-users \
owasp-aggregate-projects \
owasp-update-events \
owasp-update-sponsors
1 change: 1 addition & 0 deletions backend/apps/github/graphql/nodes/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class Meta:
"avatar_url",
"bio",
"company",
"contributions_count",
"email",
"followers_count",
"following_count",
Expand Down
55 changes: 55 additions & 0 deletions backend/apps/github/management/commands/github_update_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""A command to update GitHub users."""

import logging

from django.core.management.base import BaseCommand
from django.db.models import Sum

from apps.common.models import BATCH_SIZE
from apps.github.models.repository_contributor import RepositoryContributor
from apps.github.models.user import User

logger = logging.getLogger(__name__)


class Command(BaseCommand):
help = "Update GitHub users."

def add_arguments(self, parser):
"""Add command-line arguments to the parser.

Args:
parser (argparse.ArgumentParser): The argument parser instance.

"""
parser.add_argument("--offset", default=0, required=False, type=int)

def handle(self, *args, **options):
"""Handle the command execution.

Args:
*args: Variable length argument list.
**options: Arbitrary keyword arguments containing command options.

"""
active_users = User.objects.order_by("-created_at")
active_users_count = active_users.count()
offset = options["offset"]
user_contributions = {
item["user_id"]: item["total_contributions"]
for item in RepositoryContributor.objects.values("user_id").annotate(
total_contributions=Sum("contributions_count")
)
}
users = []
for idx, user in enumerate(active_users[offset:]):
prefix = f"{idx + offset + 1} of {active_users_count - offset}"
print(f"{prefix:<10} {user.title}")

user.contributions_count = user_contributions.get(user.id, 0)
users.append(user)

if not len(users) % BATCH_SIZE:
User.bulk_save(users, fields=("contributions_count",))

User.bulk_save(users, fields=("contributions_count",))
17 changes: 17 additions & 0 deletions backend/apps/github/migrations/0021_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-04-07 07:56

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("github", "0020_repositorycontributor_user_contrib_idx"),
]

operations = [
migrations.AddField(
model_name="user",
name="contributions_count",
field=models.IntegerField(default=0, verbose_name="Contributions count"),
),
]
12 changes: 12 additions & 0 deletions backend/apps/github/migrations/0022_merge_20250417_1000.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Generated by Django 5.2 on 2025-04-17 10:00

from django.db import migrations


class Migration(migrations.Migration):
dependencies = [
("github", "0021_release_release_published_at_idx"),
("github", "0021_user_contributions_count"),
]

operations = []
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 5.2 on 2025-04-20 02:55

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("github", "0022_merge_20250417_1000"),
]

operations = [
migrations.AlterField(
model_name="user",
name="contributions_count",
field=models.PositiveIntegerField(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
11 changes: 10 additions & 1 deletion backend/apps/github/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from django.db import models

from apps.common.models import TimestampedModel
from apps.common.models import BulkSaveModel, TimestampedModel
from apps.github.constants import GITHUB_GHOST_USER_LOGIN, OWASP_FOUNDATION_LOGIN
from apps.github.models.common import GenericUserModel, NodeModel
from apps.github.models.mixins.user import UserIndexMixin
Expand All @@ -22,6 +22,10 @@ class Meta:

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

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

def __str__(self):
"""Return a human-readable representation of the user.

Expand Down Expand Up @@ -74,6 +78,11 @@ def from_github(self, gh_user):

self.is_bot = gh_user.type == "Bot"

@staticmethod
def bulk_save(users, fields=None):
"""Bulk save users."""
BulkSaveModel.bulk_save(User, users, fields=fields)

@staticmethod
def get_non_indexable_logins():
"""Get logins that should not be indexed.
Expand Down
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 @@ -19,6 +19,7 @@ def test_meta_configuration(self):
"avatar_url",
"bio",
"company",
"contributions_count",
"created_at",
"email",
"followers_count",
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 @@ -35,6 +35,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,
},
recentIssues: [
{
Expand Down
11 changes: 6 additions & 5 deletions frontend/__tests__/unit/pages/UserDetails.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ describe('UserDetailsPage', () => {

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

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

Expand Down Expand Up @@ -330,11 +333,10 @@ describe('UserDetailsPage', () => {
...mockUserDetailsData,
user: {
...mockUserDetailsData.user,
contributionsCount: 0,
followersCount: 0,
followingCount: 0,
publicRepositoriesCount: 0,
issuesCount: 0,
releasesCount: 0,
},
}
;(useQuery as jest.Mock).mockReturnValue({
Expand All @@ -348,8 +350,7 @@ describe('UserDetailsPage', () => {
expect(screen.getByText('No Followers')).toBeInTheDocument()
expect(screen.getByText('No Followings')).toBeInTheDocument()
expect(screen.getByText('No Repositories')).toBeInTheDocument()
expect(screen.getByText('No Issues')).toBeInTheDocument()
expect(screen.getByText('No Releases')).toBeInTheDocument()
expect(screen.getByText('No Contributions')).toBeInTheDocument()
})
})

Expand All @@ -371,7 +372,7 @@ describe('UserDetailsPage', () => {

render(<UserDetailsPage />)
await waitFor(() => {
expect(screen.getAllByText('Not provided').length).toBe(3)
expect(screen.getAllByText('N/A').length).toBe(3)
})
})
})
20 changes: 5 additions & 15 deletions frontend/src/app/members/[memberKey]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
'use client'
import { useQuery } from '@apollo/client'
import {
faCircleExclamation,
faCodeMerge,
faFolderOpen,
faPersonWalkingArrowRight,
faTag,
faUserPlus,
} from '@fortawesome/free-solid-svg-icons'
import Image from 'next/image'
Expand Down Expand Up @@ -173,18 +172,10 @@ const UserDetailsPage: React.FC = () => {
}

const userDetails = [
{
label: 'GitHub Profile',
value: (
<Link href={user?.url || '#'} className="text-blue-400 hover:underline">
@{user?.login}
</Link>
),
},
{ label: 'Joined', value: user?.createdAt ? formatDate(user.createdAt) : 'Not available' },
{ label: 'Email', value: user?.email || 'Not provided' },
{ label: 'Company', value: user?.company || 'Not provided' },
{ label: 'Location', value: user?.location || 'Not provided' },
{ label: 'Email', value: user?.email || 'N/A' },
{ label: 'Company', value: user?.company || 'N/A' },
{ label: 'Location', value: user?.location || 'N/A' },
]

const userStats = [
Expand All @@ -196,8 +187,7 @@ const UserDetailsPage: React.FC = () => {
unit: 'Repository',
value: user?.publicRepositoriesCount ?? 0,
},
{ icon: faCircleExclamation, value: user?.issuesCount || 0, unit: 'Issue' },
{ icon: faTag, value: user?.releasesCount || 0, unit: 'Release' },
{ icon: faCodeMerge, value: user?.contributionsCount || 0, unit: 'Contribution' },
]

const Heatmap = () => (
Expand Down
1 change: 1 addition & 0 deletions frontend/src/server/queries/userQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const GET_USER_DATA = gql`
avatarUrl
bio
company
contributionsCount
createdAt
email
followersCount
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 @@ -4,6 +4,7 @@ export type user = {
avatar_url: string
bio: string
company: string
contributions_count: number
created_at: number
email: string
followers_count: number
Expand Down Expand Up @@ -56,6 +57,7 @@ export type User = {
releases?: Release[]
releases_count?: number
url: string
contributions_count: number
}

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

export interface PullRequestsType {
Expand Down