Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
821663b
add BoardCandidateProfile model and update code
rudransh-shrivastava Aug 2, 2026
2a7a638
add candidate profile page with annotations
rudransh-shrivastava Aug 5, 2026
c1cf6e8
integrate claim highlights and candidate page with other components
rudransh-shrivastava Aug 5, 2026
4d64d9f
backend: apply bot comments
rudransh-shrivastava Aug 7, 2026
3930de5
apply frontend bot comments
rudransh-shrivastava Aug 7, 2026
126da9a
refactor frontend to use markdown-to-jsx
rudransh-shrivastava Aug 7, 2026
1d0de2d
frontend cleanup
rudransh-shrivastava Aug 8, 2026
9e9c30d
remove AnnotatedProfile
rudransh-shrivastava Aug 8, 2026
5e390c8
rewrite AnnotatedProfile with better quality code
rudransh-shrivastava Aug 8, 2026
b5a000a
generate graphql types
rudransh-shrivastava Aug 8, 2026
59a0bb5
bot comments, hide popup when overlapping existing highlight
rudransh-shrivastava Aug 8, 2026
c4a83e6
don't show create claim button when highlighted text is not in raw ma…
rudransh-shrivastava Aug 8, 2026
5c200da
remove unused isReviewer
rudransh-shrivastava Aug 8, 2026
2d456a9
display all matched claims instead of first
rudransh-shrivastava Aug 8, 2026
0694855
Merge branch 'feature/bod-candidate-transparency' into feature/bod-ca…
rudransh-shrivastava Aug 8, 2026
b3981fb
post merge, fix generate command to include source text
rudransh-shrivastava Aug 8, 2026
c64d77a
validate source text in backend
rudransh-shrivastava Aug 8, 2026
4767af9
cleanup frontend code
rudransh-shrivastava Aug 8, 2026
162a9a1
Merge branch 'feature/bod-candidate-transparency' into feature/bod-ca…
rudransh-shrivastava Aug 10, 2026
d2b7ee6
update claim status RBAC, show submitted claims, change colors
rudransh-shrivastava Aug 16, 2026
b15e9ee
fix breadcrumbs
rudransh-shrivastava Aug 16, 2026
04e4dde
update styles on candidate pages
rudransh-shrivastava Aug 20, 2026
b3fb362
no longer display approved claims
rudransh-shrivastava Aug 20, 2026
f876857
update highlight colors in dark theme
rudransh-shrivastava Aug 20, 2026
a078171
apply bot comments
rudransh-shrivastava Aug 20, 2026
93a555a
remove test
rudransh-shrivastava Aug 20, 2026
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
10 changes: 7 additions & 3 deletions backend/make/apps/owasp.mk
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
owasp-aggregate-member-contributions owasp-aggregate-projects owasp-create-project-metadata-file \
owasp-enrich-chapters owasp-enrich-committees owasp-enrich-events owasp-enrich-projects \
owasp-generate-community-snapshot-video owasp-process-snapshots owasp-scrape-chapters \
owasp-scrape-committees owasp-scrape-projects owasp-sync-posts owasp-update-events \
owasp-update-leaders owasp-update-project-health-metrics owasp-update-project-health-requirements \
owasp-update-project-health-scores owasp-update-sponsors
owasp-scrape-committees owasp-scrape-projects owasp-sync-board-candidates owasp-sync-posts \
owasp-update-events owasp-update-leaders owasp-update-project-health-metrics \
owasp-update-project-health-requirements owasp-update-project-health-scores owasp-update-sponsors

owasp-add-project-custom-tags:
@echo "Adding project custom tags from $(FILE)"
Expand Down Expand Up @@ -81,6 +81,10 @@ owasp-scrape-projects:
owasp-sync-posts:
@CMD="python manage.py owasp_sync_posts" $(MAKE) backend-exec-command

owasp-sync-board-candidates:
@echo "Sync OWASP board candidates"
@CMD="python manage.py owasp_sync_board_candidates $(ARGS)" $(MAKE) backend-exec-command

owasp-update-events:
@echo "Getting OWASP events data"
@CMD="python manage.py owasp_update_events" $(MAKE) backend-exec-command
Expand Down
1 change: 1 addition & 0 deletions backend/src/apps/owasp/admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from .board_candidate_claim import BoardCandidateClaimAdmin
from .board_candidate_claim_evidence import BoardCandidateClaimEvidenceAdmin
from .board_candidate_claim_review import BoardCandidateClaimReviewAdmin
from .board_candidate_profile import BoardCandidateProfileAdmin
from .board_of_directors import BoardOfDirectorsAdmin
from .chapter import ChapterAdmin
from .committee import CommitteeAdmin
Expand Down
41 changes: 41 additions & 0 deletions backend/src/apps/owasp/admin/board_candidate_profile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Django admin configuration for BoardCandidateProfile model."""

from django.contrib import admin
from django.db import models

from apps.owasp.models.board_candidate_profile import BoardCandidateProfile


class BoardCandidateProfileAdmin(admin.ModelAdmin):
"""Admin for BoardCandidateProfile model."""
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.

autocomplete_fields = ("candidate",)
list_display = (
"__str__",
"nest_created_at",
"nest_updated_at",
)
search_fields = (
"candidate__member_name",
"candidate__member__login",
"raw_markdown",
)
readonly_fields = (
"nest_created_at",
"nest_updated_at",
)

def get_queryset(self, request) -> models.QuerySet:
"""Retrieve optimized queryset with related candidate.

