From 6c49a628000605d1beb120431003abb329b9fd16 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rub=C3=A9n=20D=C3=A1vila?= <ruben@gitlab.com>
Date: Thu, 17 Aug 2017 10:37:36 -0500
Subject: [PATCH] Restore some changes from !9199

---
 app/controllers/admin/users_controller.rb     |  1 +
 .../profiles/preferences_controller.rb        |  3 +-
 app/helpers/preferences_helper.rb             |  4 +
 app/models/user.rb                            |  1 +
 config/gitlab.yml.example                     | 10 ++-
 config/initializers/1_settings.rb             |  1 +
 doc/api/keys.md                               |  1 +
 doc/api/session.md                            |  1 +
 doc/api/users.md                              |  5 ++
 lib/api/entities.rb                           |  2 +-
 lib/gitlab/themes.rb                          | 87 +++++++++++++++++++
 .../profiles/preferences_controller_spec.rb   |  6 +-
 .../api/schemas/public_api/v4/user/login.json |  1 +
 spec/helpers/preferences_helper_spec.rb       | 26 ++++++
 spec/lib/gitlab/themes_spec.rb                | 48 ++++++++++
 spec/models/user_spec.rb                      |  2 +
 spec/support/gitlab_stubs/session.json        |  2 +-
 spec/support/gitlab_stubs/user.json           |  4 +-
 18 files changed, 197 insertions(+), 8 deletions(-)
 create mode 100644 lib/gitlab/themes.rb
 create mode 100644 spec/lib/gitlab/themes_spec.rb

diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index a99563b7100d7..635298bc24a30 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -211,6 +211,7 @@ def user_params_ce
       :provider,
       :remember_me,
       :skype,
+      :theme_id,
       :twitter,
       :username,
       :website_url
diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb
index 1e557c4763844..cce2a847b5389 100644
--- a/app/controllers/profiles/preferences_controller.rb
+++ b/app/controllers/profiles/preferences_controller.rb
@@ -35,7 +35,8 @@ def preferences_params
       :color_scheme_id,
       :layout,
       :dashboard,
-      :project_view
+      :project_view,
+      :theme_id
     )
   end
 end
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index d36bb4ab07495..64605908c055d 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -40,6 +40,10 @@ def project_view_choices
     ]
   end
 
+  def user_application_theme
+    Gitlab::Themes.for_user(current_user).css_class
+  end
+
   def user_color_scheme
     Gitlab::ColorSchemes.for_user(current_user).css_class
   end
diff --git a/app/models/user.rb b/app/models/user.rb
index 105eb62f1fa9b..f75cc21c65c8d 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -35,6 +35,7 @@ class User < ActiveRecord::Base
   default_value_for :project_view, :files
   default_value_for :notified_of_own_activity, false
   default_value_for :preferred_language, I18n.default_locale
+  default_value_for :theme_id, gitlab_config.default_theme
 
   attr_encrypted :otp_secret,
     key:       Gitlab::Application.secrets.otp_key_base,
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index e96610908448e..1ca0f263f133b 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -76,6 +76,14 @@ production: &base
 
     # default_can_create_group: false  # default: true
     # username_changing_enabled: false # default: true - User can change her username/namespace
+    ## Default theme ID
+    ##   1 - Graphite
+    ##   2 - Charcoal
+    ##   3 - Green
+    ##   4 - Gray
+    ##   5 - Violet
+    ##   6 - Blue
+    # default_theme: 2 # default: 2
 
     ## Automatic issue closing
     # If a commit message matches this regular expression, all issues referenced from the matched text will be closed.
@@ -741,4 +749,4 @@ test:
         admin_group: ''
 
 staging:
-  <<: *base
+  <<: *base
\ No newline at end of file
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 7c1ca05a57b7e..40fbdd3ef9b74 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -232,6 +232,7 @@ def cron_random_weekly_time
 Settings.gitlab['default_projects_limit'] ||= 100000
 Settings.gitlab['default_branch_protection'] ||= 2
 Settings.gitlab['default_can_create_group'] = true if Settings.gitlab['default_can_create_group'].nil?
