summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG2
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee2
-rw-r--r--app/assets/javascripts/profile.js.coffee9
-rw-r--r--app/assets/stylesheets/application.scss16
-rw-r--r--app/assets/stylesheets/pages/profile.scss61
-rw-r--r--app/assets/stylesheets/pages/profiles/preferences.scss56
-rw-r--r--app/assets/stylesheets/pages/themes.scss0
-rw-r--r--app/assets/stylesheets/themes/gitlab-theme.scss41
-rw-r--r--app/assets/stylesheets/themes/ui_basic.scss8
-rw-r--r--app/assets/stylesheets/themes/ui_blue.scss6
-rw-r--r--app/assets/stylesheets/themes/ui_color.scss6
-rw-r--r--app/assets/stylesheets/themes/ui_gray.scss6
-rw-r--r--app/assets/stylesheets/themes/ui_mars.scss6
-rw-r--r--app/assets/stylesheets/themes/ui_modern.scss6
-rw-r--r--app/controllers/profiles/preferences_controller.rb38
-rw-r--r--app/controllers/profiles_controller.rb23
-rw-r--r--app/controllers/root_controller.rb28
-rw-r--r--app/helpers/application_helper.rb32
-rw-r--r--app/helpers/gitlab_markdown_helper.rb1
-rw-r--r--app/helpers/preferences_helper.rb53
-rw-r--r--app/models/user.rb7
-rw-r--r--app/views/layouts/application.html.haml6
-rw-r--r--app/views/layouts/devise.html.haml2
-rw-r--r--app/views/layouts/errors.html.haml2
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml4
-rw-r--r--app/views/layouts/nav/_profile.html.haml7
-rw-r--r--app/views/profiles/design.html.haml56
-rw-r--r--app/views/profiles/preferences/show.html.haml42
-rw-r--r--app/views/profiles/preferences/update.js.erb9
-rw-r--r--app/views/profiles/update.js.erb3
-rw-r--r--config/gitlab.yml.example13
-rw-r--r--config/initializers/1_settings.rb2
-rw-r--r--config/routes.rb4
-rw-r--r--db/fixtures/production/001_admin.rb2
-rw-r--r--db/migrate/20150610065936_add_dashboard_to_users.rb9
-rw-r--r--db/schema.rb3
-rw-r--r--doc/README.md71
-rw-r--r--doc/profile/2fa.png (renamed from doc/workflow/2fa.png)bin23415 -> 23415 bytes
-rw-r--r--doc/profile/2fa_auth.png (renamed from doc/workflow/2fa_auth.png)bin15569 -> 15569 bytes
-rw-r--r--doc/profile/README.md4
-rw-r--r--doc/profile/preferences.md32
-rw-r--r--doc/profile/two_factor_authentication.md (renamed from doc/workflow/two_factor_authentication.md)0
-rw-r--r--doc/workflow/README.md31
-rw-r--r--features/profile/active_tab.feature6
-rw-r--r--features/profile/profile.feature13
-rw-r--r--features/steps/profile/active_tab.rb4
-rw-r--r--features/steps/profile/profile.rb21
-rw-r--r--features/steps/shared/paths.rb4
-rw-r--r--lib/gitlab/theme.rb50
-rw-r--r--lib/gitlab/themes.rb67
-rw-r--r--spec/controllers/profiles/preferences_controller_spec.rb88
-rw-r--r--spec/controllers/root_controller_spec.rb32
-rw-r--r--spec/features/profiles/preferences_spec.rb82
-rw-r--r--spec/features/security/profile_access_spec.rb4
-rw-r--r--spec/helpers/application_helper_spec.rb21
-rw-r--r--spec/helpers/preferences_helper_spec.rb72
-rw-r--r--spec/lib/gitlab/themes_spec.rb51
-rw-r--r--spec/models/user_spec.rb7
-rw-r--r--spec/routing/routing_spec.rb25
-rw-r--r--spec/support/capybara.rb33
-rw-r--r--spec/support/login_helpers.rb24
61 files changed, 901 insertions, 412 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 2d41e455271..36f6f84cbd3 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -2,6 +2,8 @@ Please view this file on the master branch, on stable branches it's out of date.
v 7.13.0 (unreleased)
- Remove project visibility icons from dashboard projects list
+ - Rename "Design" profile settings page to "Preferences".
+ - Allow users to customize their default Dashboard page.
v 7.12.0 (unreleased)
- Fix post-receive errors on a push when an external issue tracker is configured (Stan Hu)
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index da56e3cdbc8..b7ebe6a5c89 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -55,7 +55,7 @@ class Dispatcher
when 'projects:merge_requests:index'
shortcut_handler = new ShortcutsNavigation()
MergeRequests.init()
- when 'dashboard:show'
+ when 'dashboard:show', 'root:show'
new Dashboard()
new Activities()
when 'dashboard:projects:starred'
diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee
index 40459a9a155..bb0b66b86e1 100644
--- a/app/assets/javascripts/profile.js.coffee
+++ b/app/assets/javascripts/profile.js.coffee
@@ -1,10 +1,8 @@
class @Profile
constructor: ->
- $('.edit_user .application-theme input, .edit_user .code-preview-theme input').click ->
- # Submit the form
- $('.edit_user').submit()
-
- new Flash("Appearance settings saved", "notice")
+ # Automatically submit the Preferences form when any of its radio buttons change
+ $('.js-preferences-form').on 'change.preference', 'input[type=radio]', ->
+ $(this).parents('form').submit()
$('.update-username form').on 'ajax:before', ->
$('.loading-gif').show()
@@ -18,7 +16,6 @@ class @Profile
$('.update-notifications').on 'ajax:complete', ->
$(this).find('.btn-save').enable()
-
$('.js-choose-user-avatar-button').bind "click", ->
form = $(this).closest("form")
form.find(".js-user-avatar-input").click()
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 015ff2ce4ec..1a5f11df7d1 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -36,25 +36,25 @@
@import "font-awesome";
/**
+ * UI themes:
+ */
+@import "themes/**/*";
+
+/**
* Generic css (forms, nav etc):
*/
-@import "generic/*";
+@import "generic/**/*";
/**
* Page specific styles (issues, projects etc):
*/
-@import "pages/*";
+@import "pages/**/*";
/**
* Code highlight
*/
-@import "highlight/*";
-
-/**
- * UI themes:
- */
-@import "themes/*";
+@import "highlight/**/*";
/**
* Styles for JS behaviors.
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 5a5fbc468a3..8e4f0eb2b25 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -17,67 +17,6 @@
}
}
-/*
- * Appearance settings
- *
- */
-.themes_opts {
- label {
- margin-right: 20px;
- text-align: center;
-
- .prev {
- height: 80px;
- width: 160px;
- margin-bottom: 10px;
- @include border-radius(4px);
-
- &.classic {
- background: #31363e;
- }
-
- &.default {
- background: #888888;
- }
-
- &.modern {
- background: #009871;
- }
-
- &.gray {
- background: #373737;
- }
-
- &.violet {
- background: #548;
- }
-
- &.blue {
- background: #2980b9;
- }
- }
- }
-}
-
-.code_highlight_opts {
- margin-top: 10px;
-
- label {
- margin-right: 20px;
- text-align: center;
-
- .prev {
- width: 160px;
- margin-bottom: 10px;
-
- img {
- max-width: 100%;
- @include border-radius(4px);
- }
- }
- }
-}
-
.oauth-buttons {
.btn-group {
margin-right: 10px;
diff --git a/app/assets/stylesheets/pages/profiles/preferences.scss b/app/assets/stylesheets/pages/profiles/preferences.scss
new file mode 100644
index 00000000000..e5859fe7384
--- /dev/null
+++ b/app/assets/stylesheets/pages/profiles/preferences.scss
@@ -0,0 +1,56 @@
+.application-theme {
+ label {
+ margin-right: 20px;
+ text-align: center;
+
+ .preview {
+ @include border-radius(4px);
+
+ height: 80px;
+ margin-bottom: 10px;
+ width: 160px;
+
+ &.ui_blue {
+ background: $theme-blue;
+ }
+
+ &.ui_charcoal {
+ background: $theme-charcoal;
+ }
+
+ &.ui_graphite {
+ background: $theme-graphite;
+ }
+
+ &.ui_gray {
+ background: $theme-gray;
+ }
+
+ &.ui_green {
+ background: $theme-green;
+ }
+
+ &.ui_violet {
+ background: $theme-violet;
+ }
+ }
+ }
+}
+
+.syntax-theme {
+ label {
+ margin-right: 20px;
+ text-align: center;
+
+ .preview {
+ margin-bottom: 10px;
+ width: 160px;
+
+ img {
+ @include border-radius(4px);
+
+ max-width: 100%;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/themes.scss b/app/assets/stylesheets/pages/themes.scss
deleted file mode 100644
index e69de29bb2d..00000000000
--- a/app/assets/stylesheets/pages/themes.scss
+++ /dev/null
diff --git a/app/assets/stylesheets/themes/gitlab-theme.scss b/app/assets/stylesheets/themes/gitlab-theme.scss
index 10fcaf18fa9..7cabeaefb93 100644
--- a/app/assets/stylesheets/themes/gitlab-theme.scss
+++ b/app/assets/stylesheets/themes/gitlab-theme.scss
@@ -1,3 +1,11 @@
+/**
+ * Styles the GitLab application with a specific color theme
+ *
+ * $color-light -
+ * $color -
+ * $color-darker -
+ * $color-dark -
+ */
@mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) {
header {
&.navbar-gitlab {
@@ -77,3 +85,36 @@
}
}
}
+
+$theme-blue: #2980B9;
+$theme-charcoal: #474D57;
+$theme-graphite: #888888;
+$theme-gray: #373737;
+$theme-green: #019875;
+$theme-violet: #554488;
+
+body {
+ &.ui_blue {
+ @include gitlab-theme(#BECDE9, $theme-blue, #1970A9, #096099);
+ }
+
+ &.ui_charcoal {
+ @include gitlab-theme(#979DA7, $theme-charcoal, #373D47, #24272D);
+ }
+
+ &.ui_graphite {
+ @include gitlab-theme(#CCCCCC, $theme-graphite, #777777, #666666);
+ }
+
+ &.ui_gray {
+ @include gitlab-theme(#979797, $theme-gray, #272727, #222222);
+ }
+
+ &.ui_green {
+ @include gitlab-theme(#AADDCC, $theme-green, #018865, #017855);
+ }
+
+ &.ui_violet {
+ @include gitlab-theme(#9988CC, $theme-violet, #443366, #332255);
+ }
+}
diff --git a/app/assets/stylesheets/themes/ui_basic.scss b/app/assets/stylesheets/themes/ui_basic.scss
deleted file mode 100644
index 63e8dce1e92..00000000000
--- a/app/assets/stylesheets/themes/ui_basic.scss
+++ /dev/null
@@ -1,8 +0,0 @@
-/**
- * This file represent some UI that can be changed
- * during web app restyle or theme select.
- *
- */
-.ui_basic {
- @include gitlab-theme(#CCCCCC, #888888, #777777, #666666);
-}
diff --git a/app/assets/stylesheets/themes/ui_blue.scss b/app/assets/stylesheets/themes/ui_blue.scss
deleted file mode 100644
index cf995622b6b..00000000000
--- a/app/assets/stylesheets/themes/ui_blue.scss
+++ /dev/null
@@ -1,6 +0,0 @@
-/**
- * Blue GitLab UI theme
- */
-.ui_blue {
- @include gitlab-theme(#BECDE9, #2980b9, #1970a9, #096099);
-}
diff --git a/app/assets/stylesheets/themes/ui_color.scss b/app/assets/stylesheets/themes/ui_color.scss
deleted file mode 100644
index 6babccec0da..00000000000
--- a/app/assets/stylesheets/themes/ui_color.scss
+++ /dev/null
@@ -1,6 +0,0 @@
-/**
- * Violet GitLab UI theme
- */
-.ui_color {
- @include gitlab-theme(#98C, #548, #436, #325);
-}
diff --git a/app/assets/stylesheets/themes/ui_gray.scss b/app/assets/stylesheets/themes/ui_gray.scss
deleted file mode 100644
index f8e4a6ea7da..00000000000
--- a/app/assets/stylesheets/themes/ui_gray.scss
+++ /dev/null
@@ -1,6 +0,0 @@
-/**
- * Gray GitLab UI theme
- */
-.ui_gray {
- @include gitlab-theme(#979797, #373737, #272727, #222222);
-}
diff --git a/app/assets/stylesheets/themes/ui_mars.scss b/app/assets/stylesheets/themes/ui_mars.scss
deleted file mode 100644
index fda96b64cd9..00000000000
--- a/app/assets/stylesheets/themes/ui_mars.scss
+++ /dev/null
@@ -1,6 +0,0 @@
-/**
- * Classic GitLab UI theme
- */
-.ui_mars {
- @include gitlab-theme(#979DA7, #474D57, #373D47, #24272D);
-}
diff --git a/app/assets/stylesheets/themes/ui_modern.scss b/app/assets/stylesheets/themes/ui_modern.scss
deleted file mode 100644
index 8261e80b35f..00000000000
--- a/app/assets/stylesheets/themes/ui_modern.scss
+++ /dev/null
@@ -1,6 +0,0 @@
-/**
- * Modern GitLab UI theme
- */
-.ui_modern {
- @include gitlab-theme(#ADC, #019875, #018865, #017855);
-}
diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb
new file mode 100644
index 00000000000..538b09ca54d
--- /dev/null
+++ b/app/controllers/profiles/preferences_controller.rb
@@ -0,0 +1,38 @@
+class Profiles::PreferencesController < Profiles::ApplicationController
+ before_action :user
+
+ def show
+ end
+
+ def update
+ begin
+ if @user.update_attributes(preferences_params)
+ flash[:notice] = 'Preferences saved.'
+ else
+ flash[:alert] = 'Failed to save preferences.'
+ end
+ rescue ArgumentError => e
+ # Raised when `dashboard` is given an invalid value.
+ flash[:alert] = "Failed to save preferences (#{e.message})."
+ end
+
+ respond_to do |format|
+ format.html { redirect_to profile_preferences_path }
+ format.js
+ end
+ end
+
+ private
+
+ def user
+ @user = current_user
+ end
+
+ def preferences_params
+ params.require(:user).permit(
+ :color_scheme_id,
+ :dashboard,
+ :theme_id
+ )
+ end
+end
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index f4366c18e7b..b4af9e490ed 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -8,9 +8,6 @@ class ProfilesController < Profiles::ApplicationController
def show
end
- def design
- end
-
def applications
@applications = current_user.oauth_applications
@authorized_tokens = current_user.oauth_authorized_tokens
@@ -29,7 +26,6 @@ class ProfilesController < Profiles::ApplicationController
respond_to do |format|
format.html { redirect_to :back }
- format.js
end
end
@@ -65,10 +61,21 @@ class ProfilesController < Profiles::ApplicationController
def user_params
params.require(:user).permit(
- :email, :password, :password_confirmation, :bio, :name,
- :username, :skype, :linkedin, :twitter, :website_url,
- :color_scheme_id, :theme_id, :avatar, :hide_no_ssh_key,
- :hide_no_password, :location, :public_email
+ :avatar,
+ :bio,
+ :email,
+ :hide_no_password,
+ :hide_no_ssh_key,
+ :linkedin,
+ :location,
+ :name,
+ :password,
+ :password_confirmation,
+ :public_email,
+ :skype,
+ :twitter,
+ :username,
+ :website_url
)
end
end
diff --git a/app/controllers/root_controller.rb b/app/controllers/root_controller.rb
new file mode 100644
index 00000000000..fdfe00dc135
--- /dev/null
+++ b/app/controllers/root_controller.rb
@@ -0,0 +1,28 @@
+# RootController
+#
+# This controller exists solely to handle requests to `root_url`. When a user is
+# logged in and has customized their `dashboard` setting, they will be
+# redirected to their preferred location.
+#
+# For users who haven't customized the setting, we simply delegate to
+# `DashboardController#show`, which is the default.
+class RootController < DashboardController
+ before_action :redirect_to_custom_dashboard, only: [:show]
+
+ def show
+ super
+ end
+
+ private
+
+ def redirect_to_custom_dashboard
+ return unless current_user
+
+ case current_user.dashboard
+ when 'stars'
+ redirect_to starred_dashboard_projects_path
+ else
+ return
+ end
+ end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index a539ec49f7a..10d7aa11209 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -2,26 +2,6 @@ require 'digest/md5'
require 'uri'
module ApplicationHelper
- COLOR_SCHEMES = {
- 1 => 'white',
- 2 => 'dark',
- 3 => 'solarized-light',
- 4 => 'solarized-dark',
- 5 => 'monokai',
- }
- COLOR_SCHEMES.default = 'white'
-
- # Helper method to access the COLOR_SCHEMES
- #
- # The keys are the `color_scheme_ids`
- # The values are the `name` of the scheme.
- #
- # The preview images are `name-scheme-preview.png`
- # The stylesheets should use the css class `.name`
- def color_schemes
- COLOR_SCHEMES.freeze
- end
-
# Check if a particular controller is the current one
#
# args - One or more controller names to check
@@ -138,18 +118,6 @@ module ApplicationHelper
Emoji.names.to_s
end
- def app_theme
- Gitlab::Theme.css_class_by_id(current_user.try(:theme_id))
- end
-
- def theme_type
- Gitlab::Theme.type_css_class_by_id(current_user.try(:theme_id))
- end
-
- def user_color_scheme_class
- COLOR_SCHEMES[current_user.try(:color_scheme_id)] if defined?(current_user)
- end
-
# Define whenever show last push event
# with suggestion to create MR
def show_last_push_widget?(event)
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index 2777944fc9d..9aabe01f60e 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -2,6 +2,7 @@ require 'nokogiri'
module GitlabMarkdownHelper
include Gitlab::Markdown
+ include PreferencesHelper
# Use this in places where you would normally use link_to(gfm(...), ...).
#
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
new file mode 100644
index 00000000000..bceff4fd52e
--- /dev/null
+++ b/app/helpers/preferences_helper.rb
@@ -0,0 +1,53 @@
+# Helper methods for per-User preferences
+module PreferencesHelper
+ COLOR_SCHEMES = {
+ 1 => 'white',
+ 2 => 'dark',
+ 3 => 'solarized-light',
+ 4 => 'solarized-dark',
+ 5 => 'monokai',
+ }
+ COLOR_SCHEMES.default = 'white'
+
+ # Helper method to access the COLOR_SCHEMES
+ #
+ # The keys are the `color_scheme_ids`
+ # The values are the `name` of the scheme.
+ #
+ # The preview images are `name-scheme-preview.png`
+ # The stylesheets should use the css class `.name`
+ def color_schemes
+ COLOR_SCHEMES.freeze
+ end
+
+ # Maps `dashboard` values to more user-friendly option text
+ DASHBOARD_CHOICES = {
+ projects: 'Your Projects (default)',
+ stars: 'Starred Projects'
+ }.with_indifferent_access.freeze
+
+ # Returns an Array usable by a select field for more user-friendly option text
+ def dashboard_choices
+ defined = User.dashboards
+
+ if defined.size != DASHBOARD_CHOICES.size
+ # Ensure that anyone adding new options updates this method too
+ raise RuntimeError, "`User` defines #{defined.size} dashboard choices," +
+ " but `DASHBOARD_CHOICES` defined #{DASHBOARD_CHOICES.size}."
+ else
+ defined.map do |key, _|
+ # Use `fetch` so `KeyError` gets raised when a key is missing
+ [DASHBOARD_CHOICES.fetch(key), key]
+ end
+ end
+ end
+
+ def user_application_theme
+ theme = Gitlab::Themes.by_id(current_user.try(:theme_id))
+ theme.css_class
+ end
+
+ def user_color_scheme_class
+ COLOR_SCHEMES[current_user.try(:color_scheme_id)] if defined?(current_user)
+ end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index 8be0b622704..6ac287203b1 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -50,12 +50,13 @@
# bitbucket_access_token :string(255)
# bitbucket_access_token_secret :string(255)
# location :string(255)
+# public_email :string(255) default(""), not null
# encrypted_otp_secret :string(255)
# encrypted_otp_secret_iv :string(255)
# encrypted_otp_secret_salt :string(255)
# otp_required_for_login :boolean
# otp_backup_codes :text
-# public_email :string(255) default(""), not null
+# dashboard :integer default(0)
#
require 'carrierwave/orm/activerecord'
@@ -701,4 +702,8 @@ class User < ActiveRecord::Base
def can_be_removed?
!solo_owned_groups.present?
end
+
+ # User's Dashboard preference
+ # Note: When adding an option, it MUST go on the end of the array.
+ enum dashboard: [:projects, :stars]
end
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 173033f7eab..678ed3c2c1f 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -1,10 +1,10 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head"
- %body{class: "#{app_theme}", :'data-page' => body_data_page}
- / Ideally this would be inside the head, but turbolinks only evaluates page-specific JS in the body.
+ %body{class: "#{user_application_theme}", 'data-page' => body_data_page}
+ -# Ideally this would be inside the head, but turbolinks only evaluates page-specific JS in the body.
= yield :scripts_body_top
-
+
- if current_user
= render "layouts/header/default", title: header_title
- else
diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml
index d406f5764a7..1987bf1592a 100644
--- a/app/views/layouts/devise.html.haml
+++ b/app/views/layouts/devise.html.haml
@@ -1,7 +1,7 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head"
- %body.ui_mars.login-page.application
+ %body.ui_charcoal.login-page.application
= render "layouts/header/empty"
= render "layouts/broadcast"
.container.navless-container
diff --git a/app/views/layouts/errors.html.haml b/app/views/layouts/errors.html.haml
index 2e3a2b16eb7..2af265a2296 100644
--- a/app/views/layouts/errors.html.haml
+++ b/app/views/layouts/errors.html.haml
@@ -1,7 +1,7 @@
!!! 5
%html{ lang: "en"}
= render "layouts/head"
- %body{class: "#{app_theme} application"}
+ %body{class: "#{user_application_theme} application"}
= render "layouts/header/empty"
.container.navless-container
= render "layouts/flash"
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index d46dba4a240..687c1fc3dd2 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -1,6 +1,6 @@
%ul.nav.nav-sidebar
- = nav_link(path: 'dashboard#show', html_options: {class: 'home'}) do
- = link_to root_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
+ = nav_link(path: ['dashboard#show', 'root#show'], html_options: {class: 'home'}) do
+ = link_to dashboard_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
= icon('dashboard fw')
%span
Your Projects
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index ac37fd4c1c1..121665bd536 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -38,11 +38,12 @@
%span
SSH Keys
%span.count= current_user.keys.count
- = nav_link(path: 'profiles#design') do
- = link_to design_profile_path, title: 'Design', data: {placement: 'right'} do
+ = nav_link(controller: :preferences) do
+ = link_to profile_preferences_path, title: 'Preferences', data: {placement: 'right'} do
+ -# TODO (rspeicher): Better icon?
= icon('image fw')
%span
- Design
+ Preferences
= nav_link(path: 'profiles#history') do
= link_to history_profile_path, title: 'History', data: {placement: 'right'} do
= icon('history fw')
diff --git a/app/views/profiles/design.html.haml b/app/views/profiles/design.html.haml
deleted file mode 100644
index f450ec1c018..00000000000
--- a/app/views/profiles/design.html.haml
+++ /dev/null
@@ -1,56 +0,0 @@
-- page_title "Design"
-%h3.page-title
- = page_title
-%p.light
- Appearance settings will be saved to your profile and made available across all devices.
-%hr
-
-= form_for @user, url: profile_path, remote: true, method: :put do |f|
- .panel.panel-default.application-theme
- .panel-heading
- Application theme
- .panel-body
- .themes_opts
- = label_tag do
- .prev.default
- = f.radio_button :theme_id, 1
- Graphite
-
- = label_tag do
- .prev.classic
- = f.radio_button :theme_id, 2
- Charcoal
-
- = label_tag do
- .prev.modern
- = f.radio_button :theme_id, 3
- Green
-
- = label_tag do
- .prev.gray
- = f.radio_button :theme_id, 4
- Gray
-
- = label_tag do
- .prev.violet
- = f.radio_button :theme_id, 5
- Violet
-
- = label_tag do
- .prev.blue
- = f.radio_button :theme_id, 6
- Blue
- %br
- .clearfix
-
- .panel.panel-default.code-preview-theme
- .panel-heading
- Code preview theme
- .panel-body
- .code_highlight_opts
- - color_schemes.each do |color_scheme_id, color_scheme|
- = label_tag do
- .prev
- = image_tag "#{color_scheme}-scheme-preview.png"
- = f.radio_button :color_scheme_id, color_scheme_id
- = color_scheme.gsub(/[-_]+/, ' ').humanize
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
new file mode 100644
index 00000000000..aa99280fde6
--- /dev/null
+++ b/app/views/profiles/preferences/show.html.haml
@@ -0,0 +1,42 @@
+- page_title 'Preferences'
+%h3.page-title
+ = page_title
+%p.light
+ These settings allow you to customize the appearance and behavior of the site.
+ They are saved with your account and will persist to any device you use to
+ access the site.
+%hr
+
+= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: {class: 'js-preferences-form form-horizontal'} do |f|
+ .panel.panel-default.application-theme
+ .panel-heading
+ Application theme
+ .panel-body
+ - Gitlab::Themes.each do |theme|
+ = label_tag do
+ .preview{class: theme.css_class}
+ = f.radio_button :theme_id, theme.id
+ = theme.name
+
+ .panel.panel-default.syntax-theme
+ .panel-heading
+ Syntax highlighting theme
+ .panel-body
+ - color_schemes.each do |color_scheme_id, color_scheme|
+ = label_tag do
+ .preview= image_tag "#{color_scheme}-scheme-preview.png"
+ = f.radio_button :color_scheme_id, color_scheme_id
+ = color_scheme.tr('-_', ' ').titleize
+
+ .panel.panel-default
+ .panel-heading
+ Behavior
+ .panel-body
+ .form-group
+ = f.label :dashboard, class: 'control-label' do
+ Default Dashboard
+ = link_to('(?)', help_page_path('profile', 'preferences') + '#default-dashboard', target: '_blank')
+ .col-sm-10
+ = f.select :dashboard, dashboard_choices, {}, class: 'form-control'
+ .panel-footer
+ = f.submit 'Save', class: 'btn btn-save'
diff --git a/app/views/profiles/preferences/update.js.erb b/app/views/profiles/preferences/update.js.erb
new file mode 100644
index 00000000000..6c4b0ce757d
--- /dev/null
+++ b/app/views/profiles/preferences/update.js.erb
@@ -0,0 +1,9 @@
+// Remove body class for any previous theme, re-add current one
+$('body').removeClass('<%= Gitlab::Themes.body_classes %>')
+$('body').addClass('<%= user_application_theme %>')
+
+// Re-enable the "Save" button
+$('input[type=submit]').enable()
+
+// Show the notice flash message
+new Flash('<%= flash.discard(:notice) %>', 'notice')
diff --git a/app/views/profiles/update.js.erb b/app/views/profiles/update.js.erb
deleted file mode 100644
index db37619136d..00000000000
--- a/app/views/profiles/update.js.erb
+++ /dev/null
@@ -1,3 +0,0 @@
-// Remove body class for any previous theme, re-add current one
-$('body').removeClass('<%= Gitlab::Theme.body_classes %>')
-$('body').addClass('<%= app_theme %> <%= theme_type %>')
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 96b6ed01f42..c32ac2042d0 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -62,12 +62,13 @@ production: &base
# default_can_create_group: false # default: true
# username_changing_enabled: false # default: true - User can change her username/namespace
- ## Default theme
- ## BASIC = 1
- ## MARS = 2
- ## MODERN = 3
- ## GRAY = 4
- ## COLOR = 5
+ ## Default theme ID
+ ## 1 - Graphite
+ ## 2 - Charcoal
+ ## 3 - Green
+ ## 4 - Gray
+ ## 5 - Violet
+ ## 6 - Blue
# default_theme: 2 # default: 2
## Automatic issue closing
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 9c622b73016..7b5d488f59e 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -103,7 +103,7 @@ Settings['gitlab'] ||= Settingslogic.new({})
Settings.gitlab['default_projects_limit'] ||= 10
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::Theme::MARS if Settings.gitlab['default_theme'].nil?
+Settings.gitlab['default_theme'] = Gitlab::Themes::APPLICATION_DEFAULT if Settings.gitlab['default_theme'].nil?
Settings.gitlab['host'] ||= 'localhost'
Settings.gitlab['ssh_host'] ||= Settings.gitlab.host
Settings.gitlab['https'] = false if Settings.gitlab['https'].nil?
diff --git a/config/routes.rb b/config/routes.rb
index f4a104664f3..d60bc796fdb 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -203,7 +203,6 @@ Gitlab::Application.routes.draw do
resource :profile, only: [:show, :update] do
member do
get :history
- get :design
get :applications
put :reset_private_token
@@ -222,6 +221,7 @@ Gitlab::Application.routes.draw do
put :reset
end
end
+ resource :preferences, only: [:show, :update]
resources :keys
resources :emails, only: [:index, :create, :destroy]
resource :avatar, only: [:destroy]
@@ -293,7 +293,7 @@ Gitlab::Application.routes.draw do
get '/users/auth/:provider/omniauth_error' => 'omniauth_callbacks#omniauth_error', as: :omniauth_error
end
- root to: "dashboard#show"
+ root to: "root#show"
#
# Project Area
diff --git a/db/fixtures/production/001_admin.rb b/db/fixtures/production/001_admin.rb
index 8b560ee09e0..1c8740f6ba9 100644
--- a/db/fixtures/production/001_admin.rb
+++ b/db/fixtures/production/001_admin.rb
@@ -12,7 +12,7 @@ admin = User.create(
username: 'root',
password: password,
password_expires_at: expire_time,
- theme_id: Gitlab::Theme::MARS
+ theme_id: Gitlab::Themes::APPLICATION_DEFAULT
)
diff --git a/db/migrate/20150610065936_add_dashboard_to_users.rb b/db/migrate/20150610065936_add_dashboard_to_users.rb
new file mode 100644
index 00000000000..2628e450722
--- /dev/null
+++ b/db/migrate/20150610065936_add_dashboard_to_users.rb
@@ -0,0 +1,9 @@
+class AddDashboardToUsers < ActiveRecord::Migration
+ def up
+ add_column :users, :dashboard, :integer, default: 0
+ end
+
+ def down
+ remove_column :users, :dashboard
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 9a9d4a85e4b..f063a4868b1 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20150609141121) do
+ActiveRecord::Schema.define(version: 20150610065936) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -502,6 +502,7 @@ ActiveRecord::Schema.define(version: 20150609141121) do
t.boolean "otp_required_for_login"
t.text "otp_backup_codes"
t.string "public_email", default: "", null: false
+ t.integer "dashboard", default: 0
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
diff --git a/doc/README.md b/doc/README.md
index 7a2181edded..28459613252 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -1,35 +1,36 @@
-# Documentation
-
-## User documentation
-
-- [API](api/README.md) Automate GitLab via a simple and powerful API.
-- [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab.
-- [Importing to GitLab](workflow/importing/README.md).
-- [Markdown](markdown/markdown.md) GitLab's advanced formatting system.
-- [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do.
-- [Project Services](project_services/project_services.md) Integrate a project with external services, such as CI and chat.
-- [Public access](public_access/public_access.md) Learn how you can allow public and internal access to projects.
-- [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects.
-- [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project.
-- [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN.
-
-## Administrator documentation
-
-- [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when web hooks aren't enough.
-- [Install](install/README.md) Requirements, directory structures and installation from source.
-- [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter.
-- [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages.
-- [Libravatar](customization/libravatar.md) Use Libravatar for user avatars.
-- [Log system](logs/logs.md) Log system.
-- [Operations](operations/README.md) Keeping GitLab up and running
-- [Raketasks](raketasks/README.md) Backups, maintenance, automatic web hook setup and the importing of projects.
-- [Security](security/README.md) Learn what you can do to further secure your GitLab instance.
-- [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed.
-- [Update](update/README.md) Update guides to upgrade your installation.
-- [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page.
-
-## Contributor documentation
-
-- [Development](development/README.md) Explains the architecture and the guidelines for shell commands.
-- [Legal](legal/README.md) Contributor license agreements.
-- [Release](release/README.md) How to make the monthly and security releases. \ No newline at end of file
+# Documentation
+
+## User documentation
+
+- [API](api/README.md) Automate GitLab via a simple and powerful API.
+- [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab.
+- [Importing to GitLab](workflow/importing/README.md).
+- [Markdown](markdown/markdown.md) GitLab's advanced formatting system.
+- [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do.
+- [Profile Settings](profile/README.md)
+- [Project Services](project_services/project_services.md) Integrate a project with external services, such as CI and chat.
+- [Public access](public_access/public_access.md) Learn how you can allow public and internal access to projects.
+- [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects.
+- [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project.
+- [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN.
+
+## Administrator documentation
+
+- [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when web hooks aren't enough.
+- [Install](install/README.md) Requirements, directory structures and installation from source.
+- [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter.
+- [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages.
+- [Libravatar](customization/libravatar.md) Use Libravatar for user avatars.
+- [Log system](logs/logs.md) Log system.
+- [Operations](operations/README.md) Keeping GitLab up and running
+- [Raketasks](raketasks/README.md) Backups, maintenance, automatic web hook setup and the importing of projects.
+- [Security](security/README.md) Learn what you can do to further secure your GitLab instance.
+- [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed.
+- [Update](update/README.md) Update guides to upgrade your installation.
+- [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page.
+
+## Contributor documentation
+
+- [Development](development/README.md) Explains the architecture and the guidelines for shell commands.
+- [Legal](legal/README.md) Contributor license agreements.
+- [Release](release/README.md) How to make the monthly and security releases.
diff --git a/doc/workflow/2fa.png b/doc/profile/2fa.png
index bbf415210d5..bbf415210d5 100644
--- a/doc/workflow/2fa.png
+++ b/doc/profile/2fa.png
Binary files differ
diff --git a/doc/workflow/2fa_auth.png b/doc/profile/2fa_auth.png
index 4a4fbe68984..4a4fbe68984 100644
--- a/doc/workflow/2fa_auth.png
+++ b/doc/profile/2fa_auth.png
Binary files differ
diff --git a/doc/profile/README.md b/doc/profile/README.md
new file mode 100644
index 00000000000..6f8359d87fa
--- /dev/null
+++ b/doc/profile/README.md
@@ -0,0 +1,4 @@
+# Profile Settings
+
+- [Preferences](preferences.md)
+- [Two-factor Authentication (2FA)](two_factor_authentication.md)
diff --git a/doc/profile/preferences.md b/doc/profile/preferences.md
new file mode 100644
index 00000000000..ce5f1936782
--- /dev/null
+++ b/doc/profile/preferences.md
@@ -0,0 +1,32 @@
+# Profile Preferences
+
+Settings in the **Profile > Preferences** page allow the user to customize
+various aspects of the site to their liking.
+
+## Application theme
+
+Changing this setting allows the user to customize the color scheme used for the
+navigation bar on the left side of the screen.
+
+The default is **Charcoal**.
+
+## Syntax highlighting theme
+
+Changing this setting allows the user to customize the theme used when viewing
+syntax highlighted code on the site.
+
+The default is **White**.
+
+## Behavior
+
+### Default Dashboard
+
+For users who have access to a large number of projects but only keep up with a
+select few, the amount of activity on the default Dashboard page can be
+overwhelming.
+
+Changing this setting allows the user to redefine what their default dashboard
+will be. Setting it to **Starred Projects** will make that Dashboard view the
+default when signing in or clicking the application logo in the upper left.
+
+The default is **Your Projects**.
diff --git a/doc/workflow/two_factor_authentication.md b/doc/profile/two_factor_authentication.md
index fb215c8b269..fb215c8b269 100644
--- a/doc/workflow/two_factor_authentication.md
+++ b/doc/profile/two_factor_authentication.md
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index 70a8179c8eb..f1959d30139 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -1,16 +1,15 @@
-# Workflow
-
-- [Authorization for merge requests](authorization_for_merge_requests.md)
-- [Change your time zone](timezone.md)
-- [Feature branch workflow](workflow.md)
-- [GitLab Flow](gitlab_flow.md)
-- [Groups](groups.md)
-- [Keyboard shortcuts](shortcuts.md)
-- [Labels](labels.md)
-- [Notifications](notifications.md)
-- [Project Features](project_features.md)
-- [Project forking workflow](forking_workflow.md)
-- [Protected branches](protected_branches.md)
-- [Two-factor Authentication (2FA)](two_factor_authentication.md)
-- [Web Editor](web_editor.md)
-- ["Work In Progress" Merge Requests](wip_merge_requests.md) \ No newline at end of file
+# Workflow
+
+- [Authorization for merge requests](authorization_for_merge_requests.md)
+- [Change your time zone](timezone.md)
+- [Feature branch workflow](workflow.md)
+- [GitLab Flow](gitlab_flow.md)
+- [Groups](groups.md)
+- [Keyboard shortcuts](shortcuts.md)
+- [Labels](labels.md)
+- [Notifications](notifications.md)
+- [Project Features](project_features.md)
+- [Project forking workflow](forking_workflow.md)
+- [Protected branches](protected_branches.md)
+- [Web Editor](web_editor.md)
+- ["Work In Progress" Merge Requests](wip_merge_requests.md)
diff --git a/features/profile/active_tab.feature b/features/profile/active_tab.feature
index 7801ae5b8ca..1fa4ac88ddc 100644
--- a/features/profile/active_tab.feature
+++ b/features/profile/active_tab.feature
@@ -18,9 +18,9 @@ Feature: Profile Active Tab
Then the active main tab should be SSH Keys
And no other main tabs should be active
- Scenario: On Profile Design
- Given I visit profile design page
- Then the active main tab should be Design
+ Scenario: On Profile Preferences
+ Given I visit profile preferences page
+ Then the active main tab should be Preferences
And no other main tabs should be active
Scenario: On Profile History
diff --git a/features/profile/profile.feature b/features/profile/profile.feature
index d586167cdf5..0dd0afde8b1 100644
--- a/features/profile/profile.feature
+++ b/features/profile/profile.feature
@@ -84,16 +84,3 @@ Feature: Profile
Then I visit profile applications page
And I click to remove application
Then I see that application is removed
-
- @javascript
- Scenario: I change my application theme
- Given I visit profile design page
- When I change my application theme
- Then I should see the theme change immediately
- And I should receive feedback that the changes were saved
-
- @javascript
- Scenario: I change my code preview theme
- Given I visit profile design page
- When I change my code preview theme
- Then I should receive feedback that the changes were saved
diff --git a/features/steps/profile/active_tab.rb b/features/steps/profile/active_tab.rb
index 8595ee876a4..79e3b55f6e1 100644
--- a/features/steps/profile/active_tab.rb
+++ b/features/steps/profile/active_tab.rb
@@ -15,8 +15,8 @@ class Spinach::Features::ProfileActiveTab < Spinach::FeatureSteps
ensure_active_main_tab('SSH Keys')
end
- step 'the active main tab should be Design' do
- ensure_active_main_tab('Design')
+ step 'the active main tab should be Preferences' do
+ ensure_active_main_tab('Preferences')
end
step 'the active main tab should be History' do
diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb
index 32e6859eff7..649aea8e3f4 100644
--- a/features/steps/profile/profile.rb
+++ b/features/steps/profile/profile.rb
@@ -114,27 +114,6 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
expect(page).to have_content "#{current_user.name} closed issue"
end
- step "I change my application theme" do
- page.within '.application-theme' do
- choose "Violet"
- end
- end
-
- step "I change my code preview theme" do
- page.within '.code-preview-theme' do
- choose "Solarized dark"
- end
- end
-
- step "I should see the theme change immediately" do
- expect(page).to have_selector('body.ui_color')
- expect(page).not_to have_selector('body.ui_basic')
- end
-
- step "I should receive feedback that the changes were saved" do
- expect(page).to have_content("saved")
- end
-
step 'my password is expired' do
current_user.update_attributes(password_expires_at: Time.now - 1.hour)
end
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index 09ae7e3a305..3bd0d60281c 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -123,8 +123,8 @@ module SharedPaths
visit profile_keys_path
end
- step 'I visit profile design page' do
- visit design_profile_path
+ step 'I visit profile preferences page' do
+ visit profile_preferences_path
end
step 'I visit profile history page' do
diff --git a/lib/gitlab/theme.rb b/lib/gitlab/theme.rb
deleted file mode 100644
index e5a1f1b44d9..00000000000
--- a/lib/gitlab/theme.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-module Gitlab
- class Theme
- BASIC = 1 unless const_defined?(:BASIC)
- MARS = 2 unless const_defined?(:MARS)
- MODERN = 3 unless const_defined?(:MODERN)
- GRAY = 4 unless const_defined?(:GRAY)
- COLOR = 5 unless const_defined?(:COLOR)
- BLUE = 6 unless const_defined?(:BLUE)
-
- def self.classes
- @classes ||= {
- BASIC => 'ui_basic',
- MARS => 'ui_mars',
- MODERN => 'ui_modern',
- GRAY => 'ui_gray',
- COLOR => 'ui_color',
- BLUE => 'ui_blue'
- }
- end
-
- def self.css_class_by_id(id)
- id ||= Gitlab.config.gitlab.default_theme
- classes[id]
- end
-
- def self.types
- @types ||= {
- BASIC => 'light_theme',
- MARS => 'dark_theme',
- MODERN => 'dark_theme',
- GRAY => 'dark_theme',
- COLOR => 'dark_theme',
- BLUE => 'light_theme'
- }
- end
-
- def self.type_css_class_by_id(id)
- id ||= Gitlab.config.gitlab.default_theme
- types[id]
- end
-
- # 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 self.body_classes
- (classes.values + types.values).uniq.join(' ')
- end
- end
-end
diff --git a/lib/gitlab/themes.rb b/lib/gitlab/themes.rb
new file mode 100644
index 00000000000..5209df92795
--- /dev/null
+++ b/lib/gitlab/themes.rb
@@ -0,0 +1,67 @@
+module Gitlab
+ # Module containing GitLab's application theme definitions and helper methods
+ # for accessing them.
+ module Themes
+ # 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, 'Gray', 'ui_gray'),
+ 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 self.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 self.by_id(id)
+ THEMES.detect { |t| t.id == id } || default
+ end
+
+ # Get the default Theme
+ #
+ # Returns a Theme
+ def self.default
+ by_id(default_id)
+ end
+
+ # Iterate through each Theme
+ #
+ # Yields the Theme object
+ def self.each(&block)
+ THEMES.each(&block)
+ end
+
+ private
+
+ def self.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
new file mode 100644
index 00000000000..1f0943c93d8
--- /dev/null
+++ b/spec/controllers/profiles/preferences_controller_spec.rb
@@ -0,0 +1,88 @@
+require 'spec_helper'
+
+describe Profiles::PreferencesController do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+
+ allow(subject).to receive(:current_user).and_return(user)
+ end
+
+ describe 'GET show' do
+ it 'renders' do
+ get :show
+ expect(response).to render_template :show
+ end
+
+ it 'assigns user' do
+ get :show
+ expect(assigns[:user]).to eq user
+ end
+ end
+
+ describe 'PATCH update' do
+ def go(params: {}, format: :js)
+ params.reverse_merge!(
+ color_scheme_id: '1',
+ dashboard: 'stars',
+ theme_id: '1'
+ )
+
+ patch :update, user: params, format: format
+ end
+
+ context 'on successful update' do
+ it 'sets the flash' do
+ go
+ expect(flash[:notice]).to eq 'Preferences saved.'
+ end
+
+ it "changes the user's preferences" do
+ prefs = {
+ color_scheme_id: '1',
+ dashboard: 'stars',
+ theme_id: '2'
+ }.with_indifferent_access
+
+ expect(user).to receive(:update_attributes).with(prefs)
+
+ go params: prefs
+ end
+ end
+
+ context 'on failed update' do
+ it 'sets the flash' do
+ expect(user).to receive(:update_attributes).and_return(false)
+
+ go
+
+ expect(flash[:alert]).to eq('Failed to save preferences.')
+ end
+ end
+
+ context 'on invalid dashboard setting' do
+ it 'sets the flash' do
+ prefs = {dashboard: 'invalid'}
+
+ go params: prefs
+
+ expect(flash[:alert]).to match(/\AFailed to save preferences \(.+\)\.\z/)
+ end
+ end
+
+ context 'as js' do
+ it 'renders' do
+ go
+ expect(response).to render_template :update
+ end
+ end
+
+ context 'as html' do
+ it 'redirects' do
+ go format: :html
+ expect(response).to redirect_to(profile_preferences_path)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/root_controller_spec.rb b/spec/controllers/root_controller_spec.rb
new file mode 100644
index 00000000000..abbbf6855fc
--- /dev/null
+++ b/spec/controllers/root_controller_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe RootController do
+ describe 'GET show' do
+ context 'with a user' do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ allow(subject).to receive(:current_user).and_return(user)
+ end
+
+ context 'who has customized their dashboard setting' do
+ before do
+ user.update_attribute(:dashboard, 'stars')
+ end
+
+ it 'redirects to their specified dashboard' do
+ get :show
+ expect(response).to redirect_to starred_dashboard_projects_path
+ end
+ end
+
+ context 'who uses the default dashboard setting' do
+ it 'renders the default dashboard' do
+ get :show
+ expect(response).to render_template 'dashboard/show'
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/profiles/preferences_spec.rb b/spec/features/profiles/preferences_spec.rb
new file mode 100644
index 00000000000..69d15f41706
--- /dev/null
+++ b/spec/features/profiles/preferences_spec.rb
@@ -0,0 +1,82 @@
+require 'spec_helper'
+
+describe 'Profile > Preferences' do
+ let(:user) { create(:user) }
+
+ before do
+ login_as(user)
+ visit profile_preferences_path
+ end
+
+ describe 'User changes their application theme', js: true do
+ let(:default) { Gitlab::Themes.default }
+ let(:theme) { Gitlab::Themes.by_id(5) }
+
+ it 'creates a flash message' do
+ choose "user_theme_id_#{theme.id}"
+
+ expect_preferences_saved_message
+ end
+
+ it 'updates their preference' do
+ choose "user_theme_id_#{theme.id}"
+
+ allowing_for_delay do
+ visit page.current_path
+ expect(page).to have_checked_field("user_theme_id_#{theme.id}")
+ end
+ end
+
+ it 'reflects the changes immediately' do
+ expect(page).to have_selector("body.#{default.css_class}")
+
+ choose "user_theme_id_#{theme.id}"
+
+ expect(page).not_to have_selector("body.#{default.css_class}")
+ expect(page).to have_selector("body.#{theme.css_class}")
+ end
+ end
+
+ describe 'User changes their syntax highlighting theme', js: true do
+ it 'creates a flash message' do
+ choose 'user_color_scheme_id_5'
+
+ expect_preferences_saved_message
+ end
+
+ it 'updates their preference' do
+ choose 'user_color_scheme_id_5'
+
+ allowing_for_delay do
+ visit page.current_path
+ expect(page).to have_checked_field('user_color_scheme_id_5')
+ end
+ end
+ end
+
+ describe 'User changes their default dashboard' do
+ it 'creates a flash message' do
+ select 'Starred Projects', from: 'user_dashboard'
+ click_button 'Save'
+
+ expect_preferences_saved_message
+ end
+
+ it 'updates their preference' do
+ select 'Starred Projects', from: 'user_dashboard'
+ click_button 'Save'
+
+ click_link 'Dashboard'
+ expect(page.current_path).to eq starred_dashboard_projects_path
+
+ click_link 'Your Projects'
+ expect(page.current_path).to eq dashboard_path
+ end
+ end
+
+ def expect_preferences_saved_message
+ within('.flash-container') do
+ expect(page).to have_content('Preferences saved.')
+ end
+ end
+end
diff --git a/spec/features/security/profile_access_spec.rb b/spec/features/security/profile_access_spec.rb
index 2512a9c0e3d..2b09771851e 100644
--- a/spec/features/security/profile_access_spec.rb
+++ b/spec/features/security/profile_access_spec.rb
@@ -36,8 +36,8 @@ describe "Profile access", feature: true do
it { is_expected.to be_denied_for :visitor }
end
- describe "GET /profile/design" do
- subject { design_profile_path }
+ describe "GET /profile/preferences" do
+ subject { profile_preferences_path }
it { is_expected.to be_allowed_for @u1 }
it { is_expected.to be_allowed_for :admin }
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 3307ac776fc..47e10197f5c 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -185,27 +185,6 @@ describe ApplicationHelper do
end
end
- describe 'user_color_scheme_class' do
- context 'with current_user is nil' do
- it 'should return a string' do
- allow(self).to receive(:current_user).and_return(nil)
- expect(user_color_scheme_class).to be_kind_of(String)
- end
- end
-
- context 'with a current_user' do
- (1..5).each do |color_scheme_id|
- context "with color_scheme_id == #{color_scheme_id}" do
- it 'should return a string' do
- current_user = double(:color_scheme_id => color_scheme_id)
- allow(self).to receive(:current_user).and_return(current_user)
- expect(user_color_scheme_class).to be_kind_of(String)
- end
- end
- end
- end
- end
-
describe 'simple_sanitize' do
let(:a_tag) { '<a href="#">Foo</a>' }
diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb
new file mode 100644
index 00000000000..920de8c4325
--- /dev/null
+++ b/spec/helpers/preferences_helper_spec.rb
@@ -0,0 +1,72 @@
+require 'spec_helper'
+
+describe PreferencesHelper do
+ describe 'user_application_theme' do
+ context 'with a user' do
+ it "returns user's theme's css_class" do
+ user = double('user', theme_id: 3)
+ allow(self).to receive(:current_user).and_return(user)
+ expect(user_application_theme).to eq 'ui_green'
+ end
+
+ it 'returns the default when id is invalid' do
+ user = double('user', theme_id: Gitlab::Themes::THEMES.size + 5)
+
+ allow(Gitlab.config.gitlab).to receive(:default_theme).and_return(2)
+ allow(self).to receive(:current_user).and_return(user)
+
+ expect(user_application_theme).to eq 'ui_charcoal'
+ end
+ end
+
+ context 'without a user' do
+ before do
+ allow(self).to receive(:current_user).and_return(nil)
+ end
+
+ it 'returns the default theme' do
+ expect(user_application_theme).to eq Gitlab::Themes.default.css_class
+ end
+ end
+ end
+
+ describe 'dashboard_choices' do
+ it 'raises an exception when defined choices may be missing' do
+ expect(User).to receive(:dashboards).and_return(foo: 'foo')
+ expect { dashboard_choices }.to raise_error(RuntimeError)
+ end
+
+ it 'raises an exception when defined choices may be using the wrong key' do
+ expect(User).to receive(:dashboards).and_return(foo: 'foo', bar: 'bar')
+ expect { dashboard_choices }.to raise_error(KeyError)
+ end
+
+ it 'provides better option descriptions' do
+ expect(dashboard_choices).to match_array [
+ ['Your Projects (default)', 'projects'],
+ ['Starred Projects', 'stars']
+ ]
+ end
+ end
+
+ describe 'user_color_scheme_class' do
+ context 'with current_user is nil' do
+ it 'should return a string' do
+ allow(self).to receive(:current_user).and_return(nil)
+ expect(user_color_scheme_class).to be_kind_of(String)
+ end
+ end
+
+ context 'with a current_user' do
+ (1..5).each do |color_scheme_id|
+ context "with color_scheme_id == #{color_scheme_id}" do
+ it 'should return a string' do
+ current_user = double(:color_scheme_id => color_scheme_id)
+ allow(self).to receive(:current_user).and_return(current_user)
+ expect(user_color_scheme_class).to be_kind_of(String)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/themes_spec.rb b/spec/lib/gitlab/themes_spec.rb
new file mode 100644
index 00000000000..9c6c3fd8104
--- /dev/null
+++ b/spec/lib/gitlab/themes_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+describe Gitlab::Themes 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
+
+ # TODO (rspeicher): RSpec 3.x
+ # expect(described_class.each).to yield_with_arg(described_class::Theme)
+ end
+ end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index f1b8afa5854..f3e278e5c5f 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -50,12 +50,13 @@
# bitbucket_access_token :string(255)
# bitbucket_access_token_secret :string(255)
# location :string(255)
+# public_email :string(255) default(""), not null
# encrypted_otp_secret :string(255)
# encrypted_otp_secret_iv :string(255)
# encrypted_otp_secret_salt :string(255)
# otp_required_for_login :boolean
# otp_backup_codes :text
-# public_email :string(255) default(""), not null
+# dashboard :integer default(0)
#
require 'spec_helper'
@@ -329,12 +330,12 @@ describe User do
end
describe 'with default overrides' do
- let(:user) { User.new(projects_limit: 123, can_create_group: false, can_create_team: true, theme_id: Gitlab::Theme::BASIC) }
+ let(:user) { User.new(projects_limit: 123, can_create_group: false, can_create_team: true, theme_id: 1) }
it "should apply 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(Gitlab::Theme::BASIC)
+ expect(user.theme_id).to eq(1)
end
end
end
diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb
index 953c8dd8ddc..f268e4755d1 100644
--- a/spec/routing/routing_spec.rb
+++ b/spec/routing/routing_spec.rb
@@ -102,7 +102,6 @@ end
# profile_token GET /profile/token(.:format) profile#token
# profile_reset_private_token PUT /profile/reset_private_token(.:format) profile#reset_private_token
# profile GET /profile(.:format) profile#show
-# profile_design GET /profile/design(.:format) profile#design
# profile_update PUT /profile/update(.:format) profile#update
describe ProfilesController, "routing" do
it "to #account" do
@@ -120,9 +119,19 @@ describe ProfilesController, "routing" do
it "to #show" do
expect(get("/profile")).to route_to('profiles#show')
end
+end
- it "to #design" do
- expect(get("/profile/design")).to route_to('profiles#design')
+# profile_preferences GET /profile/preferences(.:format) profiles/preferences#show
+# PATCH /profile/preferences(.:format) profiles/preferences#update
+# PUT /profile/preferences(.:format) profiles/preferences#update
+describe Profiles::PreferencesController, 'routing' do
+ it 'to #show' do
+ expect(get('/profile/preferences')).to route_to('profiles/preferences#show')
+ end
+
+ it 'to #update' do
+ expect(put('/profile/preferences')).to route_to('profiles/preferences#update')
+ expect(patch('/profile/preferences')).to route_to('profiles/preferences#update')
end
end
@@ -195,11 +204,9 @@ end
# dashboard GET /dashboard(.:format) dashboard#show
# dashboard_issues GET /dashboard/issues(.:format) dashboard#issues
# dashboard_merge_requests GET /dashboard/merge_requests(.:format) dashboard#merge_requests
-# root / dashboard#show
describe DashboardController, "routing" do
it "to #index" do
expect(get("/dashboard")).to route_to('dashboard#show')
- expect(get("/")).to route_to('dashboard#show')
end
it "to #issues" do
@@ -211,6 +218,14 @@ describe DashboardController, "routing" do
end
end
+# root / root#show
+describe RootController, 'routing' do
+ it 'to #show' do
+ expect(get('/')).to route_to('root#show')
+ end
+end
+
+
# new_user_session GET /users/sign_in(.:format) devise/sessions#new
# user_session POST /users/sign_in(.:format) devise/sessions#create
# destroy_user_session DELETE /users/sign_out(.:format) devise/sessions#destroy
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index fed1ab6ee33..3e41aec425a 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -19,3 +19,36 @@ unless ENV['CI'] || ENV['CI_SERVER']
# Keep only the screenshots generated from the last failing test suite
Capybara::Screenshot.prune_strategy = :keep_last_run
end
+
+module CapybaraHelpers
+ # Execute a block a certain number of times before considering it a failure
+ #
+ # The given block is called, and if it raises a `Capybara::ExpectationNotMet`
+ # error, we wait `interval` seconds and then try again, until `retries` is
+ # met.
+ #
+ # This allows for better handling of timing-sensitive expectations in a
+ # sketchy CI environment, for example.
+ #
+ # interval - Delay between retries in seconds (default: 0.5)
+ # retries - Number of times to execute before failing (default: 5)
+ def allowing_for_delay(interval: 0.5, retries: 5)
+ tries = 0
+
+ begin
+ yield
+ rescue Capybara::ExpectationNotMet => ex
+ if tries <= retries
+ tries += 1
+ sleep interval
+ retry
+ else
+ raise ex
+ end
+ end
+ end
+end
+
+RSpec.configure do |config|
+ config.include CapybaraHelpers, type: :feature
+end
diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb
index 791d2a1fd64..1bd68552012 100644
--- a/spec/support/login_helpers.rb
+++ b/spec/support/login_helpers.rb
@@ -1,9 +1,25 @@
module LoginHelpers
- # Internal: Create and log in as a user of the specified role
+ # Internal: Log in as a specific user or a new user of a specific role
#
- # role - User role (e.g., :admin, :user)
- def login_as(role)
- @user = create(role)
+ # user_or_role - User object, or a role to create (e.g., :admin, :user)
+ #
+ # Examples:
+ #
+ # # Create a user automatically
+ # login_as(:user)
+ #
+ # # Create an admin automatically
+ # login_as(:admin)
+ #
+ # # Provide an existing User record
+ # user = create(:user)
+ # login_as(user)
+ def login_as(user_or_role)
+ if user_or_role.kind_of?(User)
+ @user = user_or_role
+ else
+ @user = create(user_or_role)
+ end
login_with(@user)
end