Args:
request: The HTTP request object.

Returns:
QuerySet: BoardCandidateProfile queryset with prefetched candidate.

"""
return super().get_queryset(request).select_related("candidate__member")


admin.site.register(BoardCandidateProfile, BoardCandidateProfileAdmin)
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class CreateClaimInput:

description: str
name: str
source_text: str = ""
year: int


Expand All @@ -37,6 +38,7 @@ class UpdateClaimInput:
description: str | None = None
key: str
name: str | None = None
source_text: str | None = None
year: int


Expand Down Expand Up @@ -173,6 +175,7 @@ def create_board_candidate_claim(
candidate=candidate,
description=input_data.description,
name=input_data.name,
source_text=input_data.source_text,
)
except IntegrityError:
logger.warning(
Expand Down Expand Up @@ -232,6 +235,9 @@ def update_board_candidate_claim(
if input_data.description:
claim.description = input_data.description
update_fields.append("description")
if input_data.source_text is not None:
claim.source_text = input_data.source_text
Comment thread
rudransh-shrivastava marked this conversation as resolved.
update_fields.append("source_text")

try:
claim.save(update_fields=update_fields)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"key",
"name",
"order",
"source_text",
"withdrawn_at",
"withdrawn_reason",
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""OWASP Board Candidate Profile GraphQL node."""

from datetime import datetime

import strawberry
import strawberry_django

from apps.owasp.api.internal.nodes.entity_member import EntityMemberNode
from apps.owasp.models.board_candidate_profile import BoardCandidateProfile


@strawberry_django.type(
BoardCandidateProfile,
fields=[
"raw_markdown",
],
)
class BoardCandidateProfileNode(strawberry.relay.Node):
"""Board Candidate Profile node."""

@strawberry_django.field
def candidate(self, root: BoardCandidateProfile) -> EntityMemberNode:
"""Resolve candidate."""
return root.candidate

@strawberry_django.field
def created_at(self, root: BoardCandidateProfile) -> datetime:
"""Resolve profile creation date."""
return root.nest_created_at

@strawberry_django.field
def updated_at(self, root: BoardCandidateProfile) -> datetime:
"""Resolve profile last update date."""
return root.nest_updated_at
2 changes: 2 additions & 0 deletions backend/src/apps/owasp/api/internal/queries/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from apps.owasp.api.internal.queries.board_candidate_claim_evidence import (
BoardCandidateClaimEvidenceQuery,
)
from apps.owasp.api.internal.queries.board_candidate_profile import BoardCandidateProfileQuery

from .board_of_directors import BoardOfDirectorsQuery
from .chapter import ChapterQuery
Expand All @@ -21,6 +22,7 @@
class OwaspQuery(
BoardCandidateClaimEvidenceQuery,
BoardCandidateClaimQuery,
BoardCandidateProfileQuery,
BoardOfDirectorsQuery,
ChapterQuery,
CommitteeQuery,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,18 @@ def board_candidate_claims(
claims = claims.filter(candidate__member__login=login)

if not is_self and not is_reviewer:
claims = claims.filter(status=BoardCandidateClaim.Status.APPROVED)
claims = claims.filter(
status__in=[
BoardCandidateClaim.Status.APPROVED,
BoardCandidateClaim.Status.REJECTED,
Comment thread
rudransh-shrivastava marked this conversation as resolved.
]
)
elif is_reviewer and not is_self:
claims = claims.filter(
status__in=[
BoardCandidateClaim.Status.SUBMITTED,
BoardCandidateClaim.Status.APPROVED,
BoardCandidateClaim.Status.REJECTED,
]
)
elif is_reviewer:
Expand All @@ -62,16 +68,27 @@ def board_candidate_claims(
status__in=[
BoardCandidateClaim.Status.SUBMITTED,
BoardCandidateClaim.Status.APPROVED,
BoardCandidateClaim.Status.REJECTED,
]
)
)
elif user.is_authenticated and user.github_user:
claims = claims.filter(
Q(candidate__member=user.github_user)
| Q(status=BoardCandidateClaim.Status.APPROVED)
| Q(
status__in=[
BoardCandidateClaim.Status.APPROVED,
BoardCandidateClaim.Status.REJECTED,
]
)
)
else:
claims = claims.filter(status=BoardCandidateClaim.Status.APPROVED)
claims = claims.filter(
status__in=[
BoardCandidateClaim.Status.APPROVED,
BoardCandidateClaim.Status.REJECTED,
]
)

return (
claims.annotate(
Expand Down Expand Up @@ -133,7 +150,11 @@ def board_candidate_claim(
if (
is_self
or (is_reviewer and claim.status == BoardCandidateClaim.Status.SUBMITTED)
or claim.status == BoardCandidateClaim.Status.APPROVED
or claim.status
in {
BoardCandidateClaim.Status.APPROVED,
BoardCandidateClaim.Status.REJECTED,
}
)
else None
)
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ def get_claim_evidence(
if (
is_self
or (is_reviewer and evidence.claim.status == BoardCandidateClaim.Status.SUBMITTED)
or evidence.claim.status == BoardCandidateClaim.Status.APPROVED
or evidence.claim.status
Comment thread
rudransh-shrivastava marked this conversation as resolved.
in {
BoardCandidateClaim.Status.APPROVED,
BoardCandidateClaim.Status.REJECTED,
}
)
else None
)
Expand Down Expand Up @@ -99,7 +103,11 @@ def board_candidate_claim_evidences(
if (
is_self
or (is_reviewer and claim.status == BoardCandidateClaim.Status.SUBMITTED)
or claim.status == BoardCandidateClaim.Status.APPROVED
or claim.status
in {
BoardCandidateClaim.Status.APPROVED,
BoardCandidateClaim.Status.REJECTED,
}
)
else []
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""OWASP Board Candidate Profile GraphQL queries."""

import strawberry
import strawberry_django
from django.contrib.contenttypes.models import ContentType

from apps.owasp.api.internal.nodes.board_candidate_profile import BoardCandidateProfileNode
from apps.owasp.models.board_candidate_profile import BoardCandidateProfile
from apps.owasp.models.board_of_directors import BoardOfDirectors
from apps.owasp.models.entity_member import EntityMember


@strawberry.type
class BoardCandidateProfileQuery:
"""GraphQL queries for Board Candidate Profile model."""

@strawberry_django.field
def board_candidate_profile(
self, info: strawberry.Info, login: str, year: int
) -> BoardCandidateProfileNode | None:
"""Resolve Board Candidate Profile.

Args:
info (Info): Strawberry Info.
login (str): The login of the candidate.
year (int): The year of the election.

Returns:
BoardCandidateProfileNode object if found, None otherwise.

"""
try:
return BoardCandidateProfile.objects.select_related("candidate__member").get(
Comment thread
rudransh-shrivastava marked this conversation as resolved.
candidate__member__login=login,
candidate__entity_type=ContentType.objects.get_for_model(BoardOfDirectors),
candidate__entity_id=BoardOfDirectors.objects.get(year=year).id,
candidate__role=EntityMember.Role.CANDIDATE,
candidate__is_active=True,
candidate__is_reviewed=True,
)
except (BoardOfDirectors.DoesNotExist, BoardCandidateProfile.DoesNotExist):
return None
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from apps.common.utils import slugify
from apps.github.utils import get_repository_file_content
from apps.owasp.models.board_candidate_claim import BoardCandidateClaim
from apps.owasp.models.board_candidate_profile import BoardCandidateProfile
from apps.owasp.models.board_of_directors import BoardOfDirectors
from apps.owasp.models.entity_member import EntityMember

Expand Down Expand Up @@ -45,9 +46,10 @@
Avoid present perfect constructions such as "has done", "has been", "has contributed", etc.

Return ONLY a valid JSON array of objects.
Each object must have exactly two keys:
Each object must have exactly three keys:
- "name": A concise 10-20 word summary of the claim.
- "description": The full contextual text of the claim.
- "source_text": A single verbatim sentence copied from the input that supports the claim.
"""


Expand Down Expand Up @@ -156,6 +158,11 @@ def generate_claims(
)
return []

try:
profile_markdown = candidate.board_profile.raw_markdown or ""
except BoardCandidateProfile.DoesNotExist:
profile_markdown = ""

claims = []
for claim_data in claims_data:
if not isinstance(claim_data, dict):
Expand All @@ -165,6 +172,10 @@ def generate_claims(
: BoardCandidateClaim._meta.get_field("name").max_length
]
description = str(claim_data.get("description") or "").strip()
source_text = str(claim_data.get("source_text") or "").strip()

if source_text and source_text not in profile_markdown:
Comment thread
rudransh-shrivastava marked this conversation as resolved.
Comment thread
rudransh-shrivastava marked this conversation as resolved.
source_text = ""

if name:
claims.append(
Expand All @@ -173,6 +184,7 @@ def generate_claims(
description=description,
candidate=candidate,
name=name,
source_text=source_text,
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
status=BoardCandidateClaim.Status.DRAFT,
)
)
Expand Down
Loading
Loading