From 471ea73a56fcac4118e7161eeeec845ba831b104 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 5 Jun 2015 14:00:21 -0400 Subject: [PATCH 01/21] Add Profiles::PreferencesController cherry-picked --- .../profiles/preferences_controller.rb | 29 ++++++++ app/views/layouts/nav/_profile.html.haml | 7 +- app/views/profiles/preferences/show.html.haml | 1 + app/views/profiles/preferences/update.js.erb | 1 + config/routes.rb | 1 + features/profile/active_tab.feature | 6 +- features/steps/profile/active_tab.rb | 4 +- features/steps/shared/paths.rb | 4 +- .../profiles/preferences_controller_spec.rb | 70 +++++++++++++++++++ spec/features/security/profile_access_spec.rb | 4 +- spec/routing/routing_spec.rb | 15 +++- 11 files changed, 127 insertions(+), 15 deletions(-) create mode 100644 app/controllers/profiles/preferences_controller.rb create mode 100644 app/views/profiles/preferences/show.html.haml create mode 100644 app/views/profiles/preferences/update.js.erb create mode 100644 spec/controllers/profiles/preferences_controller_spec.rb diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb new file mode 100644 index 0000000000..897e6fe074 --- /dev/null +++ b/app/controllers/profiles/preferences_controller.rb @@ -0,0 +1,29 @@ +class Profiles::PreferencesController < Profiles::ApplicationController + before_action :user + + def show + end + + def update + if @user.update_attributes(preferences_params) + flash[:notice] = 'Preferences saved.' + else + # TODO (rspeicher): There's no validation on these values, so can it fail? + 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, :theme_id) + end +end diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml index ac37fd4c1c..121665bd53 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/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml new file mode 100644 index 0000000000..1333ed77b7 --- /dev/null +++ b/app/views/profiles/preferences/show.html.haml @@ -0,0 +1 @@ +TODO diff --git a/app/views/profiles/preferences/update.js.erb b/app/views/profiles/preferences/update.js.erb new file mode 100644 index 0000000000..70b786d12e --- /dev/null +++ b/app/views/profiles/preferences/update.js.erb @@ -0,0 +1 @@ +// TODO diff --git a/config/routes.rb b/config/routes.rb index f4a104664f..9b1a746f54 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -222,6 +222,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] diff --git a/features/profile/active_tab.feature b/features/profile/active_tab.feature index 7801ae5b8c..1fa4ac88dd 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/steps/profile/active_tab.rb b/features/steps/profile/active_tab.rb index 8595ee876a..79e3b55f6e 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/shared/paths.rb b/features/steps/shared/paths.rb index 09ae7e3a30..3bd0d60281 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/spec/controllers/profiles/preferences_controller_spec.rb b/spec/controllers/profiles/preferences_controller_spec.rb new file mode 100644 index 0000000000..87503b1ed4 --- /dev/null +++ b/spec/controllers/profiles/preferences_controller_spec.rb @@ -0,0 +1,70 @@ +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', + 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', + theme_id: '2' + }.with_indifferent_access + + expect(user).to receive(:update_attributes).with(prefs) + + go params: prefs + end + end + + context 'on unsuccessful update' do + # TODO (rspeicher): Can this happen? + 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/features/security/profile_access_spec.rb b/spec/features/security/profile_access_spec.rb index 2512a9c0e3..2b09771851 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/routing/routing_spec.rb b/spec/routing/routing_spec.rb index 953c8dd8dd..199851be48 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 From 3cae81a5975bf3cc6941a3d8e9cb0900d154446f Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 5 Jun 2015 13:57:01 -0400 Subject: [PATCH 02/21] Move the "Design" templates and logic to Preferences cherry-picked --- app/assets/javascripts/profile.js.coffee | 2 +- .../profiles/preferences_controller.rb | 5 +- app/controllers/profiles_controller.rb | 22 ++++--- app/views/profiles/design.html.haml | 56 ------------------ app/views/profiles/preferences/show.html.haml | 57 ++++++++++++++++++- app/views/profiles/preferences/update.js.erb | 4 +- app/views/profiles/update.js.erb | 3 - config/routes.rb | 1 - features/profile/profile.feature | 13 ----- features/steps/profile/profile.rb | 21 ------- spec/features/profiles/preferences_spec.rb | 33 +++++++++++ 11 files changed, 112 insertions(+), 105 deletions(-) delete mode 100644 app/views/profiles/design.html.haml delete mode 100644 app/views/profiles/update.js.erb create mode 100644 spec/features/profiles/preferences_spec.rb diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee index 40459a9a15..a402973a54 100644 --- a/app/assets/javascripts/profile.js.coffee +++ b/app/assets/javascripts/profile.js.coffee @@ -4,7 +4,7 @@ class @Profile # Submit the form $('.edit_user').submit() - new Flash("Appearance settings saved", "notice") + new Flash('Preferences saved.', 'notice') $('.update-username form').on 'ajax:before', -> $('.loading-gif').show() diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb index 897e6fe074..8b2630d164 100644 --- a/app/controllers/profiles/preferences_controller.rb +++ b/app/controllers/profiles/preferences_controller.rb @@ -24,6 +24,9 @@ class Profiles::PreferencesController < Profiles::ApplicationController end def preferences_params - params.require(:user).permit(:color_scheme_id, :theme_id) + params.require(:user).permit( + :color_scheme_id, + :theme_id + ) end end diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index f4366c18e7..88e8799627 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 @@ -65,10 +62,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/views/profiles/design.html.haml b/app/views/profiles/design.html.haml deleted file mode 100644 index f450ec1c01..0000000000 --- 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 index 1333ed77b7..2fc47227c3 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -1 +1,56 @@ -TODO +- 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_preferences_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/update.js.erb b/app/views/profiles/preferences/update.js.erb index 70b786d12e..db37619136 100644 --- a/app/views/profiles/preferences/update.js.erb +++ b/app/views/profiles/preferences/update.js.erb @@ -1 +1,3 @@ -// TODO +// 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/app/views/profiles/update.js.erb b/app/views/profiles/update.js.erb deleted file mode 100644 index db37619136..0000000000 --- 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/routes.rb b/config/routes.rb index 9b1a746f54..52c98541da 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 diff --git a/features/profile/profile.feature b/features/profile/profile.feature index d586167cdf..0dd0afde8b 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/profile.rb b/features/steps/profile/profile.rb index 32e6859eff..649aea8e3f 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/spec/features/profiles/preferences_spec.rb b/spec/features/profiles/preferences_spec.rb new file mode 100644 index 0000000000..0e033652a9 --- /dev/null +++ b/spec/features/profiles/preferences_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe 'Profile > Preferences' do + let(:user) { create(:user) } + + before do + login_as(user) + end + + describe 'User changes their application theme', js: true do + let(:default_class) { Gitlab::Theme.css_class_by_id(nil) } + let(:theme_5_class) { Gitlab::Theme.css_class_by_id(5) } + + before do + visit profile_preferences_path + end + + it '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' do + before do + visit profile_preferences_path + end + end +end From 59ab9cc9fc5c7c594bcea574d2f7fda455562a32 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 5 Jun 2015 16:39:01 -0400 Subject: [PATCH 03/21] Remove js handler from Profiles#update It was only used for the appearance live updating, which is now handled by Profiles::Preferences#update cherry-picked --- app/controllers/profiles_controller.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index 88e8799627..b4af9e490e 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -26,7 +26,6 @@ class ProfilesController < Profiles::ApplicationController respond_to do |format| format.html { redirect_to :back } - format.js end end From 122e83c32612951e52bffe0bf770cce3e35115d6 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 5 Jun 2015 18:01:45 -0400 Subject: [PATCH 04/21] Simplify the javascript behavior for Preference updating cherry-picked --- app/assets/javascripts/profile.js.coffee | 9 +++------ app/views/profiles/preferences/show.html.haml | 14 ++++++-------- app/views/profiles/preferences/update.js.erb | 1 + spec/features/profiles/preferences_spec.rb | 10 +++++++++- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee index a402973a54..bb0b66b86e 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('Preferences 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/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index 2fc47227c3..59df849780 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -5,7 +5,7 @@ Appearance settings will be saved to your profile and made available across all devices. %hr -= form_for @user, url: profile_preferences_path, remote: true, method: :put do |f| += form_for @user, url: profile_preferences_path, remote: true, method: :put, html: {class: 'js-preferences-form'} do |f| .panel.panel-default.application-theme .panel-heading Application theme @@ -47,10 +47,8 @@ .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 + - 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 diff --git a/app/views/profiles/preferences/update.js.erb b/app/views/profiles/preferences/update.js.erb index db37619136..ad4118eabc 100644 --- a/app/views/profiles/preferences/update.js.erb +++ b/app/views/profiles/preferences/update.js.erb @@ -1,3 +1,4 @@ // Remove body class for any previous theme, re-add current one $('body').removeClass('<%= Gitlab::Theme.body_classes %>') $('body').addClass('<%= app_theme %> <%= theme_type %>') +new Flash('<%= flash.discard(:notice) %>', 'notice') diff --git a/spec/features/profiles/preferences_spec.rb b/spec/features/profiles/preferences_spec.rb index 0e033652a9..04aa64343d 100644 --- a/spec/features/profiles/preferences_spec.rb +++ b/spec/features/profiles/preferences_spec.rb @@ -15,7 +15,15 @@ describe 'Profile > Preferences' do visit profile_preferences_path end - it 'changes immediately' do + it 'creates a flash message' do + choose "user_theme_id_#{theme.id}" + + within('.flash-container') do + expect(page).to have_content('Preferences saved.') + end + end + + it 'reflects the changes immediately' do expect(page).to have_selector("body.#{default.css_class}") choose "user_theme_id_#{theme.id}" From 7f702b33b91ad40c2d2648a6700c964ad2f3a905 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 10 Jun 2015 03:23:28 -0400 Subject: [PATCH 05/21] Add `dashboard` attribute to User model cherry-picked --- app/controllers/profiles/preferences_controller.rb | 1 + app/models/user.rb | 7 ++++++- db/migrate/20150610065936_add_dashboard_to_users.rb | 9 +++++++++ db/schema.rb | 8 +++++--- spec/controllers/profiles/preferences_controller_spec.rb | 2 ++ spec/models/user_spec.rb | 3 ++- 6 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 db/migrate/20150610065936_add_dashboard_to_users.rb diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb index 8b2630d164..e43a247f72 100644 --- a/app/controllers/profiles/preferences_controller.rb +++ b/app/controllers/profiles/preferences_controller.rb @@ -26,6 +26,7 @@ class Profiles::PreferencesController < Profiles::ApplicationController def preferences_params params.require(:user).permit( :color_scheme_id, + :dashboard, :theme_id ) end diff --git a/app/models/user.rb b/app/models/user.rb index 8be0b62270..6ac287203b 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/db/migrate/20150610065936_add_dashboard_to_users.rb b/db/migrate/20150610065936_add_dashboard_to_users.rb new file mode 100644 index 0000000000..2628e45072 --- /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 aea0742cf3..f063a4868b 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: 20150529150354) do +ActiveRecord::Schema.define(version: 20150610065936) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -29,12 +29,13 @@ ActiveRecord::Schema.define(version: 20150529150354) do t.boolean "twitter_sharing_enabled", default: true t.text "restricted_visibility_levels" t.boolean "version_check_enabled", default: true - t.integer "max_attachment_size", default: 10, null: false + t.integer "max_attachment_size", default: 10, null: false t.integer "default_project_visibility" t.integer "default_snippet_visibility" t.text "restricted_signup_domains" t.boolean "user_oauth_applications", default: true t.string "after_sign_out_path" + t.integer "session_expire_delay", default: 10080, null: false end create_table "broadcast_messages", force: true do |t| @@ -495,12 +496,13 @@ ActiveRecord::Schema.define(version: 20150529150354) do t.string "bitbucket_access_token" t.string "bitbucket_access_token_secret" t.string "location" - t.string "public_email", default: "", null: false t.string "encrypted_otp_secret" t.string "encrypted_otp_secret_iv" t.string "encrypted_otp_secret_salt" 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/spec/controllers/profiles/preferences_controller_spec.rb b/spec/controllers/profiles/preferences_controller_spec.rb index 87503b1ed4..646aa0320b 100644 --- a/spec/controllers/profiles/preferences_controller_spec.rb +++ b/spec/controllers/profiles/preferences_controller_spec.rb @@ -25,6 +25,7 @@ describe Profiles::PreferencesController do def go(params: {}, format: :js) params.reverse_merge!( color_scheme_id: '1', + dashboard: 'stars', theme_id: '1' ) @@ -40,6 +41,7 @@ describe Profiles::PreferencesController do it "changes the user's preferences" do prefs = { color_scheme_id: '1', + dashboard: 'stars', theme_id: '2' }.with_indifferent_access diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index f1b8afa585..5bf6ce3536 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' From bf44988a302499cb85d070ace355d3915ecd284b Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 10 Jun 2015 03:59:39 -0400 Subject: [PATCH 06/21] Add RootController This controller is now the target for `root_url`. It sub-classes DashboardController so we can render the old default without a redirect if the user hasn't customized their dashboard location. cherry-picked --- app/controllers/root_controller.rb | 18 ++++++++++++ app/views/layouts/nav/_dashboard.html.haml | 2 +- config/routes.rb | 2 +- spec/controllers/root_controller_spec.rb | 32 ++++++++++++++++++++++ spec/routing/routing_spec.rb | 10 +++++-- 5 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 app/controllers/root_controller.rb create mode 100644 spec/controllers/root_controller_spec.rb diff --git a/app/controllers/root_controller.rb b/app/controllers/root_controller.rb new file mode 100644 index 0000000000..7606d2d0fb --- /dev/null +++ b/app/controllers/root_controller.rb @@ -0,0 +1,18 @@ +# 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 + def show + case current_user.try(:dashboard) + when 'stars' + redirect_to starred_dashboard_projects_path + else + super + end + end +end diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index d46dba4a24..83e6fe863f 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 + = link_to dashboard_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do = icon('dashboard fw') %span Your Projects diff --git a/config/routes.rb b/config/routes.rb index 52c98541da..d60bc796fd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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/spec/controllers/root_controller_spec.rb b/spec/controllers/root_controller_spec.rb new file mode 100644 index 0000000000..abbbf6855f --- /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/routing/routing_spec.rb b/spec/routing/routing_spec.rb index 199851be48..f268e4755d 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -204,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 @@ -220,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 From 5e0a812669382a18d2688854cb14755078c44eea Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 10 Jun 2015 04:36:09 -0400 Subject: [PATCH 07/21] Account for RootController for dashboard navigation and Dispatch JS cherry-picked --- app/assets/javascripts/dispatcher.js.coffee | 2 +- app/views/layouts/nav/_dashboard.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index da56e3cdbc..b7ebe6a5c8 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/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 83e6fe863f..687c1fc3dd 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -1,5 +1,5 @@ %ul.nav.nav-sidebar - = nav_link(path: 'dashboard#show', html_options: {class: 'home'}) 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 From 878732b4264eeba0214964da096b6f8c139f6900 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 10 Jun 2015 04:42:02 -0400 Subject: [PATCH 08/21] Add a form field to customize the dashboard preference cherry-picked --- app/helpers/preferences_helper.rb | 21 +++++++++++++++++++ app/views/profiles/preferences/show.html.haml | 19 +++++++++++++++-- spec/helpers/preferences_helper_spec.rb | 20 ++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 app/helpers/preferences_helper.rb create mode 100644 spec/helpers/preferences_helper_spec.rb diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb new file mode 100644 index 0000000000..247be8239c --- /dev/null +++ b/app/helpers/preferences_helper.rb @@ -0,0 +1,21 @@ +# Helper methods for per-User preferences +module PreferencesHelper + # Populates the dashboard preference select field with more user-friendly + # values. + def dashboard_choices + orig = User.dashboards.keys + + choices = [ + ['Projects (default)', orig[0]], + ['Starred Projects', orig[1]] + ] + + if orig.size != choices.size + # Assure that anyone adding new options updates this method too + raise RuntimeError, "`User` defines #{orig.size} dashboard choices," + + " but #{__method__} defined #{choices.size}" + else + choices + end + end +end diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index 59df849780..773a324e1a 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -1,8 +1,10 @@ -- page_title "Design" +- page_title 'Preferences' %h3.page-title = page_title %p.light - Appearance settings will be saved to your profile and made available across all devices. + 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'} do |f| @@ -52,3 +54,16 @@ .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' + .col-sm-10 + = f.select :dashboard, dashboard_choices, {}, class: 'form-control' + %p.help-block.hint + This setting allows you to customize the default Dashboard page. + .panel-footer + = f.submit 'Save', class: 'btn btn-save' diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb new file mode 100644 index 0000000000..2a70672ce4 --- /dev/null +++ b/spec/helpers/preferences_helper_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe PreferencesHelper do + describe 'dashboard_choices' do + it 'raises an exception when defined choices may be missing' do + dashboards = User.dashboards + expect(User).to receive(:dashboards). + and_return(dashboards.merge(foo: 'foo')) + + expect { dashboard_choices }.to raise_error + end + + it 'provides better option descriptions' do + choices = dashboard_choices + + expect(choices[0]).to eq ['Projects (default)', 'projects'] + expect(choices[1]).to eq ['Starred Projects', 'stars'] + end + end +end From 8dfe01cbb0dd5c0c271176150193293ecdf57cd3 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 10 Jun 2015 17:08:10 -0400 Subject: [PATCH 09/21] Add feature specs for default dashboard preference cherry-picked --- app/views/profiles/preferences/show.html.haml | 2 +- app/views/profiles/preferences/update.js.erb | 5 ++ spec/features/profiles/preferences_spec.rb | 59 +++++++++++++++---- 3 files changed, 55 insertions(+), 11 deletions(-) diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index 773a324e1a..db4d1dbe2a 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -60,7 +60,7 @@ Behavior .panel-body .form-group - = f.label :dashboard, class: 'control-label' + = f.label :dashboard, 'Default Dashboard', class: 'control-label' .col-sm-10 = f.select :dashboard, dashboard_choices, {}, class: 'form-control' %p.help-block.hint diff --git a/app/views/profiles/preferences/update.js.erb b/app/views/profiles/preferences/update.js.erb index ad4118eabc..5b7b92aac4 100644 --- a/app/views/profiles/preferences/update.js.erb +++ b/app/views/profiles/preferences/update.js.erb @@ -1,4 +1,9 @@ // Remove body class for any previous theme, re-add current one $('body').removeClass('<%= Gitlab::Theme.body_classes %>') $('body').addClass('<%= app_theme %> <%= theme_type %>') + +// Re-enable the "Save" button +$('input[type=submit]').enable() + +// Show the notice flash message new Flash('<%= flash.discard(:notice) %>', 'notice') diff --git a/spec/features/profiles/preferences_spec.rb b/spec/features/profiles/preferences_spec.rb index 04aa64343d..dcef436c57 100644 --- a/spec/features/profiles/preferences_spec.rb +++ b/spec/features/profiles/preferences_spec.rb @@ -5,22 +5,25 @@ describe 'Profile > Preferences' do before do login_as(user) + visit profile_preferences_path end describe 'User changes their application theme', js: true do let(:default_class) { Gitlab::Theme.css_class_by_id(nil) } let(:theme_5_class) { Gitlab::Theme.css_class_by_id(5) } - before do - visit profile_preferences_path - end - it 'creates a flash message' do choose "user_theme_id_#{theme.id}" - within('.flash-container') do - expect(page).to have_content('Preferences saved.') - end + expect_preferences_saved_message + end + + it 'updates their preference' do + choose "user_theme_id_#{theme.id}" + + visit page.current_path + + expect(page).to have_checked_field("user_theme_id_#{theme.id}") end it 'reflects the changes immediately' do @@ -33,9 +36,45 @@ describe 'Profile > Preferences' do end end - describe 'User changes their syntax highlighting theme' do - before do - visit profile_preferences_path + 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' + + visit page.current_path + + expect(page).to have_checked_field('user_color_scheme_id_5') + 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 From f22d80ad7587ee55cb2d07320ea46dbe7d6156a8 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 10 Jun 2015 17:38:11 -0400 Subject: [PATCH 10/21] Make the dashboard choice text match the text in the sidebar cherry-picked --- app/helpers/preferences_helper.rb | 4 ++-- spec/helpers/preferences_helper_spec.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb index 247be8239c..4349f12ec5 100644 --- a/app/helpers/preferences_helper.rb +++ b/app/helpers/preferences_helper.rb @@ -6,8 +6,8 @@ module PreferencesHelper orig = User.dashboards.keys choices = [ - ['Projects (default)', orig[0]], - ['Starred Projects', orig[1]] + ['Your Projects (default)', orig[0]], + ['Starred Projects', orig[1]] ] if orig.size != choices.size diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb index 2a70672ce4..46d104a8e8 100644 --- a/spec/helpers/preferences_helper_spec.rb +++ b/spec/helpers/preferences_helper_spec.rb @@ -13,8 +13,8 @@ describe PreferencesHelper do it 'provides better option descriptions' do choices = dashboard_choices - expect(choices[0]).to eq ['Projects (default)', 'projects'] - expect(choices[1]).to eq ['Starred Projects', 'stars'] + expect(choices[0]).to eq ['Your Projects (default)', 'projects'] + expect(choices[1]).to eq ['Starred Projects', 'stars'] end end end From 2b4b05727db923d901f90571be94c64f0e1f5fdd Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 10 Jun 2015 18:19:06 -0400 Subject: [PATCH 11/21] Spec the failure cases for PreferencesController#update cherry-picked --- .../profiles/preferences_controller.rb | 13 ++++++++---- .../profiles/preferences_controller_spec.rb | 20 +++++++++++++++++-- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb index e43a247f72..538b09ca54 100644 --- a/app/controllers/profiles/preferences_controller.rb +++ b/app/controllers/profiles/preferences_controller.rb @@ -5,10 +5,15 @@ class Profiles::PreferencesController < Profiles::ApplicationController end def update - if @user.update_attributes(preferences_params) - flash[:notice] = 'Preferences saved.' - else - # TODO (rspeicher): There's no validation on these values, so can it fail? + 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| diff --git a/spec/controllers/profiles/preferences_controller_spec.rb b/spec/controllers/profiles/preferences_controller_spec.rb index 646aa0320b..1f0943c93d 100644 --- a/spec/controllers/profiles/preferences_controller_spec.rb +++ b/spec/controllers/profiles/preferences_controller_spec.rb @@ -51,8 +51,24 @@ describe Profiles::PreferencesController do end end - context 'on unsuccessful update' do - # TODO (rspeicher): Can this happen? + 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 From 1779cbfe88b25b3ee86d00989711863aacf79e9b Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 10 Jun 2015 17:37:48 -0400 Subject: [PATCH 12/21] Add docs for Profile > Preferences Also converts doc/README.md to Unix line endings cherry-picked --- doc/README.md | 71 +++++++++++++++++++------------------- doc/profile/preferences.md | 33 ++++++++++++++++++ doc/profile/profile.md | 3 ++ 3 files changed, 72 insertions(+), 35 deletions(-) create mode 100644 doc/profile/preferences.md create mode 100644 doc/profile/profile.md diff --git a/doc/README.md b/doc/README.md index 7a2181edde..451abf8b79 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/profile.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/profile/preferences.md b/doc/profile/preferences.md new file mode 100644 index 0000000000..0c12eb0c65 --- /dev/null +++ b/doc/profile/preferences.md @@ -0,0 +1,33 @@ +# 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 settings 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 to 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 **Projects**. diff --git a/doc/profile/profile.md b/doc/profile/profile.md new file mode 100644 index 0000000000..032d62cf88 --- /dev/null +++ b/doc/profile/profile.md @@ -0,0 +1,3 @@ +# Profile Settings + +- [Preferences](preferences.md) From ba60b742e1dbb367c87a73cc4d6910760cf0f317 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 11 Jun 2015 22:54:21 -0400 Subject: [PATCH 13/21] Move 2FA docs from "Workflow" to "Profile Settings" cherry-picked --- doc/README.md | 2 +- doc/{workflow => profile}/2fa.png | Bin doc/{workflow => profile}/2fa_auth.png | Bin doc/profile/README.md | 4 +++ doc/profile/profile.md | 3 -- .../two_factor_authentication.md | 0 doc/workflow/README.md | 31 +++++++++--------- 7 files changed, 20 insertions(+), 20 deletions(-) rename doc/{workflow => profile}/2fa.png (100%) rename doc/{workflow => profile}/2fa_auth.png (100%) create mode 100644 doc/profile/README.md delete mode 100644 doc/profile/profile.md rename doc/{workflow => profile}/two_factor_authentication.md (100%) diff --git a/doc/README.md b/doc/README.md index 451abf8b79..2845961325 100644 --- a/doc/README.md +++ b/doc/README.md @@ -7,7 +7,7 @@ - [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/profile.md) +- [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. diff --git a/doc/workflow/2fa.png b/doc/profile/2fa.png similarity index 100% rename from doc/workflow/2fa.png rename to doc/profile/2fa.png diff --git a/doc/workflow/2fa_auth.png b/doc/profile/2fa_auth.png similarity index 100% rename from doc/workflow/2fa_auth.png rename to doc/profile/2fa_auth.png diff --git a/doc/profile/README.md b/doc/profile/README.md new file mode 100644 index 0000000000..6f8359d87f --- /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/profile.md b/doc/profile/profile.md deleted file mode 100644 index 032d62cf88..0000000000 --- a/doc/profile/profile.md +++ /dev/null @@ -1,3 +0,0 @@ -# Profile Settings - -- [Preferences](preferences.md) diff --git a/doc/workflow/two_factor_authentication.md b/doc/profile/two_factor_authentication.md similarity index 100% rename from doc/workflow/two_factor_authentication.md rename to doc/profile/two_factor_authentication.md diff --git a/doc/workflow/README.md b/doc/workflow/README.md index 70a8179c8e..f1959d3013 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) From 2797daade9cbdd85ed05a3a0d2b4f26989bc6744 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 11 Jun 2015 23:08:47 -0400 Subject: [PATCH 14/21] Fix alignment of Behavior form; add documentation link cherry-picked --- app/views/profiles/preferences/show.html.haml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index db4d1dbe2a..4cae8e61e7 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -7,7 +7,7 @@ access the site. %hr -= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: {class: 'js-preferences-form'} do |f| += 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 @@ -60,7 +60,9 @@ Behavior .panel-body .form-group - = f.label :dashboard, 'Default Dashboard', class: 'control-label' + = 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' %p.help-block.hint From 17af09b19075e74688ae9b78ccd773230e5b46fc Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 12 Jun 2015 20:20:53 -0400 Subject: [PATCH 15/21] Remove redundant help text from custom dashboard selection cherry-picked --- app/views/profiles/preferences/show.html.haml | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index 4cae8e61e7..79709da8fa 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -65,7 +65,5 @@ = link_to('(?)', help_page_path('profile', 'preferences') + '#default-dashboard', target: '_blank') .col-sm-10 = f.select :dashboard, dashboard_choices, {}, class: 'form-control' - %p.help-block.hint - This setting allows you to customize the default Dashboard page. .panel-footer = f.submit 'Save', class: 'btn btn-save' From 7cff7d6fce29511230314123a9de1d78b048fa29 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 12 Jun 2015 20:39:48 -0400 Subject: [PATCH 16/21] Refactor dashboard_choices cherry-picked --- app/helpers/preferences_helper.rb | 29 ++++++++++++++----------- spec/helpers/preferences_helper_spec.rb | 18 ++++++++------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb index 4349f12ec5..f58e2c6635 100644 --- a/app/helpers/preferences_helper.rb +++ b/app/helpers/preferences_helper.rb @@ -1,21 +1,24 @@ # Helper methods for per-User preferences module PreferencesHelper - # Populates the dashboard preference select field with more user-friendly - # values. + # 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 - orig = User.dashboards.keys + defined = User.dashboards - choices = [ - ['Your Projects (default)', orig[0]], - ['Starred Projects', orig[1]] - ] - - if orig.size != choices.size - # Assure that anyone adding new options updates this method too - raise RuntimeError, "`User` defines #{orig.size} dashboard choices," + - " but #{__method__} defined #{choices.size}" + 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 - choices + defined.map do |key, _| + # Use `fetch` so `KeyError` gets raised when a key is missing + [DASHBOARD_CHOICES.fetch(key), key] + end end end end diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb index 46d104a8e8..0fba3ba563 100644 --- a/spec/helpers/preferences_helper_spec.rb +++ b/spec/helpers/preferences_helper_spec.rb @@ -3,18 +3,20 @@ require 'spec_helper' describe PreferencesHelper do describe 'dashboard_choices' do it 'raises an exception when defined choices may be missing' do - dashboards = User.dashboards - expect(User).to receive(:dashboards). - and_return(dashboards.merge(foo: 'foo')) + expect(User).to receive(:dashboards).and_return(foo: 'foo') + expect { dashboard_choices }.to raise_error(RuntimeError) + end - expect { dashboard_choices }.to raise_error + 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 - choices = dashboard_choices - - expect(choices[0]).to eq ['Your Projects (default)', 'projects'] - expect(choices[1]).to eq ['Starred Projects', 'stars'] + expect(dashboard_choices).to match_array [ + ['Your Projects (default)', 'projects'], + ['Starred Projects', 'stars'] + ] end end end From f323b8c4e5f43597d126dbed2fa27455a8c2e416 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 12 Jun 2015 20:53:58 -0400 Subject: [PATCH 17/21] Refactor RootController cherry-picked --- app/controllers/root_controller.rb | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/controllers/root_controller.rb b/app/controllers/root_controller.rb index 7606d2d0fb..fdfe00dc13 100644 --- a/app/controllers/root_controller.rb +++ b/app/controllers/root_controller.rb @@ -7,12 +7,22 @@ # 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 - case current_user.try(:dashboard) + 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 - super + return end end end From 0d4eb312d95e3518386202a776cd1a0ce5573d2c Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sun, 14 Jun 2015 20:33:29 -0400 Subject: [PATCH 18/21] Add `allowing_for_delay` helper method for feature specs cherry-picked --- spec/features/profiles/preferences_spec.rb | 14 +++++---- spec/support/capybara.rb | 33 ++++++++++++++++++++++ 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/spec/features/profiles/preferences_spec.rb b/spec/features/profiles/preferences_spec.rb index dcef436c57..c4d9281b1f 100644 --- a/spec/features/profiles/preferences_spec.rb +++ b/spec/features/profiles/preferences_spec.rb @@ -21,9 +21,10 @@ describe 'Profile > Preferences' do it 'updates their preference' do choose "user_theme_id_#{theme.id}" - visit page.current_path - - expect(page).to have_checked_field("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 @@ -46,9 +47,10 @@ describe 'Profile > Preferences' do it 'updates their preference' do choose 'user_color_scheme_id_5' - visit page.current_path - - expect(page).to have_checked_field('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 diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb index fed1ab6ee3..3e41aec425 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 From 42d8ebebb9ca504793eaa4bca633d9ac0f6e9666 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 15 Jun 2015 00:39:13 -0400 Subject: [PATCH 19/21] Fix doc typos cherry-picked --- doc/profile/preferences.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/doc/profile/preferences.md b/doc/profile/preferences.md index 0c12eb0c65..ce5f193678 100644 --- a/doc/profile/preferences.md +++ b/doc/profile/preferences.md @@ -5,8 +5,8 @@ various aspects of the site to their liking. ## Application theme -Changing this settings allows the user to customize the color scheme used for -the navigation bar on the left side of the screen. +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**. @@ -25,9 +25,8 @@ 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 to 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. +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 **Projects**. +The default is **Your Projects**. From a28686d3317efab7fe407e86ddf08bbac71b1a96 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 16 Jun 2015 12:47:02 -0400 Subject: [PATCH 20/21] Fix preferences/show --- app/views/profiles/preferences/show.html.haml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index 79709da8fa..18692bd32a 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -49,11 +49,12 @@ .panel-heading Code preview 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 + .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.tr('-_', ' ').titleize .panel.panel-default .panel-heading From 41dadc37a55460f27eba860b385993f4a8ed268a Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 16 Jun 2015 12:50:18 -0400 Subject: [PATCH 21/21] Fix Preferences feature spec --- spec/features/profiles/preferences_spec.rb | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/spec/features/profiles/preferences_spec.rb b/spec/features/profiles/preferences_spec.rb index c4d9281b1f..e1182e82f5 100644 --- a/spec/features/profiles/preferences_spec.rb +++ b/spec/features/profiles/preferences_spec.rb @@ -1,10 +1,8 @@ require 'spec_helper' describe 'Profile > Preferences' do - let(:user) { create(:user) } - before do - login_as(user) + login_as(:user) visit profile_preferences_path end @@ -13,27 +11,27 @@ describe 'Profile > Preferences' do let(:theme_5_class) { Gitlab::Theme.css_class_by_id(5) } it 'creates a flash message' do - choose "user_theme_id_#{theme.id}" + choose 'user_theme_id_5' expect_preferences_saved_message end it 'updates their preference' do - choose "user_theme_id_#{theme.id}" + choose 'user_theme_id_5' allowing_for_delay do visit page.current_path - expect(page).to have_checked_field("user_theme_id_#{theme.id}") + expect(page).to have_checked_field("user_theme_id_5") end end it 'reflects the changes immediately' do - expect(page).to have_selector("body.#{default.css_class}") + expect(page).to have_selector("body.#{default_class}") - choose "user_theme_id_#{theme.id}" + choose 'user_theme_id_5' - expect(page).not_to have_selector("body.#{default.css_class}") - expect(page).to have_selector("body.#{theme.css_class}") + expect(page).not_to have_selector("body.#{default_class}") + expect(page).to have_selector("body.#{theme_5_class}") end end