Add the default emoji skin tone user preference
What does this MR do and why?
Adds a per-user Default emoji skin tone preference with six values (default plus five tones). It is the second step of the default emoji skin tone epic, after the skin tone mapping fixture.
- Database:
emoji_skin_tone smallint DEFAULT 0 NOT NULLonuser_preferences, a Rails enum onUserPreference, and delegates onUser. - GraphQL:
UserPreferences.emojiSkinTone, anemojiSkinToneargument onuserPreferencesUpdatethat rejectsnull, and theEmojiSkinToneenum. Field and argument are markedexperimentfor 19.6. - REST:
emoji_skin_toneonGET /user/preferencesandPUT /user/preferences, restricted to the six values. An unknown value returns 400 withemoji_skin_tone does not have a valid value. - Behind the new
emoji_skin_tone_preferenceflag (wip, default off): a Default emoji skin tone dropdown list on the preferences page, with the thumbs up in each tone next to the tone name as the options, andgon.emoji_skin_tone. With the flag off nothing is visible and thegonkey is absent.
Nothing applies the tone yet. The consumers are server-side reaction toggling and the web picker and autocomplete, which need this MR deployed one release ahead of the frontend.
Resolves https://gitlab.com/gitlab-org/gitlab/-/issues/633107.
Decisions
- The enum value
defaultmirrors Slack and Unicode.scopes: falseavoids aUserPreference.defaultclass method, andprefix: truekeeps the predicates (emoji_skin_tone_light?) off generic names such aslight?anddark?. - A typed column rather than the JSON settings blob, because the value is validated and queried through two APIs.
- The GraphQL field is non-null because the column is
NOT NULL. The mutation argument rejectsnullinstead of silently ignoring it, so a client gets an error rather than an unchanged value. - A presence validation on the model turns a blank form value into a 400 instead of a
NOT NULLviolation. - The setting sits on the Editing tab in the Markdown section, next to Enable emoji autocomplete. The issue says "under Behavior", but the preferences page was split into tabs after the issue was written and the emoji settings now live in Editing.
- The control is a listbox mounted through the page's existing
.js-listbox-inputhook, the same as the Homepage setting on the Behavior tab, so no JavaScript was added. Each option shows the thumbs up in that tone followed by the Unicode modifier name (LighttoDark, withDefaultfor the unmodified emoji), which makes a separate preview unnecessary. The emoji is the native Unicode character, because listbox options are plain text. The choice is saved with Save changes, like the other dropdown lists on the page.
Database
The migration adds one column with a default to user_preferences (one row per user, no index). No new queries: the preference is read through the existing lookup of user_preferences by user_id.
Migration output
main: == 20261014224518 AddEmojiSkinToneToUserPreferences: migrating ================
main: -- add_column(:user_preferences, :emoji_skin_tone, :smallint, {default: 0, null: false})
main: -> 0.0148s
main: == 20261014224518 AddEmojiSkinToneToUserPreferences: migrated (0.0196s) =======
main: == 20261014224518 AddEmojiSkinToneToUserPreferences: reverting ================
main: -- remove_column(:user_preferences, :emoji_skin_tone, :smallint, {default: 0, null: false})
main: -> 0.0118s
main: == 20261014224518 AddEmojiSkinToneToUserPreferences: reverted (0.0202s) =======References
- Epic: https://gitlab.com/groups/gitlab-org/-/epics/24004
- Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/633107
- Step 1, the fixture: !261139 (merged)
- Rollout and flag removal: https://gitlab.com/gitlab-org/gitlab/-/issues/633111
Screenshots or screen recordings
Preferences, Editing tab, Markdown section.
Before (flag off, same as master) |
After (flag on) |
|---|---|
![]() |
![]() |
The same setting in light mode:
How to set up and validate locally
-
In a Rails console, enable the flag:
Feature.enable(:emoji_skin_tone_preference) -
Open Preferences, then the Editing tab. In the Markdown section, open the Default emoji skin tone dropdown list, pick a tone, and select Save changes. Reload: the dropdown list shows the saved tone, and
gon.emoji_skin_tonein the browser console holds it. -
In GraphiQL (
/-/graphql-explorer):mutation { userPreferencesUpdate(input: { emojiSkinTone: MEDIUM }) { userPreferences { emojiSkinTone } errors } } query { currentUser { userPreferences { emojiSkinTone } } }emojiSkinTone: nullreturns a validation error. -
REST, with a personal access token:
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" --url "http://gdk.test:3000/api/v4/user/preferences?emoji_skin_tone=dark" curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" --url "http://gdk.test:3000/api/v4/user/preferences?emoji_skin_tone=purple" curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" --url "http://gdk.test:3000/api/v4/user/preferences"The second call returns 400 with
emoji_skin_tone does not have a valid value. -
Disable the flag. The dropdown list is gone and
gon.emoji_skin_toneis absent, while both APIs keep working.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.


