Commit 73007291 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'rs-dev-issue-2228' into 'master'

Allow user to customize default Dashboard page

Renames the "Design" profile page to "Preferences" and adds a field to customize the default Dashboard page:

> ![Screen_Shot_2015-06-11_at_11.12.53_PM](https://gitlab.com/gitlab-org/gitlab-ce/uploads/b5282a3be7861d1148528c6bc9e7a0e0/Screen_Shot_2015-06-11_at_11.12.53_PM.png)

See merge request !778
parents 168d5eab 9eec51d9
......@@ -2,6 +2,8 @@ Please view this file on the master branch, on stable branches it's out of date.
v 7.13.0 (unreleased)
- Remove project visibility icons from dashboard projects list
- Rename "Design" profile settings page to "Preferences".
- Allow users to customize their default Dashboard page.
v 7.12.0 (unreleased)
- Fix post-receive errors on a push when an external issue tracker is configured (Stan Hu)
......
......@@ -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'
......
class @Profile
constructor: ->
$('.edit_user .application-theme input, .edit_user .code-preview-theme input').click ->
# Submit the form
$('.edit_user').submit()
new Flash("Appearance settings saved", "notice")
# Automatically submit the Preferences form when any of its radio buttons change
$('.js-preferences-form').on 'change.preference', 'input[type=radio]', ->
$(this).parents('form').submit()
$('.update-username form').on 'ajax:before', ->
$('.loading-gif').show()
......@@ -18,7 +16,6 @@ class @Profile
$('.update-notifications').on 'ajax:complete', ->
$(this).find('.btn-save').enable()
$('.js-choose-user-avatar-button').bind "click", ->
form = $(this).closest("form")
form.find(".js-user-avatar-input").click()
......
......@@ -35,26 +35,26 @@
*/
@import "font-awesome";
/**
* UI themes:
*/
@import "themes/**/*";
/**
* Generic css (forms, nav etc):
*/
@import "generic/*";
@import "generic/**/*";
/**
* Page specific styles (issues, projects etc):
*/
@import "pages/*";
@import "pages/**/*";
/**
* Code highlight
*/
@import "highlight/*";
/**
* UI themes:
*/
@import "themes/*";
@import "highlight/**/*";
/**
* Styles for JS behaviors.
......
......@@ -17,67 +17,6 @@
}
}
/*
* Appearance settings
*
*/
.themes_opts {
label {
margin-right: 20px;
text-align: center;
.prev {
height: 80px;
width: 160px;
margin-bottom: 10px;
@include border-radius(4px);
&.classic {
background: #31363e;
}
&.default {
background: #888888;
}
&.modern {
background: #009871;
}
&.gray {
background: #373737;
}
&.violet {
background: #548;
}
&.blue {
background: #2980b9;
}
}
}
}
.code_highlight_opts {
margin-top: 10px;
label {
margin-right: 20px;
text-align: center;
.prev {
width: 160px;
margin-bottom: 10px;
img {
max-width: 100%;
@include border-radius(4px);
}
}
}
}
.oauth-buttons {
.btn-group {
margin-right: 10px;
......
.application-theme {
label {
margin-right: 20px;
text-align: center;
.preview {
@include border-radius(4px);
height: 80px;
margin-bottom: 10px;
width: 160px;
&.ui_blue {
background: $theme-blue;
}
&.ui_charcoal {
background: $theme-charcoal;
}
&.ui_graphite {
background: $theme-graphite;
}
&.ui_gray {
background: $theme-gray;
}
&.ui_green {
background: $theme-green;
}
&.ui_violet {
background: $theme-violet;
}
}
}
}
.syntax-theme {
label {
margin-right: 20px;
text-align: center;
.preview {
margin-bottom: 10px;
width: 160px;
img {
@include border-radius(4px);
max-width: 100%;
}
}
}
}
/**
* Styles the GitLab application with a specific color theme
*
* $color-light -
* $color -
* $color-darker -
* $color-dark -
*/
@mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) {
header {
&.navbar-gitlab {
......@@ -77,3 +85,36 @@
}
}
}
$theme-blue: #2980B9;
$theme-charcoal: #474D57;
$theme-graphite: #888888;
$theme-gray: #373737;
$theme-green: #019875;
$theme-violet: #554488;
body {
&.ui_blue {
@include gitlab-theme(#BECDE9, $theme-blue, #1970A9, #096099);
}
&.ui_charcoal {
@include gitlab-theme(#979DA7, $theme-charcoal, #373D47, #24272D);
}
&.ui_graphite {
@include gitlab-theme(#CCCCCC, $theme-graphite, #777777, #666666);
}
&.ui_gray {
@include gitlab-theme(#979797, $theme-gray, #272727, #222222);
}
&.ui_green {
@include gitlab-theme(#AADDCC, $theme-green, #018865, #017855);
}
&.ui_violet {
@include gitlab-theme(#9988CC, $theme-violet, #443366, #332255);
}
}
/**
* This file represent some UI that can be changed
* during web app restyle or theme select.
*
*/
.ui_basic {
@include gitlab-theme(#CCCCCC, #888888, #777777, #666666);
}
/**
* Blue GitLab UI theme
*/
.ui_blue {
@include gitlab-theme(#BECDE9, #2980b9, #1970a9, #096099);
}
/**
* Violet GitLab UI theme
*/
.ui_color {
@include gitlab-theme(#98C, #548, #436, #325);
}
/**
* Gray GitLab UI theme
*/
.ui_gray {
@include gitlab-theme(#979797, #373737, #272727, #222222);
}
/**
* Classic GitLab UI theme
*/
.ui_mars {
@include gitlab-theme(#979DA7, #474D57, #373D47, #24272D);
}
/**
* Modern GitLab UI theme
*/
.ui_modern {
@include gitlab-theme(#ADC, #019875, #018865, #017855);
}
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
def show
end
def design
end
def applications
@applications = current_user.oauth_applications
@authorized_tokens = current_user.oauth_authorized_tokens
......@@ -29,7 +26,6 @@ class ProfilesController < Profiles::ApplicationController
respond_to do |format|
format.html { redirect_to :back }
format.js
end
end
......@@ -65,10 +61,21 @@ class ProfilesController < Profiles::ApplicationController
def user_params
params.require(:user).permit(
:email, :password, :password_confirmation, :bio, :name,
:username, :skype, :linkedin, :twitter, :website_url,
:color_scheme_id, :theme_id, :avatar, :hide_no_ssh_key,
:hide_no_password, :location, :public_email
:avatar,
:bio,
:email,
:hide_no_password,
:hide_no_ssh_key,
:linkedin,
:location,
:name,
:password,
:password_confirmation,
:public_email,
:skype,
:twitter,
:username,
:website_url
)
end
end
# 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
......@@ -2,26 +2,6 @@ require 'digest/md5'
require 'uri'
module ApplicationHelper
COLOR_SCHEMES = {
1 => 'white',
2 => 'dark',
3 => 'solarized-light',
4 => 'solarized-dark',
5 => 'monokai',
}
COLOR_SCHEMES.default = 'white'
# Helper method to access the COLOR_SCHEMES
#
# The keys are the `color_scheme_ids`
# The values are the `name` of the scheme.
#
# The preview images are `name-scheme-preview.png`
# The stylesheets should use the css class `.name`
def color_schemes
COLOR_SCHEMES.freeze
end
# Check if a particular controller is the current one
#
# args - One or more controller names to check
......@@ -138,18 +118,6 @@ module ApplicationHelper
Emoji.names.to_s
end
def app_theme
Gitlab::Theme.css_class_by_id(current_user.try(:theme_id))
end
def theme_type
Gitlab::Theme.type_css_class_by_id(current_user.try(:theme_id))
end
def user_color_scheme_class
COLOR_SCHEMES[current_user.try(:color_scheme_id)] if defined?(current_user)
end
# Define whenever show last push event
# with suggestion to create MR
def show_last_push_widget?(event)
......
......@@ -2,6 +2,7 @@ require 'nokogiri'
module GitlabMarkdownHelper
include Gitlab::Markdown
include PreferencesHelper
# Use this in places where you would normally use link_to(gfm(...), ...).
#
......
# Helper methods for per-User preferences
module PreferencesHelper
COLOR_SCHEMES = {
1 => 'white',
2 => 'dark',
3 => 'solarized-light',
4 => 'solarized-dark',
5 => 'monokai',
}
COLOR_SCHEMES.default = 'white'
# Helper method to access the COLOR_SCHEMES
#
# The keys are the `color_scheme_ids`
# The values are the `name` of the scheme.
#
# The preview images are `name-scheme-preview.png`
# The stylesheets should use the css class `.name`
def color_schemes
COLOR_SCHEMES.freeze
end
# Maps `dashboard` values to more user-friendly option text
DASHBOARD_CHOICES = {
projects: 'Your Projects (default)',
stars: 'Starred Projects'
}.with_indifferent_access.freeze
# Returns an Array usable by a select field for more user-friendly option text
def dashboard_choices
defined = User.dashboards
if defined.size != DASHBOARD_CHOICES.size
# Ensure that anyone adding new options updates this method too
raise RuntimeError, "`User` defines #{defined.size} dashboard choices," +
" but `DASHBOARD_CHOICES` defined #{DASHBOARD_CHOICES.size}."
else
defined.map do |key, _|
# Use `fetch` so `KeyError` gets raised when a key is missing
[DASHBOARD_CHOICES.fetch(key), key]
end
end
end
def user_application_theme
theme = Gitlab::Themes.by_id(current_user.try(:theme_id))
theme.css_class
end
def user_color_scheme_class
COLOR_SCHEMES[current_user.try(:color_scheme_id)] if defined?(current_user)
end
end
......@@ -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
!!! 5
%html{ lang: "en"}
= render "layouts/head"
%body{class: "#{app_theme}", :'data-page' => body_data_page}
/ Ideally this would be inside the head, but turbolinks only evaluates page-specific JS in the body.
%body{class: "#{user_application_theme}", 'data-page' => body_data_page}
-# Ideally this would be inside the head, but turbolinks only evaluates page-specific JS in the body.
= yield :scripts_body_top
- if current_user
= render "layouts/header/default", title: header_title
- else
......
!!! 5
%html{ lang: "en"}
= render "layouts/head"
%body.ui_mars.login-page.application
%body.ui_charcoal.login-page.application
= render "layouts/header/empty"
= render "layouts/broadcast"
.container.navless-container
......
!!! 5
%html{ lang: "en"}
= render "layouts/head"
%body{class: "#{app_theme} application"}
%body{class: "#{user_application_theme} application"}
= render "layouts/header/empty"
.container.navless-container
= render "layouts/flash"
......
%ul.nav.nav-sidebar
= nav_link(path: 'dashboard#show', html_options: {class: 'home'}) do
= link_to root_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
= nav_link(path: ['dashboard#show', 'root#show'], html_options: {class: 'home'}) do
= link_to dashboard_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do
= icon('dashboard fw')
%span
Your Projects
......
......@@ -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')
......
- 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
- page_title 'Preferences'
%h3.page-title
= page_title
%p.light
These settings allow you to customize the appearance and behavior of the site.
They are saved with your account and will persist to any device you use to
access the site.
%hr
= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: {class: 'js-preferences-form form-horizontal'} do |f|
.panel.panel-default.application-theme
.panel-heading
Application theme
.panel-body
- Gitlab::Themes.each do |theme|
= label_tag do
.preview{class: theme.css_class}
= f.radio_button :theme_id, theme.id
= theme.name
.panel.panel-default.syntax-theme
.panel-heading
Syntax highlighting theme
.panel-body
- color_schemes.each do |color_scheme_id, color_scheme|
= label_tag do
.preview= image_tag "#{color_scheme}-scheme-preview.png"
= f.radio_button :color_scheme_id, color_scheme_id
= color_scheme.tr('-_', ' ').titleize
.panel.panel-default
.panel-heading
Behavior
.panel-body
.form-group
= f.label :dashboard, class: 'control-label' do
Default Dashboard
= link_to('(?)', help_page_path('profile', 'preferences') + '#default-dashboard', target: '_blank')
.col-sm-10
= f.select :dashboard, dashboard_choices, {}, class: 'form-control'
.panel-footer
= f.submit 'Save', class: 'btn btn-save'
// Remove body class for any previous theme, re-add current one
$('body').removeClass('<%= Gitlab::Themes.body_classes %>')
$('body').addClass('<%= user_application_theme %>')
// Re-enable the "Save" button
$('input[type=submit]').enable()
// Show the notice flash message
new Flash('<%= flash.discard(:notice) %>', 'notice')
// Remove body class for any previous theme, re-add current one
$('body').removeClass('<%= Gitlab::Theme.body_classes %>')
$('body').addClass('<%= app_theme %> <%= theme_type %>')
......@@ -62,12 +62,13 @@ production: &base
# default_can_create_group: false # default: true
# username_changing_enabled: false # default: true - User can change her username/namespace
## Default theme
## BASIC = 1
## MARS = 2
## MODERN = 3
## GRAY = 4
## COLOR = 5
## Default theme ID
## 1 - Graphite
## 2 - Charcoal
## 3 - Green
## 4 - Gray
## 5 - Violet
## 6 - Blue
# default_theme: 2 # default: 2
## Automatic issue closing
......
......@@ -103,7 +103,7 @@ Settings['gitlab'] ||= Settingslogic.new({})
Settings.gitlab['default_projects_limit'] ||= 10
Settings.gitlab['default_branch_protection'] ||= 2
Settings.gitlab['default_can_create_group'] = true if Settings.gitlab['default_can_create_group'].nil?
Settings.gitlab['default_theme'] = Gitlab::Theme::MARS if Settings.gitlab['default_theme'].nil?
Settings.gitlab['default_theme'] = Gitlab::Themes::APPLICATION_DEFAULT if Settings.gitlab['default_theme'].nil?
Settings.gitlab['host'] ||= 'localhost'
Settings.gitlab['ssh_host'] ||= Settings.gitlab.host
Settings.gitlab['https'] = false if Settings.gitlab['https'].nil?
......
......@@ -203,7 +203,6 @@ Gitlab::Application.routes.draw do
resource :profile, only: [:show, :update] do
member do
get :history
get :design
get :applications
put :reset_private_token
......@@ -222,6 +221,7 @@ Gitlab::Application.routes.draw do
put :reset
end
end
resource :preferences, only: [:show, :update]
resources :keys
resources :emails, only: [:index, :create, :destroy]
resource :avatar, only: [:destroy]
......@@ -293,7 +293,7 @@ Gitlab::Application.routes.draw do
get '/users/auth/:provider/omniauth_error' => 'omniauth_callbacks#omniauth_error', as: :omniauth_error
end
root to: "dashboard#show"
root to: "root#show"
#
# Project Area
......
......@@ -12,7 +12,7 @@ admin = User.create(
username: 'root',
password: password,
password_expires_at: expire_time,
theme_id: Gitlab::Theme::MARS
theme_id: Gitlab::Themes::APPLICATION_DEFAULT
)
......
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 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20150609141121) do
ActiveRecord::Schema.define(version: 20150610065936) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -502,6 +502,7 @@ ActiveRecord::Schema.define(version: 20150609141121) do
t.boolean "otp_required_for_login"
t.text "otp_backup_codes"
t.string "public_email", default: "", null: false
t.integer "dashboard", default: 0
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
......
# Documentation
## User documentation
- [API](api/README.md) Automate GitLab via a simple and powerful API.
- [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab.
- [Importing to GitLab](workflow/importing/README.md).
- [Markdown](markdown/markdown.md) GitLab's advanced formatting system.
- [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do.
- [Project Services](project_services/project_services.md) Integrate a project with external services, such as CI and chat.
- [Public access](public_access/public_access.md) Learn how you can allow public and internal access to projects.
- [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects.
- [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project.
- [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN.
## Administrator documentation
- [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when web hooks aren't enough.
- [Install](install/README.md) Requirements, directory structures and installation from source.
- [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter.
- [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages.
- [Libravatar](customization/libravatar.md) Use Libravatar for user avatars.
- [Log system](logs/logs.md) Log system.
- [Operations](operations/README.md) Keeping GitLab up and running
- [Raketasks](raketasks/README.md) Backups, maintenance, automatic web hook setup and the importing of projects.
- [Security](security/README.md) Learn what you can do to further secure your GitLab instance.
- [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed.
- [Update](update/README.md) Update guides to upgrade your installation.
- [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page.
## Contributor documentation
- [Development](development/README.md) Explains the architecture and the guidelines for shell commands.
- [Legal](legal/README.md) Contributor license agreements.
- [Release](release/README.md) How to make the monthly and security releases.
\ No newline at end of file
# Documentation
## User documentation
- [API](api/README.md) Automate GitLab via a simple and powerful API.
- [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab.
- [Importing to GitLab](workflow/importing/README.md).
- [Markdown](markdown/markdown.md) GitLab's advanced formatting system.
- [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do.
- [Profile Settings](profile/README.md)
- [Project Services](project_services/project_services.md) Integrate a project with external services, such as CI and chat.
- [Public access](public_access/public_access.md) Learn how you can allow public and internal access to projects.
- [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects.
- [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project.
- [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN.
## Administrator documentation
- [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when web hooks aren't enough.
- [Install](install/README.md) Requirements, directory structures and installation from source.
- [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter.
- [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages.
- [Libravatar](customization/libravatar.md) Use Libravatar for user avatars.
- [Log system](logs/logs.md) Log system.
- [Operations](operations/README.md) Keeping GitLab up and running
- [Raketasks](raketasks/README.md) Backups, maintenance, automatic web hook setup and the importing of projects.
- [Security](security/README.md) Learn what you can do to further secure your GitLab instance.
- [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed.
- [Update](update/README.md) Update guides to upgrade your installation.
- [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page.
## Contributor documentation
- [Development](development/README.md) Explains the architecture and the guidelines for shell commands.
- [Legal](legal/README.md) Contributor license agreements.
- [Release](release/README.md) How to make the monthly and security releases.
# 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
- [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)
......@@ -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
......
......@@ -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
......@@ -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
......
......@@ -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
......
......@@ -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
......
module Gitlab
class Theme
BASIC = 1 unless const_defined?(:BASIC)
MARS = 2 unless const_defined?(:MARS)
MODERN = 3 unless const_defined?(:MODERN)
GRAY = 4 unless const_defined?(:GRAY)
COLOR = 5 unless const_defined?(:COLOR)
BLUE = 6 unless const_defined?(:BLUE)
def self.classes
@classes ||= {
BASIC => 'ui_basic',
MARS => 'ui_mars',
MODERN => 'ui_modern',
GRAY => 'ui_gray',
COLOR => 'ui_color',
BLUE => 'ui_blue'
}
end
def self.css_class_by_id(id)
id ||= Gitlab.config.gitlab.default_theme
classes[id]
end
def self.types
@types ||= {
BASIC => 'light_theme',
MARS => 'dark_theme',
MODERN => 'dark_theme',
GRAY => 'dark_theme',
COLOR => 'dark_theme',
BLUE => 'light_theme'
}
end
def self.type_css_class_by_id(id)
id ||= Gitlab.config.gitlab.default_theme
types[id]
end
# Convenience method to get a space-separated String of all the theme
# classes that might be applied to the `body` element
#
# Returns a String
def self.body_classes
(classes.values + types.values).uniq.join(' ')
end
end
end
module Gitlab
# Module containing GitLab's application theme definitions and helper methods
# for accessing them.
module Themes
# Theme ID used when no `default_theme` configuration setting is provided.
APPLICATION_DEFAULT = 2
# Struct class representing a single Theme
Theme = Struct.new(:id, :name, :css_class)
# All available Themes
THEMES = [
Theme.new(1, 'Graphite', 'ui_graphite'),
Theme.new(2, 'Charcoal', 'ui_charcoal'),
Theme.new(3, 'Green', 'ui_green'),
Theme.new(4, 'Gray', 'ui_gray'),
Theme.new(5, 'Violet', 'ui_violet'),
Theme.new(6, 'Blue', 'ui_blue')
].freeze
# Convenience method to get a space-separated String of all the theme
# classes that might be applied to the `body` element
#
# Returns a String
def self.body_classes
THEMES.collect(&:css_class).uniq.join(' ')
end
# Get a Theme by its ID
#
# If the ID is invalid, returns the default Theme.
#
# id - Integer ID
#
# Returns a Theme
def self.by_id(id)
THEMES.detect { |t| t.id == id } || default
end
# Get the default Theme
#
# Returns a Theme
def self.default
by_id(default_id)
end
# Iterate through each Theme
#
# Yields the Theme object
def self.each(&block)
THEMES.each(&block)
end
private
def self.default_id
id = Gitlab.config.gitlab.default_theme.to_i
# Prevent an invalid configuration setting from causing an infinite loop
if id < THEMES.first.id || id > THEMES.last.id
APPLICATION_DEFAULT
else
id
end
end
end
end
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
let(:user) { create(:user) }
before do
login_as(user)
visit profile_preferences_path
end
describe 'User changes their application theme', js: true do
let(:default) { Gitlab::Themes.default }
let(:theme) { Gitlab::Themes.by_id(5) }
it 'creates a flash message' do
choose "user_theme_id_#{theme.id}"
expect_preferences_saved_message
end
it 'updates their preference' do
choose "user_theme_id_#{theme.id}"
allowing_for_delay do
visit page.current_path
expect(page).to have_checked_field("user_theme_id_#{theme.id}")
end
end
it 'reflects the changes immediately' do
expect(page).to have_selector("body.#{default.css_class}")
choose "user_theme_id_#{theme.id}"
expect(page).not_to have_selector("body.#{default.css_class}")
expect(page).to have_selector("body.#{theme.css_class}")
end
end
describe 'User changes their syntax highlighting theme', js: true do
it 'creates a flash message' do
choose 'user_color_scheme_id_5'
expect_preferences_saved_message
end
it 'updates their preference' do
choose 'user_color_scheme_id_5'
allowing_for_delay do
visit page.current_path
expect(page).to have_checked_field('user_color_scheme_id_5')
end
end
end
describe 'User changes their default dashboard' do
it 'creates a flash message' do
select 'Starred Projects', from: 'user_dashboard'
click_button 'Save'
expect_preferences_saved_message
end
it 'updates their preference' do
select 'Starred Projects', from: 'user_dashboard'
click_button 'Save'
click_link 'Dashboard'
expect(page.current_path).to eq starred_dashboard_projects_path
click_link 'Your Projects'
expect(page.current_path).to eq dashboard_path
end
end
def expect_preferences_saved_message
within('.flash-container') do
expect(page).to have_content('Preferences saved.')
end
end
end
......@@ -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 }
......
......@@ -185,27 +185,6 @@ describe ApplicationHelper do
end
end
describe 'user_color_scheme_class' do
context 'with current_user is nil' do
it 'should return a string' do
allow(self).to receive(:current_user).and_return(nil)
expect(user_color_scheme_class).to be_kind_of(String)
end
end
context 'with a current_user' do
(1..5).each do |color_scheme_id|
context "with color_scheme_id == #{color_scheme_id}" do
it 'should return a string' do
current_user = double(:color_scheme_id => color_scheme_id)
allow(self).to receive(:current_user).and_return(current_user)
expect(user_color_scheme_class).to be_kind_of(String)
end
end
end
end
end
describe 'simple_sanitize' do
let(:a_tag) { '<a href="#">Foo</a>' }
......
require 'spec_helper'
describe PreferencesHelper do
describe 'user_application_theme' do
context 'with a user' do
it "returns user's theme's css_class" do
user = double('user', theme_id: 3)
allow(self).to receive(:current_user).and_return(user)
expect(user_application_theme).to eq 'ui_green'
end
it 'returns the default when id is invalid' do
user = double('user', theme_id: Gitlab::Themes::THEMES.size + 5)
allow(Gitlab.config.gitlab).to receive(:default_theme).and_return(2)
allow(self).to receive(:current_user).and_return(user)
expect(user_application_theme).to eq 'ui_charcoal'
end
end
context 'without a user' do
before do
allow(self).to receive(:current_user).and_return(nil)
end
it 'returns the default theme' do
expect(user_application_theme).to eq Gitlab::Themes.default.css_class
end
end
end
describe 'dashboard_choices' do
it 'raises an exception when defined choices may be missing' do
expect(User).to receive(:dashboards).and_return(foo: 'foo')
expect { dashboard_choices }.to raise_error(RuntimeError)
end
it 'raises an exception when defined choices may be using the wrong key' do
expect(User).to receive(:dashboards).and_return(foo: 'foo', bar: 'bar')
expect { dashboard_choices }.to raise_error(KeyError)
end
it 'provides better option descriptions' do
expect(dashboard_choices).to match_array [
['Your Projects (default)', 'projects'],
['Starred Projects', 'stars']
]
end
end
describe 'user_color_scheme_class' do
context 'with current_user is nil' do
it 'should return a string' do
allow(self).to receive(:current_user).and_return(nil)
expect(user_color_scheme_class).to be_kind_of(String)
end
end
context 'with a current_user' do
(1..5).each do |color_scheme_id|
context "with color_scheme_id == #{color_scheme_id}" do
it 'should return a string' do
current_user = double(:color_scheme_id => color_scheme_id)
allow(self).to receive(:current_user).and_return(current_user)
expect(user_color_scheme_class).to be_kind_of(String)
end
end
end
end
end
end
require 'spec_helper'
describe Gitlab::Themes do
describe '.body_classes' do
it 'returns a space-separated list of class names' do
css = described_class.body_classes
expect(css).to include('ui_graphite')
expect(css).to include(' ui_charcoal ')
expect(css).to include(' ui_blue')
end
end
describe '.by_id' do
it 'returns a Theme by its ID' do
expect(described_class.by_id(1).name).to eq 'Graphite'
expect(described_class.by_id(6).name).to eq 'Blue'
end
end
describe '.default' do
it 'returns the default application theme' do
allow(described_class).to receive(:default_id).and_return(2)
expect(described_class.default.id).to eq 2
end
it 'prevents an infinite loop when configuration default is invalid' do
default = described_class::APPLICATION_DEFAULT
themes = described_class::THEMES
config = double(default_theme: 0).as_null_object
allow(Gitlab).to receive(:config).and_return(config)
expect(described_class.default.id).to eq default
config = double(default_theme: themes.size + 5).as_null_object
allow(Gitlab).to receive(:config).and_return(config)
expect(described_class.default.id).to eq default
end
end
describe '.each' do
it 'passes the block to the THEMES Array' do
ids = []
described_class.each { |theme| ids << theme.id }
expect(ids).not_to be_empty
# TODO (rspeicher): RSpec 3.x
# expect(described_class.each).to yield_with_arg(described_class::Theme)
end
end
end
......@@ -50,12 +50,13 @@
# bitbucket_access_token :string(255)
# bitbucket_access_token_secret :string(255)
# location :string(255)
# public_email :string(255) default(""), not null
# encrypted_otp_secret :string(255)
# encrypted_otp_secret_iv :string(255)
# encrypted_otp_secret_salt :string(255)
# otp_required_for_login :boolean
# otp_backup_codes :text
# public_email :string(255) default(""), not null
# dashboard :integer default(0)
#
require 'spec_helper'
......@@ -329,12 +330,12 @@ describe User do
end
describe 'with default overrides' do
let(:user) { User.new(projects_limit: 123, can_create_group: false, can_create_team: true, theme_id: Gitlab::Theme::BASIC) }
let(:user) { User.new(projects_limit: 123, can_create_group: false, can_create_team: true, theme_id: 1) }
it "should apply defaults to user" do
expect(user.projects_limit).to eq(123)
expect(user.can_create_group).to be_falsey
expect(user.theme_id).to eq(Gitlab::Theme::BASIC)
expect(user.theme_id).to eq(1)
end
end
end
......
......@@ -102,7 +102,6 @@ end
# profile_token GET /profile/token(.:format) profile#token
# profile_reset_private_token PUT /profile/reset_private_token(.:format) profile#reset_private_token
# profile GET /profile(.:format) profile#show
# profile_design GET /profile/design(.:format) profile#design
# profile_update PUT /profile/update(.:format) profile#update
describe ProfilesController, "routing" do
it "to #account" do
......@@ -120,9 +119,19 @@ describe ProfilesController, "routing" do
it "to #show" do
expect(get("/profile")).to route_to('profiles#show')
end
end
it "to #design" do
expect(get("/profile/design")).to route_to('profiles#design')
# profile_preferences GET /profile/preferences(.:format) profiles/preferences#show
# PATCH /profile/preferences(.:format) profiles/preferences#update
# PUT /profile/preferences(.:format) profiles/preferences#update
describe Profiles::PreferencesController, 'routing' do
it 'to #show' do
expect(get('/profile/preferences')).to route_to('profiles/preferences#show')
end
it 'to #update' do
expect(put('/profile/preferences')).to route_to('profiles/preferences#update')
expect(patch('/profile/preferences')).to route_to('profiles/preferences#update')
end
end
......@@ -195,11 +204,9 @@ end
# dashboard GET /dashboard(.:format) dashboard#show
# dashboard_issues GET /dashboard/issues(.:format) dashboard#issues
# dashboard_merge_requests GET /dashboard/merge_requests(.:format) dashboard#merge_requests
# root / dashboard#show
describe DashboardController, "routing" do
it "to #index" do
expect(get("/dashboard")).to route_to('dashboard#show')
expect(get("/")).to route_to('dashboard#show')
end
it "to #issues" do
......@@ -211,6 +218,14 @@ describe DashboardController, "routing" do
end
end
# root / root#show
describe RootController, 'routing' do
it 'to #show' do
expect(get('/')).to route_to('root#show')
end
end
# new_user_session GET /users/sign_in(.:format) devise/sessions#new
# user_session POST /users/sign_in(.:format) devise/sessions#create
# destroy_user_session DELETE /users/sign_out(.:format) devise/sessions#destroy
......
......@@ -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
module LoginHelpers
# Internal: Create and log in as a user of the specified role
# Internal: Log in as a specific user or a new user of a specific role
#
# role - User role (e.g., :admin, :user)
def login_as(role)
@user = create(role)
# user_or_role - User object, or a role to create (e.g., :admin, :user)
#
# Examples:
#
# # Create a user automatically
# login_as(:user)
#
# # Create an admin automatically
# login_as(:admin)
#
# # Provide an existing User record
# user = create(:user)
# login_as(user)
def login_as(user_or_role)
if user_or_role.kind_of?(User)
@user = user_or_role
else
@user = create(user_or_role)
end
login_with(@user)
end
......
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