Achievements: Change show_on_profile default to false

What does this MR do and why?

Changes the default value of show_on_profile on user_achievements from true to false.

This ensures awarded badges do not appear on a user's profile until explicitly accepted, laying the groundwork for the accept/decline flow.

This MR also implements the accept/decline flow for achievements. When a user is awarded a badge, it no longer appears on their profile automatically. Instead:

  1. The award notification email includes an Accept link (to decline just ignore)
  2. A new controller handles the accept action via signed token URLs
  3. Accept sets show_on_profile: true, there is not decline action as it would just be a noop

This ensures users have control over which achievements appear on their profile.

References

  • #593737 (closed) - Achievements: Change show_on_profile default to false
  • #593739 - Achievements: Implement accept flow

Screenshots or screen recordings

Email notification:

Screenshot 2026-04-21 at 2.44.01 PM.png

Scenario Confirmation Page (GET) After Accept (POST)
Logged in as recipient

Confirmation page with Accept/Cancel buttons

Screenshot 2026-04-21 at 2.44.41 PM.png

Cancel redirects to profile page

Redirect to profile with success flash

Screenshot 2026-04-21 at 2.45.38 PM.png

Logged in as different user

Same confirmation page

Screenshot 2026-04-22 at 9.36.13 AM.png

Redirect to 404, note link to sign out and sign in as a different user

Screenshot 2026-04-21 at 4.32.49 PM.png

Not logged in at all

Same confirmation page

Screenshot 2026-04-22 at 9.35.09 AM.png

Redirect to sign-in with success flash

Screenshot 2026-04-21 at 4.33.56 PM.png

Expired/invalid token:

Screenshot 2026-04-20 at 2.31.01 PM.png

How to set up and validate locally

  1. Log into GDK as root and navigate to the achievements page for any group (Group > Manage > Achievements) for example: http://gdk.test:3000/groups/toolbox/-/achievements/
  2. Click "New achievement" to create a new achievement and enter any title and description (or use one of the existing ones)
  3. Click "Award" next to the achievement and award it to @root and another user for example @stacey
  4. Check root and stacey's profiles and verify the achievements aren't visible on the profile yet
  5. Check letter_opener at http://gdk.test:3000/rails/letter_opener
    1. There should be 2 emails, one for the achievement awarded to root and one for stacey. Check the "To:" field in the email to differentiate between the two emails. You'll use the link for the one awarded to root as the "logged in" user and the one for stacey as the "not logged in" user.
    2. Verify the email contains "Accept" link and proper copy
    3. Copy the Accept URL and paste in a new browser tab (not from within letter_opener) - if you click the links there's a bug it seems that won't actually work, but copy/pasting into a new tab does
  6. Test accept flow:
    • Logged in - make sure you copy the accept link sent to root: Should show confirmation page, click Accept to confirm then redirect to profile and flash "accepted"
    • Logged in as different user - make sure you copy the accept link sent to stacey: Should show confirmation page, click Accept to confirm then redirect 404 with link to sign out and sign in as a different user
    • Incognito browser - Should show confirmation page same as before, click Accept to confirm then redirect to login and flash "accepted"
  7. Check root and stacey's profiles again and make sure the achievement is now visible since you accepted
  8. Test invalid token: Visit http://gdk.test:3000/-/awarded_achievements/garbage-token/accept — should show "Expired link" error page.

Database

SELECT * FROM user_achievements WHERE show_on_profile = true
UPDATE user_achievements SET show_on_profile = true, updated_at = NOW() WHERE id = 1

Query plan SELECT 👉 https://console.postgres.ai/gitlab/gitlab-production-main/sessions/50935/commands/150846

Query plan UPDATE 👉 https://console.postgres.ai/gitlab/gitlab-production-main/sessions/50935/commands/150847

It's a single-row UPDATE by primary key (user_achievements_pkey) that executes once per user accepting an achievement so it should have negligible performance impact.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist.

Edited by Missy Davies

Merge request reports

Loading