Commit a3d98ab2 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'rs-custom-dashboard-backport' into '7-12-stable'

Backport custom dashboard

This is a backport of !778 without the `Gitlab::Themes` refactor for the 7.12 branch.

See merge request !828
parents 15d7432c 41dadc37
...@@ -55,7 +55,7 @@ class Dispatcher ...@@ -55,7 +55,7 @@ class Dispatcher
when 'projects:merge_requests:index' when 'projects:merge_requests:index'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
MergeRequests.init() MergeRequests.init()
when 'dashboard:show' when 'dashboard:show', 'root:show'
new Dashboard() new Dashboard()
new Activities() new Activities()
when 'dashboard:projects:starred' when 'dashboard:projects:starred'
......
class @Profile class @Profile
constructor: -> constructor: ->
$('.edit_user .application-theme input, .edit_user .code-preview-theme input').click -> # Automatically submit the Preferences form when any of its radio buttons change
# Submit the form $('.js-preferences-form').on 'change.preference', 'input[type=radio]', ->
$('.edit_user').submit() $(this).parents('form').submit()
new Flash("Appearance settings saved", "notice")
$('.update-username form').on 'ajax:before', -> $('.update-username form').on 'ajax:before', ->
$('.loading-gif').show() $('.loading-gif').show()
...@@ -18,7 +16,6 @@ class @Profile ...@@ -18,7 +16,6 @@ class @Profile
$('.update-notifications').on 'ajax:complete', -> $('.update-notifications').on 'ajax:complete', ->
$(this).find('.btn-save').enable() $(this).find('.btn-save').enable()
$('.js-choose-user-avatar-button').bind "click", -> $('.js-choose-user-avatar-button').bind "click", ->
form = $(this).closest("form") form = $(this).closest("form")
form.find(".js-user-avatar-input").click() form.find(".js-user-avatar-input").click()
......
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
...@@ -8,9 +8,6 @@ class ProfilesController < Profiles::ApplicationController ...@@ -8,9 +8,6 @@ class ProfilesController < Profiles::ApplicationController
def show def show
end end
def design
end
def applications def applications
@applications = current_user.oauth_applications @applications = current_user.oauth_applications
@authorized_tokens = current_user.oauth_authorized_tokens @authorized_tokens = current_user.oauth_authorized_tokens
...@@ -29,7 +26,6 @@ class ProfilesController < Profiles::ApplicationController ...@@ -29,7 +26,6 @@ class ProfilesController < Profiles::ApplicationController
respond_to do |format| respond_to do |format|
format.html { redirect_to :back } format.html { redirect_to :back }
format.js
end end
end end
...@@ -65,10 +61,21 @@ class ProfilesController < Profiles::ApplicationController ...@@ -65,10 +61,21 @@ class ProfilesController < Profiles::ApplicationController
def user_params def user_params
params.require(:user).permit( params.require(:user).permit(
:email, :password, :password_confirmation, :bio, :name, :avatar,
:username, :skype, :linkedin, :twitter, :website_url, :bio,
:color_scheme_id, :theme_id, :avatar, :hide_no_ssh_key, :email,
:hide_no_password, :location, :public_email :hide_no_password,
:hide_no_ssh_key,
:linkedin,
:location,
:name,
:password,
:password_confirmation,
:public_email,
:skype,
:twitter,
:username,
:website_url
) )
end end
end end
# 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
# Helper methods for per-User preferences
module PreferencesHelper
# 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
end
...@@ -50,12 +50,13 @@ ...@@ -50,12 +50,13 @@
# bitbucket_access_token :string(255) # bitbucket_access_token :string(255)
# bitbucket_access_token_secret :string(255) # bitbucket_access_token_secret :string(255)
# location :string(255) # location :string(255)
# public_email :string(255) default(""), not null
# encrypted_otp_secret :string(255) # encrypted_otp_secret :string(255)
# encrypted_otp_secret_iv :string(255) # encrypted_otp_secret_iv :string(255)
# encrypted_otp_secret_salt :string(255) # encrypted_otp_secret_salt :string(255)
# otp_required_for_login :boolean # otp_required_for_login :boolean
# otp_backup_codes :text # otp_backup_codes :text
# public_email :string(255) default(""), not null # dashboard :integer default(0)
# #
require 'carrierwave/orm/activerecord' require 'carrierwave/orm/activerecord'
...@@ -701,4 +702,8 @@ class User < ActiveRecord::Base ...@@ -701,4 +702,8 @@ class User < ActiveRecord::Base
def can_be_removed? def can_be_removed?
!solo_owned_groups.present? !solo_owned_groups.present?
end end
# User's Dashboard preference
# Note: When adding an option, it MUST go on the end of the array.
enum dashboard: [:projects, :stars]
end end
%ul.nav.nav-sidebar %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 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') = icon('dashboard fw')
%span %span
Your Projects Your Projects
......
...@@ -38,11 +38,12 @@ ...@@ -38,11 +38,12 @@
%span %span
SSH Keys SSH Keys
%span.count= current_user.keys.count %span.count= current_user.keys.count
= nav_link(path: 'profiles#design') do = nav_link(controller: :preferences) do
= link_to design_profile_path, title: 'Design', data: {placement: 'right'} do = link_to profile_preferences_path, title: 'Preferences', data: {placement: 'right'} do
-# TODO (rspeicher): Better icon?
= icon('image fw') = icon('image fw')
%span %span
Design Preferences
= nav_link(path: 'profiles#history') do = nav_link(path: 'profiles#history') do
= link_to history_profile_path, title: 'History', data: {placement: 'right'} do = link_to history_profile_path, title: 'History', data: {placement: 'right'} do
= icon('history fw') = icon('history fw')
......
- page_title "Design" - page_title 'Preferences'
%h3.page-title %h3.page-title
= page_title = page_title
%p.light %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 %hr
= form_for @user, url: profile_path, remote: true, method: :put 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.panel-default.application-theme
.panel-heading .panel-heading
Application theme Application theme
...@@ -50,7 +52,19 @@ ...@@ -50,7 +52,19 @@
.code_highlight_opts .code_highlight_opts
- color_schemes.each do |color_scheme_id, color_scheme| - color_schemes.each do |color_scheme_id, color_scheme|
= label_tag do = label_tag do
.prev .prev= image_tag "#{color_scheme}-scheme-preview.png"
= image_tag "#{color_scheme}-scheme-preview.png"
= f.radio_button :color_scheme_id, color_scheme_id = f.radio_button :color_scheme_id, color_scheme_id
= color_scheme.gsub(/[-_]+/, ' ').humanize = 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'
// Remove body class for any previous theme, re-add current one // Remove body class for any previous theme, re-add current one
$('body').removeClass('<%= Gitlab::Theme.body_classes %>') $('body').removeClass('<%= Gitlab::Theme.body_classes %>')
$('body').addClass('<%= app_theme %> <%= theme_type %>') $('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')
...@@ -203,7 +203,6 @@ Gitlab::Application.routes.draw do ...@@ -203,7 +203,6 @@ Gitlab::Application.routes.draw do
resource :profile, only: [:show, :update] do resource :profile, only: [:show, :update] do
member do member do
get :history get :history
get :design
get :applications get :applications
put :reset_private_token put :reset_private_token
...@@ -222,6 +221,7 @@ Gitlab::Application.routes.draw do ...@@ -222,6 +221,7 @@ Gitlab::Application.routes.draw do
put :reset put :reset
end end
end end
resource :preferences, only: [:show, :update]
resources :keys resources :keys
resources :emails, only: [:index, :create, :destroy] resources :emails, only: [:index, :create, :destroy]
resource :avatar, only: [:destroy] resource :avatar, only: [:destroy]
...@@ -293,7 +293,7 @@ Gitlab::Application.routes.draw do ...@@ -293,7 +293,7 @@ Gitlab::Application.routes.draw do
get '/users/auth/:provider/omniauth_error' => 'omniauth_callbacks#omniauth_error', as: :omniauth_error get '/users/auth/:provider/omniauth_error' => 'omniauth_callbacks#omniauth_error', as: :omniauth_error
end end
root to: "dashboard#show" root to: "root#show"
# #
# Project Area # Project Area
......
class AddDashboardToUsers < ActiveRecord::Migration
def up
add_column :users, :dashboard, :integer, default: 0
end
def down
remove_column :users, :dashboard
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # 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 # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -29,12 +29,13 @@ ActiveRecord::Schema.define(version: 20150529150354) do ...@@ -29,12 +29,13 @@ ActiveRecord::Schema.define(version: 20150529150354) do
t.boolean "twitter_sharing_enabled", default: true t.boolean "twitter_sharing_enabled", default: true
t.text "restricted_visibility_levels" t.text "restricted_visibility_levels"
t.boolean "version_check_enabled", default: true 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_project_visibility"
t.integer "default_snippet_visibility" t.integer "default_snippet_visibility"
t.text "restricted_signup_domains" t.text "restricted_signup_domains"
t.boolean "user_oauth_applications", default: true t.boolean "user_oauth_applications", default: true
t.string "after_sign_out_path" t.string "after_sign_out_path"
t.integer "session_expire_delay", default: 10080, null: false
end end
create_table "broadcast_messages", force: true do |t| create_table "broadcast_messages", force: true do |t|
...@@ -495,12 +496,13 @@ ActiveRecord::Schema.define(version: 20150529150354) do ...@@ -495,12 +496,13 @@ ActiveRecord::Schema.define(version: 20150529150354) do
t.string "bitbucket_access_token" t.string "bitbucket_access_token"
t.string "bitbucket_access_token_secret" t.string "bitbucket_access_token_secret"
t.string "location" t.string "location"
t.string "public_email", default: "", null: false
t.string "encrypted_otp_secret" t.string "encrypted_otp_secret"
t.string "encrypted_otp_secret_iv" t.string "encrypted_otp_secret_iv"
t.string "encrypted_otp_secret_salt" t.string "encrypted_otp_secret_salt"
t.boolean "otp_required_for_login" t.boolean "otp_required_for_login"
t.text "otp_backup_codes" t.text "otp_backup_codes"
t.string "public_email", default: "", null: false
t.integer "dashboard", default: 0
end end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
......
# Documentation # Documentation
## User documentation ## User documentation
- [API](api/README.md) Automate GitLab via a simple and powerful API. - [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. - [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). - [Importing to GitLab](workflow/importing/README.md).
- [Markdown](markdown/markdown.md) GitLab's advanced formatting system. - [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. - [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. - [Profile Settings](profile/README.md)
- [Public access](public_access/public_access.md) Learn how you can allow public and internal access to projects. - [Project Services](project_services/project_services.md) Integrate a project with external services, such as CI and chat.
- [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects. - [Public access](public_access/public_access.md) Learn how you can allow public and internal access to projects.
- [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project. - [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects.
- [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN. - [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
## 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. - [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when web hooks aren't enough.
- [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter. - [Install](install/README.md) Requirements, directory structures and installation from source.
- [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages. - [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter.
- [Libravatar](customization/libravatar.md) Use Libravatar for user avatars. - [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages.
- [Log system](logs/logs.md) Log system. - [Libravatar](customization/libravatar.md) Use Libravatar for user avatars.
- [Operations](operations/README.md) Keeping GitLab up and running - [Log system](logs/logs.md) Log system.
- [Raketasks](raketasks/README.md) Backups, maintenance, automatic web hook setup and the importing of projects. - [Operations](operations/README.md) Keeping GitLab up and running
- [Security](security/README.md) Learn what you can do to further secure your GitLab instance. - [Raketasks](raketasks/README.md) Backups, maintenance, automatic web hook setup and the importing of projects.
- [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed. - [Security](security/README.md) Learn what you can do to further secure your GitLab instance.
- [Update](update/README.md) Update guides to upgrade your installation. - [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed.
- [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page. - [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
## Contributor documentation
- [Development](development/README.md) Explains the architecture and the guidelines for shell commands.
- [Legal](legal/README.md) Contributor license agreements. - [Development](development/README.md) Explains the architecture and the guidelines for shell commands.
- [Release](release/README.md) How to make the monthly and security releases. - [Legal](legal/README.md) Contributor license agreements.
\ No newline at end of file - [Release](release/README.md) How to make the monthly and security releases.
# Profile Settings
- [Preferences](preferences.md)
- [Two-factor Authentication (2FA)](two_factor_authentication.md)
# 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**.
# Workflow # Workflow
- [Authorization for merge requests](authorization_for_merge_requests.md) - [Authorization for merge requests](authorization_for_merge_requests.md)
- [Change your time zone](timezone.md) - [Change your time zone](timezone.md)
- [Feature branch workflow](workflow.md) - [Feature branch workflow](workflow.md)
- [GitLab Flow](gitlab_flow.md) - [GitLab Flow](gitlab_flow.md)
- [Groups](groups.md) - [Groups](groups.md)
- [Keyboard shortcuts](shortcuts.md) - [Keyboard shortcuts](shortcuts.md)
- [Labels](labels.md) - [Labels](labels.md)
- [Notifications](notifications.md) - [Notifications](notifications.md)
- [Project Features](project_features.md) - [Project Features](project_features.md)
- [Project forking workflow](forking_workflow.md) - [Project forking workflow](forking_workflow.md)
- [Protected branches](protected_branches.md) - [Protected branches](protected_branches.md)
- [Two-factor Authentication (2FA)](two_factor_authentication.md) - [Web Editor](web_editor.md)
- [Web Editor](web_editor.md) - ["Work In Progress" Merge Requests](wip_merge_requests.md)
- ["Work In Progress" Merge Requests](wip_merge_requests.md)
\ No newline at end of file
...@@ -18,9 +18,9 @@ Feature: Profile Active Tab ...@@ -18,9 +18,9 @@ Feature: Profile Active Tab
Then the active main tab should be SSH Keys Then the active main tab should be SSH Keys
And no other main tabs should be active And no other main tabs should be active
Scenario: On Profile Design Scenario: On Profile Preferences
Given I visit profile design page Given I visit profile preferences page
Then the active main tab should be Design Then the active main tab should be Preferences
And no other main tabs should be active And no other main tabs should be active
Scenario: On Profile History Scenario: On Profile History
......
...@@ -84,16 +84,3 @@ Feature: Profile ...@@ -84,16 +84,3 @@ Feature: Profile
Then I visit profile applications page Then I visit profile applications page
And I click to remove application And I click to remove application
Then I see that application is removed 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
...@@ -15,8 +15,8 @@ class Spinach::Features::ProfileActiveTab < Spinach::FeatureSteps ...@@ -15,8 +15,8 @@ class Spinach::Features::ProfileActiveTab < Spinach::FeatureSteps
ensure_active_main_tab('SSH Keys') ensure_active_main_tab('SSH Keys')
end end
step 'the active main tab should be Design' do step 'the active main tab should be Preferences' do
ensure_active_main_tab('Design') ensure_active_main_tab('Preferences')
end end
step 'the active main tab should be History' do step 'the active main tab should be History' do
......
...@@ -114,27 +114,6 @@ class Spinach::Features::Profile < Spinach::FeatureSteps ...@@ -114,27 +114,6 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
expect(page).to have_content "#{current_user.name} closed issue" expect(page).to have_content "#{current_user.name} closed issue"
end 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 step 'my password is expired' do
current_user.update_attributes(password_expires_at: Time.now - 1.hour) current_user.update_attributes(password_expires_at: Time.now - 1.hour)
end end
......
...@@ -123,8 +123,8 @@ module SharedPaths ...@@ -123,8 +123,8 @@ module SharedPaths
visit profile_keys_path visit profile_keys_path
end end
step 'I visit profile design page' do step 'I visit profile preferences page' do
visit design_profile_path visit profile_preferences_path
end end
step 'I visit profile history page' do step 'I visit profile history page' do
......
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
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
require 'spec_helper'
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) }
it 'creates a flash message' do
choose 'user_theme_id_5'
expect_preferences_saved_message
end
it 'updates their preference' do
choose 'user_theme_id_5'
allowing_for_delay do
visit page.current_path
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_class}")
choose 'user_theme_id_5'
expect(page).not_to have_selector("body.#{default_class}")
expect(page).to have_selector("body.#{theme_5_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
...@@ -36,8 +36,8 @@ describe "Profile access", feature: true do ...@@ -36,8 +36,8 @@ describe "Profile access", feature: true do
it { is_expected.to be_denied_for :visitor } it { is_expected.to be_denied_for :visitor }
end end
describe "GET /profile/design" do describe "GET /profile/preferences" do
subject { design_profile_path } subject { profile_preferences_path }
it { is_expected.to be_allowed_for @u1 } it { is_expected.to be_allowed_for @u1 }
it { is_expected.to be_allowed_for :admin } it { is_expected.to be_allowed_for :admin }
......
require 'spec_helper'
describe PreferencesHelper do
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
end
...@@ -50,12 +50,13 @@ ...@@ -50,12 +50,13 @@
# bitbucket_access_token :string(255) # bitbucket_access_token :string(255)
# bitbucket_access_token_secret :string(255) # bitbucket_access_token_secret :string(255)
# location :string(255) # location :string(255)
# public_email :string(255) default(""), not null
# encrypted_otp_secret :string(255) # encrypted_otp_secret :string(255)
# encrypted_otp_secret_iv :string(255) # encrypted_otp_secret_iv :string(255)
# encrypted_otp_secret_salt :string(255) # encrypted_otp_secret_salt :string(255)
# otp_required_for_login :boolean # otp_required_for_login :boolean
# otp_backup_codes :text # otp_backup_codes :text
# public_email :string(255) default(""), not null # dashboard :integer default(0)
# #
require 'spec_helper' require 'spec_helper'
......
...@@ -102,7 +102,6 @@ end ...@@ -102,7 +102,6 @@ end
# profile_token GET /profile/token(.:format) profile#token # profile_token GET /profile/token(.:format) profile#token
# profile_reset_private_token PUT /profile/reset_private_token(.:format) profile#reset_private_token # profile_reset_private_token PUT /profile/reset_private_token(.:format) profile#reset_private_token
# profile GET /profile(.:format) profile#show # profile GET /profile(.:format) profile#show
# profile_design GET /profile/design(.:format) profile#design
# profile_update PUT /profile/update(.:format) profile#update # profile_update PUT /profile/update(.:format) profile#update
describe ProfilesController, "routing" do describe ProfilesController, "routing" do
it "to #account" do it "to #account" do
...@@ -120,9 +119,19 @@ describe ProfilesController, "routing" do ...@@ -120,9 +119,19 @@ describe ProfilesController, "routing" do
it "to #show" do it "to #show" do
expect(get("/profile")).to route_to('profiles#show') expect(get("/profile")).to route_to('profiles#show')
end end
end
it "to #design" do # profile_preferences GET /profile/preferences(.:format) profiles/preferences#show
expect(get("/profile/design")).to route_to('profiles#design') # 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
end end
...@@ -195,11 +204,9 @@ end ...@@ -195,11 +204,9 @@ end
# dashboard GET /dashboard(.:format) dashboard#show # dashboard GET /dashboard(.:format) dashboard#show
# dashboard_issues GET /dashboard/issues(.:format) dashboard#issues # dashboard_issues GET /dashboard/issues(.:format) dashboard#issues
# dashboard_merge_requests GET /dashboard/merge_requests(.:format) dashboard#merge_requests # dashboard_merge_requests GET /dashboard/merge_requests(.:format) dashboard#merge_requests
# root / dashboard#show
describe DashboardController, "routing" do describe DashboardController, "routing" do
it "to #index" do it "to #index" do
expect(get("/dashboard")).to route_to('dashboard#show') expect(get("/dashboard")).to route_to('dashboard#show')
expect(get("/")).to route_to('dashboard#show')
end end
it "to #issues" do it "to #issues" do
...@@ -211,6 +218,14 @@ describe DashboardController, "routing" do ...@@ -211,6 +218,14 @@ describe DashboardController, "routing" do
end end
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 # new_user_session GET /users/sign_in(.:format) devise/sessions#new
# user_session POST /users/sign_in(.:format) devise/sessions#create # user_session POST /users/sign_in(.:format) devise/sessions#create
# destroy_user_session DELETE /users/sign_out(.:format) devise/sessions#destroy # destroy_user_session DELETE /users/sign_out(.:format) devise/sessions#destroy
......
...@@ -19,3 +19,36 @@ unless ENV['CI'] || ENV['CI_SERVER'] ...@@ -19,3 +19,36 @@ unless ENV['CI'] || ENV['CI_SERVER']
# Keep only the screenshots generated from the last failing test suite # Keep only the screenshots generated from the last failing test suite
Capybara::Screenshot.prune_strategy = :keep_last_run Capybara::Screenshot.prune_strategy = :keep_last_run
end 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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment