Skip to content

Task 2542: Boost Day and Tenure Achievements Fullstack - #2553

Draft
javiercoronadonarvaez wants to merge 6 commits into
developfrom
javiercoronarv/2542-boost-day-and-tenure-badge
Draft

Task 2542: Boost Day and Tenure Achievements Fullstack#2553
javiercoronadonarvaez wants to merge 6 commits into
developfrom
javiercoronarv/2542-boost-day-and-tenure-badge

Conversation

@javiercoronadonarvaez

@javiercoronadonarvaez javiercoronadonarvaez commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Issue: #2542

Summary & Context

Displays a tenure medal and a Boost Day celebration icon beside a member's name across the platform. Both are derived from the member's date_joined at render time. Nothing is stored, no admin action is needed, and no scheduled job assigns or revokes them.

Changes

  • users/achievements.py (new): resolves both badges from date_joined. tenure_years (full years elapsed), tenure_tier_token, is_boost_day, plus tenure_badge, boost_day_badge and profile_badges.
  • users/models.py: User.profile_badges (cached) with tenure_badge / boost_day_badge passthroughs.
  • users/profile_cards.py, news/services.py, libraries/models.py: the other three author-dict builders emit the same two keys. CommitAuthor delegates to its linked account and yields nothing for git-only contributors.
  • templates/v3/includes/_user_profile.html: two badge slots per Figma: the Boost Day icon inside .user-profile__name-group (beside the name), the tenure medal after .user-profile__role (beside "Contributor"/"Maintainer"). The pre-existing single-badge prop still works for the component demo and mock data.
  • static/css/v3/user-profile.css: comment only.
  • users/tests/test_achievements.py (new): 52 tests: tier boundaries, anniversary edges, all four Feb-29 permutations, ordinal labels (1st/2nd/3rd/10th/11th/22nd), and a template test asserting the two icons land on opposite sides of the role element.

Uses the medal tokens (badge-tier-1…5tier-N.png), which are the icons in the Figma and the same artwork already shown on /users/me/.

‼️ Risks & Considerations ‼️

  1. Ticket says "star", implementation uses medals. Two tier families exist in the codebase: star-tier-* (stars) and badge-tier-* (medals). The Figma shows medals, and the component demo page (_v3_example_section.html:152-156) labels the stars with these exact tenure thresholds. Design confirmed the medal placement verbally; the AC wording still says "star". Tooltip copy is unchanged from the AC ("Boost Member for N years"), so the word "star" no longer appears anywhere in the UI.
  2. Boost Day boundaries are server-local, via timezone.localdate() and not per-user timezone. A member may see their icon appear/disappear a few hours off from their own midnight.
  3. Two AC surfaces are not covered. The user profile header uses _user_card.html, whose only badge slot is a featured achievement (currently hardcoded "Bug Catcher"). Wiring tenure there needs a new template prop. Testimonials have a plain-text author CharField with no account link, so no date_joined exists to derive from.
  4. Tenure is recomputed per render. No queries are added (date_joined is already loaded) and it is cached per instance, but it is not cached across requests. A member crossing a tier threshold or anniversary is reflected on the next page load, by design.

Screenshots

Peer Testing

Seed data caps out around 3 years' tenure, so nothing above bronze appears and no Boost Day fires unless a member's anniversary happens to be today. Backdate a user to see both icons.

1. Give your user both badges

Sets date_joined to N years ago on today's date, so the tenure medal and the Boost Day icon both appear. Change YEARS to pick a tier. Note the printed ORIGINAL date_joined so you can restore it in step 4.

docker compose run --rm web python manage.py shell -c "
import datetime
from django.utils import timezone
from users.models import User

YEARS = 20   # 2 bronze | 5 silver | 10 gold | 15 diamond | 20 platinum

u = User.objects.get(email='superadmin@boost.org')
print('ORIGINAL date_joined:', u.date_joined.isoformat())
today = timezone.localdate()
u.date_joined = datetime.datetime(
    today.year - YEARS, today.month, today.day, 12, 0, tzinfo=datetime.timezone.utc
)
u.save(update_fields=['date_joined'])
print('NEW date_joined:', u.date_joined.isoformat())
print('badges:', User.objects.get(email='superadmin@boost.org').profile_badges)
"

Expected output for YEARS = 20:

badges: {'tenure_badge': {'token': 'badge-tier-5', 'label': 'Boost Member for 20 years'},
         'boost_day_badge': {'token': 'boost-day', 'label': 'Happy 20th Boost Day'}}

2. What to check in the browser

Open localhost:8000/news/ — the seeded posts are all by the same author, so you get ~10 instances.

  • The 🎉 Boost Day icon sits immediately after the name, inside the name group.
  • The tenure medal sits immediately after the role ("Contributor").
  • Hover each icon for its tooltip: "Boost Member for 20 years" and "Happy 20th Boost Day". Pure CSS, no JS.
  • Tab to each icon: they are focusable (tabindex="0") and reveal the tooltip on focus.
  • localhost:8000/ shows other members at 2–3 years with bronze medals, so you can compare tiers side by side.

3. Check the medal alone (no Boost Day)

Any date whose month/day is not today gives the medal only — this is the everyday case:

docker compose run --rm web python manage.py shell -c "
import datetime
from users.models import User
u = User.objects.get(email='superadmin@boost.org')
u.date_joined = datetime.datetime(2016, 1, 15, 12, 0, tzinfo=datetime.timezone.utc)
u.save(update_fields=['date_joined'])
print(User.objects.get(email='superadmin@boost.org').profile_badges)
"
# -> tenure_badge: badge-tier-3 (gold), boost_day_badge: None

To check the opposite case — Boost Day with no medal — use YEARS = 1 in step 1. One year is below the bronze threshold, so only the 🎉 appears with "Happy 1st Boost Day".

4. Restore your user

Substitute the ORIGINAL date_joined printed in step 1:

docker compose run --rm web python manage.py shell -c "
import datetime
from users.models import User
u = User.objects.get(email='superadmin@boost.org')
u.date_joined = datetime.datetime(2026, 6, 12, 13, 51, 38, 383511, tzinfo=datetime.timezone.utc)
u.save(update_fields=['date_joined'])
print('restored:', u.date_joined.isoformat())
"

Tests

  • Added appropriate tests and one bug fix, perhaps out of scope for this ticket, but easy gain which keeps the test suite off the shared Redis cache.

Self-review Checklist

  • Tag at least one team member from each team to review this PR
  • Link this PR to the related GitHub Project ticket

Frontend

  • UI implementation matches Figma design
  • Tested in light and dark mode
  • Responsive / mobile verified
  • Accessibility checked (keyboard navigation, etc.)
  • Ensure design tokens are used for colors, spacing, typography, etc. – No hardcoded values
  • Test without JavaScript (if applicable)
  • No console errors or warnings

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ca36208d-a35f-4de3-9972-8b80ad06ce3b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch javiercoronarv/2542-boost-day-and-tenure-badge

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@javiercoronadonarvaez javiercoronadonarvaez linked an issue Jul 27, 2026 that may be closed by this pull request
@javiercoronadonarvaez
javiercoronadonarvaez force-pushed the javiercoronarv/2542-boost-day-and-tenure-badge branch 3 times, most recently from 4587355 to c1b8faa Compare July 30, 2026 14:08
@javiercoronadonarvaez
javiercoronadonarvaez force-pushed the javiercoronarv/2542-boost-day-and-tenure-badge branch from 433f773 to c0f43eb Compare July 30, 2026 23:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Webpage Integration: Boost Day and Tenure Icons

1 participant