diff options
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 Binary files differindex bbf415210d5..bbf415210d5 100644 --- a/doc/workflow/2fa.png +++ b/doc/profile/2fa.png diff --git a/doc/workflow/2fa_auth.png b/doc/profile/2fa_auth.png Binary files differindex 4a4fbe68984..4a4fbe68984 100644 --- a/doc/workflow/2fa_auth.png +++ b/doc/profile/2fa_auth.png 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 |