+Settings.gitlab['default_theme'] = Gitlab::Themes::APPLICATION_DEFAULT if Settings.gitlab['default_theme'].nil?
 Settings.gitlab['host']       ||= ENV['GITLAB_HOST'] || 'localhost'
 Settings.gitlab['ssh_host']   ||= Settings.gitlab.host
 Settings.gitlab['https']        = false if Settings.gitlab['https'].nil?
diff --git a/doc/api/keys.md b/doc/api/keys.md
index 376ac27df3a2e..ddcf783062144 100644
--- a/doc/api/keys.md
+++ b/doc/api/keys.md
@@ -32,6 +32,7 @@ Parameters:
     "twitter": "",
     "website_url": "",
     "email": "john@example.com",
+    "theme_id": 2,
     "color_scheme_id": 1,
     "projects_limit": 10,
     "current_sign_in_at": null,
diff --git a/doc/api/session.md b/doc/api/session.md
index f79eac11689f4..b97e26f34a2c7 100644
--- a/doc/api/session.md
+++ b/doc/api/session.md
@@ -39,6 +39,7 @@ Example response:
   "twitter": "",
   "website_url": "",
   "email": "john@example.com",
+  "theme_id": 1,
   "color_scheme_id": 1,
   "projects_limit": 10,
   "current_sign_in_at": "2015-07-07T07:10:58.392Z",
diff --git a/doc/api/users.md b/doc/api/users.md
index 9f3e4caf2f476..6d5db16b36ad1 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -72,6 +72,7 @@ GET /users
     "organization": "",
     "last_sign_in_at": "2012-06-01T11:41:01Z",
     "confirmed_at": "2012-05-23T09:05:22Z",
+    "theme_id": 1,
     "last_activity_on": "2012-05-23",
     "color_scheme_id": 2,
     "projects_limit": 100,
@@ -105,6 +106,7 @@ GET /users
     "organization": "",
     "last_sign_in_at": null,
     "confirmed_at": "2012-05-30T16:53:06.148Z",
+    "theme_id": 1,
     "last_activity_on": "2012-05-23",
     "color_scheme_id": 3,
     "projects_limit": 100,
@@ -215,6 +217,7 @@ Parameters:
   "organization": "",
   "last_sign_in_at": "2012-06-01T11:41:01Z",
   "confirmed_at": "2012-05-23T09:05:22Z",
+  "theme_id": 1,
   "last_activity_on": "2012-05-23",
   "color_scheme_id": 2,
   "projects_limit": 100,
@@ -341,6 +344,7 @@ GET /user
   "organization": "",
   "last_sign_in_at": "2012-06-01T11:41:01Z",
   "confirmed_at": "2012-05-23T09:05:22Z",
+  "theme_id": 1,
   "last_activity_on": "2012-05-23",
   "color_scheme_id": 2,
   "projects_limit": 100,
@@ -387,6 +391,7 @@ GET /user
   "organization": "",
   "last_sign_in_at": "2012-06-01T11:41:01Z",
   "confirmed_at": "2012-05-23T09:05:22Z",
+  "theme_id": 1,
   "last_activity_on": "2012-05-23",
   "color_scheme_id": 2,
   "projects_limit": 100,
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 1d224d7bc217c..d5f2c422c584c 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -35,7 +35,7 @@ class UserPublic < User
       expose :confirmed_at
       expose :last_activity_on
       expose :email
-      expose :color_scheme_id, :projects_limit, :current_sign_in_at
+      expose :theme_id, :color_scheme_id, :projects_limit, :current_sign_in_at
       expose :identities, using: Entities::Identity
       expose :can_create_group?, as: :can_create_group
       expose :can_create_project?, as: :can_create_project
diff --git a/lib/gitlab/themes.rb b/lib/gitlab/themes.rb
new file mode 100644
index 0000000000000..19ab76ae80f6a
--- /dev/null
+++ b/lib/gitlab/themes.rb
@@ -0,0 +1,87 @@
+module Gitlab
+  # Module containing GitLab's application theme definitions and helper methods
+  # for accessing them.
+  module Themes
+    extend self
+
+    # Theme ID used when no `default_theme` configuration setting is provided.
+    APPLICATION_DEFAULT = 2
+
+    # Struct class representing a single Theme
+    Theme = Struct.new(:id, :name, :css_class)
+
+    # All available Themes
+    THEMES = [
+      Theme.new(1, 'Graphite', 'ui_graphite'),
+      Theme.new(2, 'Charcoal', 'ui_charcoal'),
+      Theme.new(3, 'Green',    'ui_green'),
+      Theme.new(4, 'Black',    'ui_black'),
+      Theme.new(5, 'Violet',   'ui_violet'),
+      Theme.new(6, 'Blue',     'ui_blue')
+    ].freeze
+
+    # Convenience method to get a space-separated String of all the theme
+    # classes that might be applied to the `body` element
+    #
+    # Returns a String
+    def body_classes
+      THEMES.collect(&:css_class).uniq.join(' ')
+    end
+
+    # Get a Theme by its ID
+    #
+    # If the ID is invalid, returns the default Theme.
+    #
+    # id - Integer ID
+    #
+    # Returns a Theme
+    def by_id(id)
+      THEMES.detect { |t| t.id == id } || default
+    end
+
+    # Returns the number of defined Themes
+    def count
+      THEMES.size
+    end
+
+    # Get the default Theme
+    #
+    # Returns a Theme
+    def default
+      by_id(default_id)
+    end
+
+    # Iterate through each Theme
+    #
+    # Yields the Theme object
+    def each(&block)
+      THEMES.each(&block)
+    end
+
+    # Get the Theme for the specified user, or the default
+    #
+    # user - User record
+    #
+    # Returns a Theme
+    def for_user(user)
+      if user
+        by_id(user.theme_id)
+      else
+        default
+      end
+    end
+
+    private
+
+    def default_id
+      id = Gitlab.config.gitlab.default_theme.to_i
+
+      # Prevent an invalid configuration setting from causing an infinite loop
+      if id < THEMES.first.id || id > THEMES.last.id
+        APPLICATION_DEFAULT
+      else
+        id
+      end
+    end
+  end
+end
diff --git a/spec/controllers/profiles/preferences_controller_spec.rb b/spec/controllers/profiles/preferences_controller_spec.rb
index a5f544b4f9225..a66b4ab090224 100644
--- a/spec/controllers/profiles/preferences_controller_spec.rb
+++ b/spec/controllers/profiles/preferences_controller_spec.rb
@@ -25,7 +25,8 @@
     def go(params: {}, format: :js)
       params.reverse_merge!(
         color_scheme_id: '1',
-        dashboard: 'stars'
+        dashboard: 'stars',
+        theme_id: '1'
       )
 
       patch :update, user: params, format: format
@@ -40,7 +41,8 @@ def go(params: {}, format: :js)
       it "changes the user's preferences" do
         prefs = {
           color_scheme_id: '1',
-          dashboard: 'stars'
+          dashboard: 'stars',
+          theme_id: '2'
         }.with_indifferent_access
 
         expect(user).to receive(:assign_attributes).with(prefs)
diff --git a/spec/fixtures/api/schemas/public_api/v4/user/login.json b/spec/fixtures/api/schemas/public_api/v4/user/login.json
index 6181b3ccc8694..e6c1d9c9d845a 100644
--- a/spec/fixtures/api/schemas/public_api/v4/user/login.json
+++ b/spec/fixtures/api/schemas/public_api/v4/user/login.json
@@ -19,6 +19,7 @@
     "organization",
     "last_sign_in_at",
     "confirmed_at",
+    "theme_id",
     "color_scheme_id",
     "projects_limit",
     "current_sign_in_at",
diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb
index a04c87b08eb77..50ac3886a3627 100644
--- a/spec/helpers/preferences_helper_spec.rb
+++ b/spec/helpers/preferences_helper_spec.rb
@@ -26,6 +26,32 @@
     end
   end
 
+  describe 'user_application_theme' do
+    context 'with a user' do
+      it "returns user's theme's css_class" do
+        stub_user(theme_id: 3)
+
+        expect(helper.user_application_theme).to eq 'ui_green'
+      end
+
+      it 'returns the default when id is invalid' do
+        stub_user(theme_id: Gitlab::Themes.count + 5)
+
+        allow(Gitlab.config.gitlab).to receive(:default_theme).and_return(2)
+
+        expect(helper.user_application_theme).to eq 'ui_charcoal'
+      end
+    end
+
+    context 'without a user' do
+      it 'returns the default theme' do
+        stub_user
+
+        expect(helper.user_application_theme).to eq Gitlab::Themes.default.css_class
+      end
+    end
+  end
+
   describe 'user_color_scheme' do
     context 'with a user' do
       it "returns user's scheme's css_class" do
