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. ...@@ -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) v 7.13.0 (unreleased)
- Remove project visibility icons from dashboard projects list - 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) v 7.12.0 (unreleased)
- Fix post-receive errors on a push when an external issue tracker is configured (Stan Hu) - Fix post-receive errors on a push when an external issue tracker is configured (Stan Hu)
......
...@@ -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()
......
...@@ -35,26 +35,26 @@ ...@@ -35,26 +35,26 @@
*/ */
@import "font-awesome"; @import "font-awesome";
/**
* UI themes:
*/
@import "themes/**/*";
/** /**
* Generic css (forms, nav etc): * Generic css (forms, nav etc):
*/ */
@import "generic/*"; @import "generic/**/*";
/** /**
* Page specific styles (issues, projects etc): * Page specific styles (issues, projects etc):
*/ */
@import "pages/*"; @import "pages/**/*";
/** /**
* Code highlight * Code highlight
*/ */
@import "highlight/*"; @import "highlight/**/*";
/**
* UI themes:
*/
@import "themes/*";
/** /**
* Styles for JS behaviors. * Styles for JS behaviors.
......
...@@ -17,67 +17,6 @@ ...@@ -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 { .oauth-buttons {
.btn-group { .btn-group {
margin-right: 10px; 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) { @mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) {
header { header {
&.navbar-gitlab { &.navbar-gitlab {
...@@ -77,3 +85,36 @@ ...@@ -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 ...@@ -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
...@@ -2,26 +2,6 @@ require 'digest/md5' ...@@ -2,26 +2,6 @@ require 'digest/md5'
require 'uri' require 'uri'
module ApplicationHelper 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 # Check if a particular controller is the current one
# #
# args - One or more controller names to check # args - One or more controller names to check
...@@ -138,18 +118,6 @@ module ApplicationHelper ...@@ -138,18 +118,6 @@ module ApplicationHelper
Emoji.names.to_s Emoji.names.to_s
end 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 # Define whenever show last push event
# with suggestion to create MR # with suggestion to create MR
def show_last_push_widget?(event) def show_last_push_widget?(event)
......
...@@ -2,6 +2,7 @@ require 'nokogiri' ...@@ -2,6 +2,7 @@ require 'nokogiri'
module GitlabMarkdownHelper module GitlabMarkdownHelper
include Gitlab::Markdown include Gitlab::Markdown
include PreferencesHelper
# Use this in places where you would normally use link_to(gfm(...), ...). # 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 @@ ...@@ -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
!!! 5 !!! 5
%html{ lang: "en"} %html{ lang: "en"}
= render "layouts/head" = render "layouts/head"
%body{class: "#{app_theme}", :'data-page' => body_data_page} %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. -# Ideally this would be inside the head, but turbolinks only evaluates page-specific JS in the body.
= yield :scripts_body_top = yield :scripts_body_top
- if current_user - if current_user
= render "layouts/header/default", title: header_title = render "layouts/header/default", title: header_title
- else - else
......
!!! 5 !!! 5
%html{ lang: "en"} %html{ lang: "en"}
= render "layouts/head" = render "layouts/head"
%body.ui_mars.login-page.application %body.ui_charcoal.login-page.application
= render "layouts/header/empty" = render "layouts/header/empty"
= render "layouts/broadcast" = render "layouts/broadcast"
.container.navless-container .container.navless-container
......
!!! 5 !!! 5
%html{ lang: "en"} %html{ lang: "en"}
= render "layouts/head" = render "layouts/head"
%body{class: "#{app_theme} application"} %body{class: "#{user_application_theme} application"}
= render "layouts/header/empty" = render "layouts/header/empty"
.container.navless-container .container.navless-container
= render "layouts/flash" = render "layouts/flash"
......
%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"
%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 ...@@ -62,12 +62,13 @@ production: &base
# default_can_create_group: false # default: true # default_can_create_group: false # default: true
# username_changing_enabled: false # default: true - User can change her username/namespace # username_changing_enabled: false # default: true - User can change her username/namespace
## Default theme ## Default theme ID
## BASIC = 1 ## 1 - Graphite
## MARS = 2 ## 2 - Charcoal
## MODERN = 3 ## 3 - Green
## GRAY = 4 ## 4 - Gray
## COLOR = 5 ## 5 - Violet
## 6 - Blue
# default_theme: 2 # default: 2 # default_theme: 2 # default: 2
## Automatic issue closing ## Automatic issue closing
......
...@@ -103,7 +103,7 @@ Settings['gitlab'] ||= Settingslogic.new({}) ...@@ -103,7 +103,7 @@ Settings['gitlab'] ||= Settingslogic.new({})
Settings.gitlab['default_projects_limit'] ||= 10 Settings.gitlab['default_projects_limit'] ||= 10
Settings.gitlab['default_branch_protection'] ||= 2 Settings.gitlab['default_branch_protection'] ||= 2
Settings.gitlab['default_can_create_group'] = true if Settings.gitlab['default_can_create_group'].nil? 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['host'] ||= 'localhost'
Settings.gitlab['ssh_host'] ||= Settings.gitlab.host Settings.gitlab['ssh_host'] ||= Settings.gitlab.host
Settings.gitlab['https'] = false if Settings.gitlab['https'].nil? Settings.gitlab['https'] = false if Settings.gitlab['https'].nil?
......
...@@ -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
......
...@@ -12,7 +12,7 @@ admin = User.create( ...@@ -12,7 +12,7 @@ admin = User.create(
username: 'root', username: 'root',
password: password, password: password,
password_expires_at: expire_time, 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 @@ ...@@ -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: 20150609141121) 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"
...@@ -502,6 +502,7 @@ ActiveRecord::Schema.define(version: 20150609141121) do ...@@ -502,6 +502,7 @@ ActiveRecord::Schema.define(version: 20150609141121) do
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.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
......
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 ...@@ -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 }
......
...@@ -185,27 +185,6 @@ describe ApplicationHelper do ...@@ -185,27 +185,6 @@ describe ApplicationHelper do
end end
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 describe 'simple_sanitize' do
let(:a_tag) { '<a href="#">Foo</a>' } 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 @@ ...@@ -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'
...@@ -329,12 +330,12 @@ describe User do ...@@ -329,12 +330,12 @@ describe User do
end end
describe 'with default overrides' do 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 it "should apply defaults to user" do
expect(user.projects_limit).to eq(123) expect(user.projects_limit).to eq(123)
expect(user.can_create_group).to be_falsey 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 end
end end
......
...@@ -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
module LoginHelpers 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) # user_or_role - User object, or a role to create (e.g., :admin, :user)
def login_as(role) #
@user = create(role) # 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) login_with(@user)
end 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