Browse Source

feat(ui+api): Move "Account Appearance" to react (#6905)

* feat(ui+api): Move "Account Appearance" to react

Added user "appearances" endpoint to get and edit appearance options. Added Account Appearance to new settings.
Billy Vong 7 years ago
parent
commit
ab7565b7ca

+ 1 - 0
CHANGES

@@ -10,6 +10,7 @@ Schema Changes
 API Changes
 ~~~~~~~~~~~
 - Project plugins endpoint returns every configurable plugin, and includes additional information about each one
+- Added user "appearances" endpoint to list and modify appearance options.
 
 Version 8.22
 ------------

+ 97 - 0
src/sentry/api/endpoints/user_appearance.py

@@ -0,0 +1,97 @@
+from __future__ import absolute_import
+
+from datetime import datetime
+
+import pytz
+from django.conf import settings
+from rest_framework.response import Response
+from rest_framework import serializers
+from django.utils.translation import ugettext_lazy as _
+
+from sentry.api.bases.user import UserEndpoint
+from sentry.constants import LANGUAGES
+from sentry.models import UserOption
+
+
+def _get_timezone_choices():
+    results = []
+    for tz in pytz.common_timezones:
+        now = datetime.now(pytz.timezone(tz))
+        offset = now.strftime('%z')
+        results.append((int(offset), tz, '(UTC%s) %s' % (offset, tz)))
+    results.sort()
+
+    for i in range(len(results)):
+        results[i] = results[i][1:]
+    return results
+
+
+TIMEZONE_CHOICES = _get_timezone_choices()
+
+
+class UserAppearanceSerializer(serializers.Serializer):
+    # Note the label part of these ChoiceFields are not used by the frontend
+    language = serializers.ChoiceField(choices=LANGUAGES, required=False)
+    stacktraceOrder = serializers.ChoiceField(choices=(
+        ('-1', _('Default (let Sentry decide)')),
+        ('1', _('Most recent call last')),
+        ('2', _('Most recent call first')),
+    ), required=False)
+    timezone = serializers.ChoiceField(choices=TIMEZONE_CHOICES, required=False)
+    clock24Hours = serializers.BooleanField(required=False)
+
+
+class UserAppearanceEndpoint(UserEndpoint):
+    def get(self, request, user):
+        """
+        Retrieve Account "Appearance" options
+        `````````````````````````````````````
+
+        Return details for an account's appearance options such as: timezone, 24hr times, language,
+        stacktrace_order.
+
+        :auth: required
+        """
+        options = UserOption.objects.get_all_values(user=user, project=None)
+
+        return Response({
+            'language': options.get('language') or request.LANGUAGE_CODE,
+            'stacktraceOrder': int(options.get('stacktrace_order', -1) or -1),
+            'timezone': options.get('timezone') or settings.SENTRY_DEFAULT_TIME_ZONE,
+            'clock24Hours': options.get('clock_24_hours') or False,
+        })
+
+    def put(self, request, user):
+        """
+        Update Account Appearance options
+        `````````````````````````````````
+
+        Update account appearance options. Only supplied values are updated.
+
+        :param string language: language preference
+        :param string stacktrace_order: One of -1 (default), 1 (most recent call last), 2 (most recent call first).
+        :param string timezone: timezone option
+        :param clock_24_hours boolean: use 24 hour clock
+        :auth: required
+        """
+        serializer = UserAppearanceSerializer(data=request.DATA, partial=True)
+
+        if not serializer.is_valid():
+            return Response(serializer.errors, status=400)
+
+        result = serializer.object
+
+        # map API keys to keys in model
+        key_map = {
+            'stacktraceOrder': 'stacktrace_order',
+            'clock24Hours': 'clock_24_hours',
+        }
+
+        for key in result:
+            UserOption.objects.set_value(
+                user=user,
+                key=key_map.get(key, key),
+                value=result.get(key),
+            )
+
+        return Response(status=204)

+ 6 - 0
src/sentry/api/urls.py

@@ -129,6 +129,7 @@ from .endpoints.team_members import TeamMembersEndpoint
 from .endpoints.team_project_index import TeamProjectIndexEndpoint
 from .endpoints.team_stats import TeamStatsEndpoint
 from .endpoints.useravatar import UserAvatarEndpoint
+from .endpoints.user_appearance import UserAppearanceEndpoint
 from .endpoints.user_authenticator_details import UserAuthenticatorDetailsEndpoint
 from .endpoints.user_identity_details import UserIdentityDetailsEndpoint
 from .endpoints.user_index import UserIndexEndpoint
@@ -188,6 +189,11 @@ urlpatterns = patterns(
         UserAvatarEndpoint.as_view(),
         name='sentry-api-0-user-avatar'
     ),
+    url(
+        r'^users/(?P<user_id>[^\/]+)/appearance/$',
+        UserAppearanceEndpoint.as_view(),
+        name='sentry-api-0-user-appearance'
+    ),
     url(
         r'^users/(?P<user_id>[^\/]+)/authenticators/(?P<auth_id>[^\/]+)/$',
         UserAuthenticatorDetailsEndpoint.as_view(),

+ 1 - 0
src/sentry/static/sentry/app/__mocks__/jquery.jsx

@@ -3,6 +3,7 @@ let jq = {
   select2: () => jq,
   on: () => jq,
   unbind: () => jq,
+  ajaxError: () => jq,
 };
 
 export default () => jq;

+ 64 - 0
src/sentry/static/sentry/app/data/forms/accountAppearance.jsx

@@ -0,0 +1,64 @@
+import {createSearchMap} from './util';
+import timezones from '../timezones';
+import languages from '../languages';
+
+const forms = [
+  {
+    // Form "section"/"panel"
+    title: 'Events',
+    fields: [
+      {
+        name: 'stacktraceOrder',
+        type: 'choice',
+        required: false,
+        choices: [
+          ['-1', 'Default (let Sentry decide)'],
+          ['1', 'Most recent call last'],
+          ['2', 'Most recent call first'],
+        ],
+
+        // additional data/props that is related to rendering of form field rather than data
+        label: 'Stacktrace Order',
+        help: 'Choose the default ordering of frames in stacktraces',
+      },
+    ],
+  },
+
+  {
+    title: 'Localization',
+    fields: [
+      {
+        name: 'language',
+        type: 'choice',
+        label: 'Language',
+        // seems weird to have choices in initial form data
+        choices: languages,
+      },
+      {
+        name: 'timezone',
+        type: 'choice',
+        label: 'Timezone',
+        choices: timezones,
+      },
+      {
+        name: 'clock24Hours',
+        type: 'boolean',
+        label: 'Use a 24-hour clock',
+      },
+    ],
+  },
+];
+
+export default forms;
+
+// generate search index from form fields
+export const searchIndex = createSearchMap({
+  route: '/settings/account/appearance/',
+  formGroups: forms,
+});
+
+// need to associate index -> form group -> route
+// so when we search for a term we need to find:
+//   * what field(s) it matches:
+//     * what form group it belongs to
+//     * what route that belongs to

+ 14 - 0
src/sentry/static/sentry/app/data/languages.jsx

@@ -0,0 +1,14 @@
+export default [
+  ['ja', 'Japanese'],
+  ['it', 'Italian'],
+  ['zh-tw', 'Traditional Chinese'],
+  ['cs', 'Czech'],
+  ['ru', 'Russian'],
+  ['zh-cn', 'Simplified Chinese'],
+  ['bg', 'Bulgarian'],
+  ['de', 'German'],
+  ['fi', 'Finnish'],
+  ['fr', 'French'],
+  ['es', 'Spanish'],
+  ['en', 'English'],
+];

+ 433 - 0
src/sentry/static/sentry/app/data/timezones.jsx

@@ -0,0 +1,433 @@
+export default [
+  ['Pacific/Midway', '(UTC-1100) Pacific/Midway'],
+  ['Pacific/Niue', '(UTC-1100) Pacific/Niue'],
+  ['Pacific/Pago_Pago', '(UTC-1100) Pacific/Pago_Pago'],
+  ['America/Adak', '(UTC-1000) America/Adak'],
+  ['Pacific/Honolulu', '(UTC-1000) Pacific/Honolulu'],
+  ['Pacific/Johnston', '(UTC-1000) Pacific/Johnston'],
+  ['Pacific/Rarotonga', '(UTC-1000) Pacific/Rarotonga'],
+  ['Pacific/Tahiti', '(UTC-1000) Pacific/Tahiti'],
+  ['US/Hawaii', '(UTC-1000) US/Hawaii'],
+  ['Pacific/Marquesas', '(UTC-0930) Pacific/Marquesas'],
+  ['America/Anchorage', '(UTC-0900) America/Anchorage'],
+  ['America/Juneau', '(UTC-0900) America/Juneau'],
+  ['America/Metlakatla', '(UTC-0900) America/Metlakatla'],
+  ['America/Nome', '(UTC-0900) America/Nome'],
+  ['America/Sitka', '(UTC-0900) America/Sitka'],
+  ['America/Yakutat', '(UTC-0900) America/Yakutat'],
+  ['Pacific/Gambier', '(UTC-0900) Pacific/Gambier'],
+  ['US/Alaska', '(UTC-0900) US/Alaska'],
+  ['America/Dawson', '(UTC-0800) America/Dawson'],
+  ['America/Los_Angeles', '(UTC-0800) America/Los_Angeles'],
+  ['America/Tijuana', '(UTC-0800) America/Tijuana'],
+  ['America/Vancouver', '(UTC-0800) America/Vancouver'],
+  ['America/Whitehorse', '(UTC-0800) America/Whitehorse'],
+  ['Canada/Pacific', '(UTC-0800) Canada/Pacific'],
+  ['Pacific/Pitcairn', '(UTC-0800) Pacific/Pitcairn'],
+  ['US/Pacific', '(UTC-0800) US/Pacific'],
+  ['America/Boise', '(UTC-0700) America/Boise'],
+  ['America/Cambridge_Bay', '(UTC-0700) America/Cambridge_Bay'],
+  ['America/Chihuahua', '(UTC-0700) America/Chihuahua'],
+  ['America/Creston', '(UTC-0700) America/Creston'],
+  ['America/Dawson_Creek', '(UTC-0700) America/Dawson_Creek'],
+  ['America/Denver', '(UTC-0700) America/Denver'],
+  ['America/Edmonton', '(UTC-0700) America/Edmonton'],
+  ['America/Fort_Nelson', '(UTC-0700) America/Fort_Nelson'],
+  ['America/Hermosillo', '(UTC-0700) America/Hermosillo'],
+  ['America/Inuvik', '(UTC-0700) America/Inuvik'],
+  ['America/Mazatlan', '(UTC-0700) America/Mazatlan'],
+  ['America/Ojinaga', '(UTC-0700) America/Ojinaga'],
+  ['America/Phoenix', '(UTC-0700) America/Phoenix'],
+  ['America/Yellowknife', '(UTC-0700) America/Yellowknife'],
+  ['Canada/Mountain', '(UTC-0700) Canada/Mountain'],
+  ['US/Arizona', '(UTC-0700) US/Arizona'],
+  ['US/Mountain', '(UTC-0700) US/Mountain'],
+  ['America/Bahia_Banderas', '(UTC-0600) America/Bahia_Banderas'],
+  ['America/Belize', '(UTC-0600) America/Belize'],
+  ['America/Chicago', '(UTC-0600) America/Chicago'],
+  ['America/Costa_Rica', '(UTC-0600) America/Costa_Rica'],
+  ['America/El_Salvador', '(UTC-0600) America/El_Salvador'],
+  ['America/Guatemala', '(UTC-0600) America/Guatemala'],
+  ['America/Indiana/Knox', '(UTC-0600) America/Indiana/Knox'],
+  ['America/Indiana/Tell_City', '(UTC-0600) America/Indiana/Tell_City'],
+  ['America/Managua', '(UTC-0600) America/Managua'],
+  ['America/Matamoros', '(UTC-0600) America/Matamoros'],
+  ['America/Menominee', '(UTC-0600) America/Menominee'],
+  ['America/Merida', '(UTC-0600) America/Merida'],
+  ['America/Mexico_City', '(UTC-0600) America/Mexico_City'],
+  ['America/Monterrey', '(UTC-0600) America/Monterrey'],
+  ['America/North_Dakota/Beulah', '(UTC-0600) America/North_Dakota/Beulah'],
+  ['America/North_Dakota/Center', '(UTC-0600) America/North_Dakota/Center'],
+  ['America/North_Dakota/New_Salem', '(UTC-0600) America/North_Dakota/New_Salem'],
+  ['America/Rainy_River', '(UTC-0600) America/Rainy_River'],
+  ['America/Rankin_Inlet', '(UTC-0600) America/Rankin_Inlet'],
+  ['America/Regina', '(UTC-0600) America/Regina'],
+  ['America/Resolute', '(UTC-0600) America/Resolute'],
+  ['America/Swift_Current', '(UTC-0600) America/Swift_Current'],
+  ['America/Tegucigalpa', '(UTC-0600) America/Tegucigalpa'],
+  ['America/Winnipeg', '(UTC-0600) America/Winnipeg'],
+  ['Canada/Central', '(UTC-0600) Canada/Central'],
+  ['Pacific/Galapagos', '(UTC-0600) Pacific/Galapagos'],
+  ['US/Central', '(UTC-0600) US/Central'],
+  ['America/Atikokan', '(UTC-0500) America/Atikokan'],
+  ['America/Bogota', '(UTC-0500) America/Bogota'],
+  ['America/Cancun', '(UTC-0500) America/Cancun'],
+  ['America/Cayman', '(UTC-0500) America/Cayman'],
+  ['America/Detroit', '(UTC-0500) America/Detroit'],
+  ['America/Eirunepe', '(UTC-0500) America/Eirunepe'],
+  ['America/Guayaquil', '(UTC-0500) America/Guayaquil'],
+  ['America/Havana', '(UTC-0500) America/Havana'],
+  ['America/Indiana/Indianapolis', '(UTC-0500) America/Indiana/Indianapolis'],
+  ['America/Indiana/Marengo', '(UTC-0500) America/Indiana/Marengo'],
+  ['America/Indiana/Petersburg', '(UTC-0500) America/Indiana/Petersburg'],
+  ['America/Indiana/Vevay', '(UTC-0500) America/Indiana/Vevay'],
+  ['America/Indiana/Vincennes', '(UTC-0500) America/Indiana/Vincennes'],
+  ['America/Indiana/Winamac', '(UTC-0500) America/Indiana/Winamac'],
+  ['America/Iqaluit', '(UTC-0500) America/Iqaluit'],
+  ['America/Jamaica', '(UTC-0500) America/Jamaica'],
+  ['America/Kentucky/Louisville', '(UTC-0500) America/Kentucky/Louisville'],
+  ['America/Kentucky/Monticello', '(UTC-0500) America/Kentucky/Monticello'],
+  ['America/Lima', '(UTC-0500) America/Lima'],
+  ['America/Nassau', '(UTC-0500) America/Nassau'],
+  ['America/New_York', '(UTC-0500) America/New_York'],
+  ['America/Nipigon', '(UTC-0500) America/Nipigon'],
+  ['America/Panama', '(UTC-0500) America/Panama'],
+  ['America/Pangnirtung', '(UTC-0500) America/Pangnirtung'],
+  ['America/Port-au-Prince', '(UTC-0500) America/Port-au-Prince'],
+  ['America/Rio_Branco', '(UTC-0500) America/Rio_Branco'],
+  ['America/Thunder_Bay', '(UTC-0500) America/Thunder_Bay'],
+  ['America/Toronto', '(UTC-0500) America/Toronto'],
+  ['Canada/Eastern', '(UTC-0500) Canada/Eastern'],
+  ['Pacific/Easter', '(UTC-0500) Pacific/Easter'],
+  ['US/Eastern', '(UTC-0500) US/Eastern'],
+  ['America/Caracas', '(UTC-0430) America/Caracas'],
+  ['America/Anguilla', '(UTC-0400) America/Anguilla'],
+  ['America/Antigua', '(UTC-0400) America/Antigua'],
+  ['America/Aruba', '(UTC-0400) America/Aruba'],
+  ['America/Barbados', '(UTC-0400) America/Barbados'],
+  ['America/Blanc-Sablon', '(UTC-0400) America/Blanc-Sablon'],
+  ['America/Boa_Vista', '(UTC-0400) America/Boa_Vista'],
+  ['America/Curacao', '(UTC-0400) America/Curacao'],
+  ['America/Dominica', '(UTC-0400) America/Dominica'],
+  ['America/Glace_Bay', '(UTC-0400) America/Glace_Bay'],
+  ['America/Goose_Bay', '(UTC-0400) America/Goose_Bay'],
+  ['America/Grand_Turk', '(UTC-0400) America/Grand_Turk'],
+  ['America/Grenada', '(UTC-0400) America/Grenada'],
+  ['America/Guadeloupe', '(UTC-0400) America/Guadeloupe'],
+  ['America/Guyana', '(UTC-0400) America/Guyana'],
+  ['America/Halifax', '(UTC-0400) America/Halifax'],
+  ['America/Kralendijk', '(UTC-0400) America/Kralendijk'],
+  ['America/La_Paz', '(UTC-0400) America/La_Paz'],
+  ['America/Lower_Princes', '(UTC-0400) America/Lower_Princes'],
+  ['America/Manaus', '(UTC-0400) America/Manaus'],
+  ['America/Marigot', '(UTC-0400) America/Marigot'],
+  ['America/Martinique', '(UTC-0400) America/Martinique'],
+  ['America/Moncton', '(UTC-0400) America/Moncton'],
+  ['America/Montserrat', '(UTC-0400) America/Montserrat'],
+  ['America/Port_of_Spain', '(UTC-0400) America/Port_of_Spain'],
+  ['America/Porto_Velho', '(UTC-0400) America/Porto_Velho'],
+  ['America/Puerto_Rico', '(UTC-0400) America/Puerto_Rico'],
+  ['America/Santo_Domingo', '(UTC-0400) America/Santo_Domingo'],
+  ['America/St_Barthelemy', '(UTC-0400) America/St_Barthelemy'],
+  ['America/St_Kitts', '(UTC-0400) America/St_Kitts'],
+  ['America/St_Lucia', '(UTC-0400) America/St_Lucia'],
+  ['America/St_Thomas', '(UTC-0400) America/St_Thomas'],
+  ['America/St_Vincent', '(UTC-0400) America/St_Vincent'],
+  ['America/Thule', '(UTC-0400) America/Thule'],
+  ['America/Tortola', '(UTC-0400) America/Tortola'],
+  ['Atlantic/Bermuda', '(UTC-0400) Atlantic/Bermuda'],
+  ['Canada/Atlantic', '(UTC-0400) Canada/Atlantic'],
+  ['America/St_Johns', '(UTC-0330) America/St_Johns'],
+  ['Canada/Newfoundland', '(UTC-0330) Canada/Newfoundland'],
+  ['America/Araguaina', '(UTC-0300) America/Araguaina'],
+  ['America/Argentina/Buenos_Aires', '(UTC-0300) America/Argentina/Buenos_Aires'],
+  ['America/Argentina/Catamarca', '(UTC-0300) America/Argentina/Catamarca'],
+  ['America/Argentina/Cordoba', '(UTC-0300) America/Argentina/Cordoba'],
+  ['America/Argentina/Jujuy', '(UTC-0300) America/Argentina/Jujuy'],
+  ['America/Argentina/La_Rioja', '(UTC-0300) America/Argentina/La_Rioja'],
+  ['America/Argentina/Mendoza', '(UTC-0300) America/Argentina/Mendoza'],
+  ['America/Argentina/Rio_Gallegos', '(UTC-0300) America/Argentina/Rio_Gallegos'],
+  ['America/Argentina/Salta', '(UTC-0300) America/Argentina/Salta'],
+  ['America/Argentina/San_Juan', '(UTC-0300) America/Argentina/San_Juan'],
+  ['America/Argentina/San_Luis', '(UTC-0300) America/Argentina/San_Luis'],
+  ['America/Argentina/Tucuman', '(UTC-0300) America/Argentina/Tucuman'],
+  ['America/Argentina/Ushuaia', '(UTC-0300) America/Argentina/Ushuaia'],
+  ['America/Asuncion', '(UTC-0300) America/Asuncion'],
+  ['America/Bahia', '(UTC-0300) America/Bahia'],
+  ['America/Belem', '(UTC-0300) America/Belem'],
+  ['America/Campo_Grande', '(UTC-0300) America/Campo_Grande'],
+  ['America/Cayenne', '(UTC-0300) America/Cayenne'],
+  ['America/Cuiaba', '(UTC-0300) America/Cuiaba'],
+  ['America/Fortaleza', '(UTC-0300) America/Fortaleza'],
+  ['America/Godthab', '(UTC-0300) America/Godthab'],
+  ['America/Maceio', '(UTC-0300) America/Maceio'],
+  ['America/Miquelon', '(UTC-0300) America/Miquelon'],
+  ['America/Montevideo', '(UTC-0300) America/Montevideo'],
+  ['America/Paramaribo', '(UTC-0300) America/Paramaribo'],
+  ['America/Recife', '(UTC-0300) America/Recife'],
+  ['America/Santarem', '(UTC-0300) America/Santarem'],
+  ['America/Santiago', '(UTC-0300) America/Santiago'],
+  ['Antarctica/Palmer', '(UTC-0300) Antarctica/Palmer'],
+  ['Antarctica/Rothera', '(UTC-0300) Antarctica/Rothera'],
+  ['Atlantic/Stanley', '(UTC-0300) Atlantic/Stanley'],
+  ['America/Noronha', '(UTC-0200) America/Noronha'],
+  ['America/Sao_Paulo', '(UTC-0200) America/Sao_Paulo'],
+  ['Atlantic/South_Georgia', '(UTC-0200) Atlantic/South_Georgia'],
+  ['America/Scoresbysund', '(UTC-0100) America/Scoresbysund'],
+  ['Atlantic/Azores', '(UTC-0100) Atlantic/Azores'],
+  ['Atlantic/Cape_Verde', '(UTC-0100) Atlantic/Cape_Verde'],
+  ['Africa/Abidjan', '(UTC+0000) Africa/Abidjan'],
+  ['Africa/Accra', '(UTC+0000) Africa/Accra'],
+  ['Africa/Bamako', '(UTC+0000) Africa/Bamako'],
+  ['Africa/Banjul', '(UTC+0000) Africa/Banjul'],
+  ['Africa/Bissau', '(UTC+0000) Africa/Bissau'],
+  ['Africa/Casablanca', '(UTC+0000) Africa/Casablanca'],
+  ['Africa/Conakry', '(UTC+0000) Africa/Conakry'],
+  ['Africa/Dakar', '(UTC+0000) Africa/Dakar'],
+  ['Africa/El_Aaiun', '(UTC+0000) Africa/El_Aaiun'],
+  ['Africa/Freetown', '(UTC+0000) Africa/Freetown'],
+  ['Africa/Lome', '(UTC+0000) Africa/Lome'],
+  ['Africa/Monrovia', '(UTC+0000) Africa/Monrovia'],
+  ['Africa/Nouakchott', '(UTC+0000) Africa/Nouakchott'],
+  ['Africa/Ouagadougou', '(UTC+0000) Africa/Ouagadougou'],
+  ['Africa/Sao_Tome', '(UTC+0000) Africa/Sao_Tome'],
+  ['America/Danmarkshavn', '(UTC+0000) America/Danmarkshavn'],
+  ['Antarctica/Troll', '(UTC+0000) Antarctica/Troll'],
+  ['Atlantic/Canary', '(UTC+0000) Atlantic/Canary'],
+  ['Atlantic/Faroe', '(UTC+0000) Atlantic/Faroe'],
+  ['Atlantic/Madeira', '(UTC+0000) Atlantic/Madeira'],
+  ['Atlantic/Reykjavik', '(UTC+0000) Atlantic/Reykjavik'],
+  ['Atlantic/St_Helena', '(UTC+0000) Atlantic/St_Helena'],
+  ['Europe/Dublin', '(UTC+0000) Europe/Dublin'],
+  ['Europe/Guernsey', '(UTC+0000) Europe/Guernsey'],
+  ['Europe/Isle_of_Man', '(UTC+0000) Europe/Isle_of_Man'],
+  ['Europe/Jersey', '(UTC+0000) Europe/Jersey'],
+  ['Europe/Lisbon', '(UTC+0000) Europe/Lisbon'],
+  ['Europe/London', '(UTC+0000) Europe/London'],
+  ['GMT', '(UTC+0000) GMT'],
+  ['UTC', '(UTC+0000) UTC'],
+  ['Africa/Algiers', '(UTC+0100) Africa/Algiers'],
+  ['Africa/Bangui', '(UTC+0100) Africa/Bangui'],
+  ['Africa/Brazzaville', '(UTC+0100) Africa/Brazzaville'],
+  ['Africa/Ceuta', '(UTC+0100) Africa/Ceuta'],
+  ['Africa/Douala', '(UTC+0100) Africa/Douala'],
+  ['Africa/Kinshasa', '(UTC+0100) Africa/Kinshasa'],
+  ['Africa/Lagos', '(UTC+0100) Africa/Lagos'],
+  ['Africa/Libreville', '(UTC+0100) Africa/Libreville'],
+  ['Africa/Luanda', '(UTC+0100) Africa/Luanda'],
+  ['Africa/Malabo', '(UTC+0100) Africa/Malabo'],
+  ['Africa/Ndjamena', '(UTC+0100) Africa/Ndjamena'],
+  ['Africa/Niamey', '(UTC+0100) Africa/Niamey'],
+  ['Africa/Porto-Novo', '(UTC+0100) Africa/Porto-Novo'],
+  ['Africa/Tunis', '(UTC+0100) Africa/Tunis'],
+  ['Arctic/Longyearbyen', '(UTC+0100) Arctic/Longyearbyen'],
+  ['Europe/Amsterdam', '(UTC+0100) Europe/Amsterdam'],
+  ['Europe/Andorra', '(UTC+0100) Europe/Andorra'],
+  ['Europe/Belgrade', '(UTC+0100) Europe/Belgrade'],
+  ['Europe/Berlin', '(UTC+0100) Europe/Berlin'],
+  ['Europe/Bratislava', '(UTC+0100) Europe/Bratislava'],
+  ['Europe/Brussels', '(UTC+0100) Europe/Brussels'],
+  ['Europe/Budapest', '(UTC+0100) Europe/Budapest'],
+  ['Europe/Busingen', '(UTC+0100) Europe/Busingen'],
+  ['Europe/Copenhagen', '(UTC+0100) Europe/Copenhagen'],
+  ['Europe/Gibraltar', '(UTC+0100) Europe/Gibraltar'],
+  ['Europe/Ljubljana', '(UTC+0100) Europe/Ljubljana'],
+  ['Europe/Luxembourg', '(UTC+0100) Europe/Luxembourg'],
+  ['Europe/Madrid', '(UTC+0100) Europe/Madrid'],
+  ['Europe/Malta', '(UTC+0100) Europe/Malta'],
+  ['Europe/Monaco', '(UTC+0100) Europe/Monaco'],
+  ['Europe/Oslo', '(UTC+0100) Europe/Oslo'],
+  ['Europe/Paris', '(UTC+0100) Europe/Paris'],
+  ['Europe/Podgorica', '(UTC+0100) Europe/Podgorica'],
+  ['Europe/Prague', '(UTC+0100) Europe/Prague'],
+  ['Europe/Rome', '(UTC+0100) Europe/Rome'],
+  ['Europe/San_Marino', '(UTC+0100) Europe/San_Marino'],
+  ['Europe/Sarajevo', '(UTC+0100) Europe/Sarajevo'],
+  ['Europe/Skopje', '(UTC+0100) Europe/Skopje'],
+  ['Europe/Stockholm', '(UTC+0100) Europe/Stockholm'],
+  ['Europe/Tirane', '(UTC+0100) Europe/Tirane'],
+  ['Europe/Vaduz', '(UTC+0100) Europe/Vaduz'],
+  ['Europe/Vatican', '(UTC+0100) Europe/Vatican'],
+  ['Europe/Vienna', '(UTC+0100) Europe/Vienna'],
+  ['Europe/Warsaw', '(UTC+0100) Europe/Warsaw'],
+  ['Europe/Zagreb', '(UTC+0100) Europe/Zagreb'],
+  ['Europe/Zurich', '(UTC+0100) Europe/Zurich'],
+  ['Africa/Blantyre', '(UTC+0200) Africa/Blantyre'],
+  ['Africa/Bujumbura', '(UTC+0200) Africa/Bujumbura'],
+  ['Africa/Cairo', '(UTC+0200) Africa/Cairo'],
+  ['Africa/Gaborone', '(UTC+0200) Africa/Gaborone'],
+  ['Africa/Harare', '(UTC+0200) Africa/Harare'],
+  ['Africa/Johannesburg', '(UTC+0200) Africa/Johannesburg'],
+  ['Africa/Kigali', '(UTC+0200) Africa/Kigali'],
+  ['Africa/Lubumbashi', '(UTC+0200) Africa/Lubumbashi'],
+  ['Africa/Lusaka', '(UTC+0200) Africa/Lusaka'],
+  ['Africa/Maputo', '(UTC+0200) Africa/Maputo'],
+  ['Africa/Maseru', '(UTC+0200) Africa/Maseru'],
+  ['Africa/Mbabane', '(UTC+0200) Africa/Mbabane'],
+  ['Africa/Tripoli', '(UTC+0200) Africa/Tripoli'],
+  ['Africa/Windhoek', '(UTC+0200) Africa/Windhoek'],
+  ['Asia/Amman', '(UTC+0200) Asia/Amman'],
+  ['Asia/Beirut', '(UTC+0200) Asia/Beirut'],
+  ['Asia/Damascus', '(UTC+0200) Asia/Damascus'],
+  ['Asia/Gaza', '(UTC+0200) Asia/Gaza'],
+  ['Asia/Hebron', '(UTC+0200) Asia/Hebron'],
+  ['Asia/Jerusalem', '(UTC+0200) Asia/Jerusalem'],
+  ['Asia/Nicosia', '(UTC+0200) Asia/Nicosia'],
+  ['Europe/Athens', '(UTC+0200) Europe/Athens'],
+  ['Europe/Bucharest', '(UTC+0200) Europe/Bucharest'],
+  ['Europe/Chisinau', '(UTC+0200) Europe/Chisinau'],
+  ['Europe/Helsinki', '(UTC+0200) Europe/Helsinki'],
+  ['Europe/Istanbul', '(UTC+0200) Europe/Istanbul'],
+  ['Europe/Kaliningrad', '(UTC+0200) Europe/Kaliningrad'],
+  ['Europe/Kiev', '(UTC+0200) Europe/Kiev'],
+  ['Europe/Mariehamn', '(UTC+0200) Europe/Mariehamn'],
+  ['Europe/Riga', '(UTC+0200) Europe/Riga'],
+  ['Europe/Sofia', '(UTC+0200) Europe/Sofia'],
+  ['Europe/Tallinn', '(UTC+0200) Europe/Tallinn'],
+  ['Europe/Uzhgorod', '(UTC+0200) Europe/Uzhgorod'],
+  ['Europe/Vilnius', '(UTC+0200) Europe/Vilnius'],
+  ['Europe/Zaporozhye', '(UTC+0200) Europe/Zaporozhye'],
+  ['Africa/Addis_Ababa', '(UTC+0300) Africa/Addis_Ababa'],
+  ['Africa/Asmara', '(UTC+0300) Africa/Asmara'],
+  ['Africa/Dar_es_Salaam', '(UTC+0300) Africa/Dar_es_Salaam'],
+  ['Africa/Djibouti', '(UTC+0300) Africa/Djibouti'],
+  ['Africa/Juba', '(UTC+0300) Africa/Juba'],
+  ['Africa/Kampala', '(UTC+0300) Africa/Kampala'],
+  ['Africa/Khartoum', '(UTC+0300) Africa/Khartoum'],
+  ['Africa/Mogadishu', '(UTC+0300) Africa/Mogadishu'],
+  ['Africa/Nairobi', '(UTC+0300) Africa/Nairobi'],
+  ['Antarctica/Syowa', '(UTC+0300) Antarctica/Syowa'],
+  ['Asia/Aden', '(UTC+0300) Asia/Aden'],
+  ['Asia/Baghdad', '(UTC+0300) Asia/Baghdad'],
+  ['Asia/Bahrain', '(UTC+0300) Asia/Bahrain'],
+  ['Asia/Kuwait', '(UTC+0300) Asia/Kuwait'],
+  ['Asia/Qatar', '(UTC+0300) Asia/Qatar'],
+  ['Asia/Riyadh', '(UTC+0300) Asia/Riyadh'],
+  ['Europe/Minsk', '(UTC+0300) Europe/Minsk'],
+  ['Europe/Moscow', '(UTC+0300) Europe/Moscow'],
+  ['Europe/Simferopol', '(UTC+0300) Europe/Simferopol'],
+  ['Europe/Volgograd', '(UTC+0300) Europe/Volgograd'],
+  ['Indian/Antananarivo', '(UTC+0300) Indian/Antananarivo'],
+  ['Indian/Comoro', '(UTC+0300) Indian/Comoro'],
+  ['Indian/Mayotte', '(UTC+0300) Indian/Mayotte'],
+  ['Asia/Tehran', '(UTC+0330) Asia/Tehran'],
+  ['Asia/Baku', '(UTC+0400) Asia/Baku'],
+  ['Asia/Dubai', '(UTC+0400) Asia/Dubai'],
+  ['Asia/Muscat', '(UTC+0400) Asia/Muscat'],
+  ['Asia/Tbilisi', '(UTC+0400) Asia/Tbilisi'],
+  ['Asia/Yerevan', '(UTC+0400) Asia/Yerevan'],
+  ['Europe/Samara', '(UTC+0400) Europe/Samara'],
+  ['Indian/Mahe', '(UTC+0400) Indian/Mahe'],
+  ['Indian/Mauritius', '(UTC+0400) Indian/Mauritius'],
+  ['Indian/Reunion', '(UTC+0400) Indian/Reunion'],
+  ['Asia/Kabul', '(UTC+0430) Asia/Kabul'],
+  ['Antarctica/Mawson', '(UTC+0500) Antarctica/Mawson'],
+  ['Asia/Aqtau', '(UTC+0500) Asia/Aqtau'],
+  ['Asia/Aqtobe', '(UTC+0500) Asia/Aqtobe'],
+  ['Asia/Ashgabat', '(UTC+0500) Asia/Ashgabat'],
+  ['Asia/Dushanbe', '(UTC+0500) Asia/Dushanbe'],
+  ['Asia/Karachi', '(UTC+0500) Asia/Karachi'],
+  ['Asia/Oral', '(UTC+0500) Asia/Oral'],
+  ['Asia/Samarkand', '(UTC+0500) Asia/Samarkand'],
+  ['Asia/Tashkent', '(UTC+0500) Asia/Tashkent'],
+  ['Asia/Yekaterinburg', '(UTC+0500) Asia/Yekaterinburg'],
+  ['Indian/Kerguelen', '(UTC+0500) Indian/Kerguelen'],
+  ['Indian/Maldives', '(UTC+0500) Indian/Maldives'],
+  ['Asia/Colombo', '(UTC+0530) Asia/Colombo'],
+  ['Asia/Kolkata', '(UTC+0530) Asia/Kolkata'],
+  ['Asia/Kathmandu', '(UTC+0545) Asia/Kathmandu'],
+  ['Antarctica/Vostok', '(UTC+0600) Antarctica/Vostok'],
+  ['Asia/Almaty', '(UTC+0600) Asia/Almaty'],
+  ['Asia/Bishkek', '(UTC+0600) Asia/Bishkek'],
+  ['Asia/Dhaka', '(UTC+0600) Asia/Dhaka'],
+  ['Asia/Novosibirsk', '(UTC+0600) Asia/Novosibirsk'],
+  ['Asia/Omsk', '(UTC+0600) Asia/Omsk'],
+  ['Asia/Qyzylorda', '(UTC+0600) Asia/Qyzylorda'],
+  ['Asia/Thimphu', '(UTC+0600) Asia/Thimphu'],
+  ['Asia/Urumqi', '(UTC+0600) Asia/Urumqi'],
+  ['Indian/Chagos', '(UTC+0600) Indian/Chagos'],
+  ['Asia/Rangoon', '(UTC+0630) Asia/Rangoon'],
+  ['Indian/Cocos', '(UTC+0630) Indian/Cocos'],
+  ['Antarctica/Davis', '(UTC+0700) Antarctica/Davis'],
+  ['Asia/Bangkok', '(UTC+0700) Asia/Bangkok'],
+  ['Asia/Ho_Chi_Minh', '(UTC+0700) Asia/Ho_Chi_Minh'],
+  ['Asia/Hovd', '(UTC+0700) Asia/Hovd'],
+  ['Asia/Jakarta', '(UTC+0700) Asia/Jakarta'],
+  ['Asia/Krasnoyarsk', '(UTC+0700) Asia/Krasnoyarsk'],
+  ['Asia/Novokuznetsk', '(UTC+0700) Asia/Novokuznetsk'],
+  ['Asia/Phnom_Penh', '(UTC+0700) Asia/Phnom_Penh'],
+  ['Asia/Pontianak', '(UTC+0700) Asia/Pontianak'],
+  ['Asia/Vientiane', '(UTC+0700) Asia/Vientiane'],
+  ['Indian/Christmas', '(UTC+0700) Indian/Christmas'],
+  ['Antarctica/Casey', '(UTC+0800) Antarctica/Casey'],
+  ['Asia/Brunei', '(UTC+0800) Asia/Brunei'],
+  ['Asia/Choibalsan', '(UTC+0800) Asia/Choibalsan'],
+  ['Asia/Hong_Kong', '(UTC+0800) Asia/Hong_Kong'],
+  ['Asia/Irkutsk', '(UTC+0800) Asia/Irkutsk'],
+  ['Asia/Kuala_Lumpur', '(UTC+0800) Asia/Kuala_Lumpur'],
+  ['Asia/Kuching', '(UTC+0800) Asia/Kuching'],
+  ['Asia/Macau', '(UTC+0800) Asia/Macau'],
+  ['Asia/Makassar', '(UTC+0800) Asia/Makassar'],
+  ['Asia/Manila', '(UTC+0800) Asia/Manila'],
+  ['Asia/Shanghai', '(UTC+0800) Asia/Shanghai'],
+  ['Asia/Singapore', '(UTC+0800) Asia/Singapore'],
+  ['Asia/Taipei', '(UTC+0800) Asia/Taipei'],
+  ['Asia/Ulaanbaatar', '(UTC+0800) Asia/Ulaanbaatar'],
+  ['Australia/Perth', '(UTC+0800) Australia/Perth'],
+  ['Asia/Pyongyang', '(UTC+0830) Asia/Pyongyang'],
+  ['Australia/Eucla', '(UTC+0845) Australia/Eucla'],
+  ['Asia/Chita', '(UTC+0900) Asia/Chita'],
+  ['Asia/Dili', '(UTC+0900) Asia/Dili'],
+  ['Asia/Jayapura', '(UTC+0900) Asia/Jayapura'],
+  ['Asia/Khandyga', '(UTC+0900) Asia/Khandyga'],
+  ['Asia/Seoul', '(UTC+0900) Asia/Seoul'],
+  ['Asia/Tokyo', '(UTC+0900) Asia/Tokyo'],
+  ['Asia/Yakutsk', '(UTC+0900) Asia/Yakutsk'],
+  ['Pacific/Palau', '(UTC+0900) Pacific/Palau'],
+  ['Australia/Darwin', '(UTC+0930) Australia/Darwin'],
+  ['Antarctica/DumontDUrville', '(UTC+1000) Antarctica/DumontDUrville'],
+  ['Asia/Magadan', '(UTC+1000) Asia/Magadan'],
+  ['Asia/Sakhalin', '(UTC+1000) Asia/Sakhalin'],
+  ['Asia/Ust-Nera', '(UTC+1000) Asia/Ust-Nera'],
+  ['Asia/Vladivostok', '(UTC+1000) Asia/Vladivostok'],
+  ['Australia/Brisbane', '(UTC+1000) Australia/Brisbane'],
+  ['Australia/Lindeman', '(UTC+1000) Australia/Lindeman'],
+  ['Pacific/Chuuk', '(UTC+1000) Pacific/Chuuk'],
+  ['Pacific/Guam', '(UTC+1000) Pacific/Guam'],
+  ['Pacific/Port_Moresby', '(UTC+1000) Pacific/Port_Moresby'],
+  ['Pacific/Saipan', '(UTC+1000) Pacific/Saipan'],
+  ['Australia/Adelaide', '(UTC+1030) Australia/Adelaide'],
+  ['Australia/Broken_Hill', '(UTC+1030) Australia/Broken_Hill'],
+  ['Antarctica/Macquarie', '(UTC+1100) Antarctica/Macquarie'],
+  ['Asia/Srednekolymsk', '(UTC+1100) Asia/Srednekolymsk'],
+  ['Australia/Currie', '(UTC+1100) Australia/Currie'],
+  ['Australia/Hobart', '(UTC+1100) Australia/Hobart'],
+  ['Australia/Lord_Howe', '(UTC+1100) Australia/Lord_Howe'],
+  ['Australia/Melbourne', '(UTC+1100) Australia/Melbourne'],
+  ['Australia/Sydney', '(UTC+1100) Australia/Sydney'],
+  ['Pacific/Bougainville', '(UTC+1100) Pacific/Bougainville'],
+  ['Pacific/Efate', '(UTC+1100) Pacific/Efate'],
+  ['Pacific/Guadalcanal', '(UTC+1100) Pacific/Guadalcanal'],
+  ['Pacific/Kosrae', '(UTC+1100) Pacific/Kosrae'],
+  ['Pacific/Norfolk', '(UTC+1100) Pacific/Norfolk'],
+  ['Pacific/Noumea', '(UTC+1100) Pacific/Noumea'],
+  ['Pacific/Pohnpei', '(UTC+1100) Pacific/Pohnpei'],
+  ['Asia/Anadyr', '(UTC+1200) Asia/Anadyr'],
+  ['Asia/Kamchatka', '(UTC+1200) Asia/Kamchatka'],
+  ['Pacific/Funafuti', '(UTC+1200) Pacific/Funafuti'],
+  ['Pacific/Kwajalein', '(UTC+1200) Pacific/Kwajalein'],
+  ['Pacific/Majuro', '(UTC+1200) Pacific/Majuro'],
+  ['Pacific/Nauru', '(UTC+1200) Pacific/Nauru'],
+  ['Pacific/Tarawa', '(UTC+1200) Pacific/Tarawa'],
+  ['Pacific/Wake', '(UTC+1200) Pacific/Wake'],
+  ['Pacific/Wallis', '(UTC+1200) Pacific/Wallis'],
+  ['Antarctica/McMurdo', '(UTC+1300) Antarctica/McMurdo'],
+  ['Pacific/Auckland', '(UTC+1300) Pacific/Auckland'],
+  ['Pacific/Enderbury', '(UTC+1300) Pacific/Enderbury'],
+  ['Pacific/Fakaofo', '(UTC+1300) Pacific/Fakaofo'],
+  ['Pacific/Fiji', '(UTC+1300) Pacific/Fiji'],
+  ['Pacific/Tongatapu', '(UTC+1300) Pacific/Tongatapu'],
+  ['Pacific/Chatham', '(UTC+1345) Pacific/Chatham'],
+  ['Pacific/Apia', '(UTC+1400) Pacific/Apia'],
+  ['Pacific/Kiritimati', '(UTC+1400) Pacific/Kiritimati'],
+];

+ 8 - 0
src/sentry/static/sentry/app/routes.jsx

@@ -148,6 +148,14 @@ const accountSettingsRoutes = [
     name="Avatar"
     component={errorHandler(AccountAvatar)}
   />,
+
+  <Route
+    key="appearance/"
+    path="appearance/"
+    name="Appearance"
+    componentPromise={() => import('./views/settings/account/accountAppearance')}
+    component={errorHandler(LazyLoad)}
+  />,
 ];
 
 const projectSettingsRoutes = [

+ 35 - 0
src/sentry/static/sentry/app/views/settings/account/accountAppearance.jsx

@@ -0,0 +1,35 @@
+import {Box} from 'grid-emotion';
+import React from 'react';
+
+import AsyncView from '../../asyncView';
+import Form from '../components/forms/form';
+import JsonForm from '../components/forms/jsonForm';
+import formData from '../../../data/forms/accountAppearance';
+
+const ENDPOINT = '/users/me/appearance/';
+
+class AccountAppearance extends AsyncView {
+  getEndpoints() {
+    return [['appearance', ENDPOINT]];
+  }
+
+  renderBody() {
+    return (
+      <div>
+        <Form
+          apiMethod="PUT"
+          apiEndpoint={ENDPOINT}
+          saveOnBlur
+          allowUndo
+          initialData={this.state.appearance}
+        >
+          <Box>
+            <JsonForm location={this.props.location} forms={formData} />
+          </Box>
+        </Form>
+      </div>
+    );
+  }
+}
+
+export default AccountAppearance;

+ 4 - 0
src/sentry/static/sentry/app/views/settings/account/navigationConfiguration.jsx

@@ -10,6 +10,10 @@ const accountNavigation = [
         path: `${pathPrefix}/avatar`,
         title: t('Avatar'),
       },
+      {
+        path: `${pathPrefix}/appearance/`,
+        title: t('Appearance'),
+      },
       {
         path: `${pathPrefix}/notifications/`,
         title: t('Notifications'),

Some files were not shown because too many files changed in this diff