diff --git a/spec/lib/gitlab/themes_spec.rb b/spec/lib/gitlab/themes_spec.rb
new file mode 100644
index 0000000000000..7a140518dd244
--- /dev/null
+++ b/spec/lib/gitlab/themes_spec.rb
@@ -0,0 +1,48 @@
+require 'spec_helper'
+
+describe Gitlab::Themes, lib: true do
+  describe '.body_classes' do
+    it 'returns a space-separated list of class names' do
+      css = described_class.body_classes
+
+      expect(css).to include('ui_graphite')
+      expect(css).to include(' ui_charcoal ')
+      expect(css).to include(' ui_blue')
+    end
+  end
+
+  describe '.by_id' do
+    it 'returns a Theme by its ID' do
+      expect(described_class.by_id(1).name).to eq 'Graphite'
+      expect(described_class.by_id(6).name).to eq 'Blue'
+    end
+  end
+
+  describe '.default' do
+    it 'returns the default application theme' do
+      allow(described_class).to receive(:default_id).and_return(2)
+      expect(described_class.default.id).to eq 2
+    end
+
+    it 'prevents an infinite loop when configuration default is invalid' do
+      default = described_class::APPLICATION_DEFAULT
+      themes  = described_class::THEMES
+
+      config = double(default_theme: 0).as_null_object
+      allow(Gitlab).to receive(:config).and_return(config)
+      expect(described_class.default.id).to eq default
+
+      config = double(default_theme: themes.size + 5).as_null_object
+      allow(Gitlab).to receive(:config).and_return(config)
+      expect(described_class.default.id).to eq default
+    end
+  end
+
+  describe '.each' do
+    it 'passes the block to the THEMES Array' do
+      ids = []
+      described_class.each { |theme| ids << theme.id }
+      expect(ids).not_to be_empty
+    end
+  end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index abf732e60bf88..961f891f559d8 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -716,6 +716,7 @@
       it "applies defaults to user" do
         expect(user.projects_limit).to eq(Gitlab.config.gitlab.default_projects_limit)
         expect(user.can_create_group).to eq(Gitlab.config.gitlab.default_can_create_group)
+        expect(user.theme_id).to eq(Gitlab.config.gitlab.default_theme)
         expect(user.external).to be_falsey
       end
     end
@@ -726,6 +727,7 @@
       it "applies defaults to user" do
         expect(user.projects_limit).to eq(123)
         expect(user.can_create_group).to be_falsey
+        expect(user.theme_id).to eq(1)
       end
     end
 
diff --git a/spec/support/gitlab_stubs/session.json b/spec/support/gitlab_stubs/session.json
index cd55d63125e59..688175369ae5b 100644
--- a/spec/support/gitlab_stubs/session.json
+++ b/spec/support/gitlab_stubs/session.json
@@ -7,7 +7,7 @@
   "skype":"aertert",
   "linkedin":"",
   "twitter":"",
-  "color_scheme_id":2,
+  "theme_id":2,"color_scheme_id":2,
   "state":"active",
   "created_at":"2012-12-21T13:02:20Z",
   "extern_uid":null,
diff --git a/spec/support/gitlab_stubs/user.json b/spec/support/gitlab_stubs/user.json
index cd55d63125e59..ce8dfe5ae7559 100644
--- a/spec/support/gitlab_stubs/user.json
+++ b/spec/support/gitlab_stubs/user.json
@@ -7,7 +7,7 @@
   "skype":"aertert",
   "linkedin":"",
   "twitter":"",
-  "color_scheme_id":2,
+  "theme_id":2,"color_scheme_id":2,
   "state":"active",
   "created_at":"2012-12-21T13:02:20Z",
   "extern_uid":null,
@@ -17,4 +17,4 @@
   "can_create_project":false,
   "private_token":"Wvjy2Krpb7y8xi93owUz",
   "access_token":"Wvjy2Krpb7y8xi93owUz"
-}
+}
\ No newline at end of file
-- 
GitLab