From 9b177bb7c94df6c7d3868235f75939a41acf8718 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Mon, 11 Sep 2017 15:44:42 +0000 Subject: Revert "Merge branch 'revert-f2421b2b' into 'master'" This reverts merge request !14148 --- app/assets/stylesheets/framework.scss | 1 + app/assets/stylesheets/framework/gitlab-theme.scss | 265 +++++++++++++++++++++ app/assets/stylesheets/framework/header.scss | 1 - app/assets/stylesheets/framework/variables.scss | 39 +++ app/assets/stylesheets/new_nav.scss | 119 +++------ app/assets/stylesheets/new_sidebar.scss | 7 - .../stylesheets/pages/profiles/preferences.scss | 64 +++++ app/controllers/admin/users_controller.rb | 1 + app/controllers/profiles/preferences_controller.rb | 3 +- app/helpers/preferences_helper.rb | 4 + app/models/user.rb | 1 + app/views/layouts/application.html.haml | 2 +- app/views/profiles/preferences/show.html.haml | 20 ++ app/views/profiles/preferences/update.js.erb | 4 + ...d-option-to-change-navigation-color-palette.yml | 5 + config/gitlab.yml.example | 7 + config/initializers/1_settings.rb | 1 + db/migrate/20170816234252_add_theme_id_to_users.rb | 10 + db/schema.rb | 1 + doc/api/keys.md | 1 + doc/api/session.md | 1 + doc/api/users.md | 5 + lib/api/entities.rb | 2 +- lib/gitlab/themes.rb | 84 +++++++ .../profiles/preferences_controller_spec.rb | 6 +- .../api/schemas/public_api/v4/user/login.json | 1 + spec/helpers/preferences_helper_spec.rb | 30 ++- .../migrate_events_to_push_event_payloads_spec.rb | 12 +- spec/lib/gitlab/themes_spec.rb | 48 ++++ ...custom_notification_settings_to_columns_spec.rb | 6 +- spec/models/user_spec.rb | 2 + spec/support/gitlab_stubs/session.json | 2 +- spec/support/gitlab_stubs/user.json | 4 +- 33 files changed, 648 insertions(+), 111 deletions(-) create mode 100644 app/assets/stylesheets/framework/gitlab-theme.scss create mode 100644 changelogs/unreleased/35012-navigation-add-option-to-change-navigation-color-palette.yml create mode 100644 db/migrate/20170816234252_add_theme_id_to_users.rb create mode 100644 lib/gitlab/themes.rb create mode 100644 spec/lib/gitlab/themes_spec.rb diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index c0524bf6aa3..35e7a10379f 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -19,6 +19,7 @@ @import "framework/flash"; @import "framework/forms"; @import "framework/gfm"; +@import "framework/gitlab-theme"; @import "framework/header"; @import "framework/highlight"; @import "framework/issue_box"; diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss new file mode 100644 index 00000000000..71f764923ff --- /dev/null +++ b/app/assets/stylesheets/framework/gitlab-theme.scss @@ -0,0 +1,265 @@ +/** + * Styles the GitLab application with a specific color theme + */ + +@mixin gitlab-theme($color-100, $color-200, $color-500, $color-700, $color-800, $color-900, $color-alternate) { + // Header + + header.navbar-gitlab-new { + background: linear-gradient(to right, $color-900, $color-800); + + .navbar-collapse { + color: $color-200; + } + + .container-fluid { + .navbar-toggle { + border-left: 1px solid lighten($color-700, 10%); + } + } + + .navbar-sub-nav, + .navbar-nav { + > li { + > a:hover, + > a:focus { + background-color: rgba($color-200, .2); + } + + &.active > a, + &.dropdown.open > a { + color: $color-900; + background-color: $color-alternate; + + svg { + fill: currentColor; + } + } + + &.line-separator { + border-left: 1px solid rgba($color-200, .2); + } + } + } + + .navbar-sub-nav { + color: $color-200; + } + + .nav { + > li { + color: $color-200; + + > a { + svg { + fill: $color-200; + } + + &.header-user-dropdown-toggle { + .header-user-avatar { + border-color: $color-200; + } + } + + &:hover, + &:focus { + @media (min-width: $screen-sm-min) { + background-color: rgba($color-200, .2); + } + + svg { + fill: currentColor; + } + } + } + + &.active > a, + &.dropdown.open > a { + color: $color-900; + background-color: $color-alternate; + + &:hover { + svg { + fill: $color-900; + } + } + } + + .impersonated-user, + .impersonated-user:hover { + svg { + fill: $color-900; + } + } + } + } + } + + .title { + > a { + &:hover, + &:focus { + background-color: rgba($color-200, .2); + } + } + } + + .search { + form { + background-color: rgba($color-200, .2); + + &:hover { + background-color: rgba($color-200, .3); + } + } + + .location-badge { + color: $color-100; + background-color: rgba($color-200, .1); + border-right: 1px solid $color-800; + } + + .search-input::placeholder { + color: rgba($color-200, .8); + } + + .search-input-wrap { + .search-icon, + .clear-icon { + color: rgba($color-200, .8); + } + } + + &.search-active { + form { + background-color: $white-light; + } + + .location-badge { + color: $gl-text-color; + } + + .search-input-wrap { + .search-icon { + color: rgba($color-200, .8); + } + } + } + } + + .btn-sign-in { + background-color: $color-100; + color: $color-900; + } + + + // Sidebar + .nav-sidebar li.active { + box-shadow: inset 4px 0 0 $color-700; + + > a { + color: $color-900; + } + + svg { + fill: $color-900; + } + } +} + + +body { + &.ui_indigo { + @include gitlab-theme($indigo-100, $indigo-200, $indigo-500, $indigo-700, $indigo-800, $indigo-900, $white-light); + } + + &.ui_dark { + @include gitlab-theme($theme-gray-100, $theme-gray-200, $theme-gray-500, $theme-gray-700, $theme-gray-800, $theme-gray-900, $white-light); + } + + &.ui_blue { + @include gitlab-theme($theme-blue-100, $theme-blue-200, $theme-blue-500, $theme-blue-700, $theme-blue-800, $theme-blue-900, $white-light); + } + + &.ui_green { + @include gitlab-theme($theme-green-100, $theme-green-200, $theme-green-500, $theme-green-700, $theme-green-800, $theme-green-900, $white-light); + } + + &.ui_light { + @include gitlab-theme($theme-gray-900, $theme-gray-700, $theme-gray-800, $theme-gray-700, $theme-gray-700, $theme-gray-100, $theme-gray-700); + + header.navbar-gitlab-new { + background: $theme-gray-100; + box-shadow: 0 2px 0 0 $border-color; + + .logo-text svg { + fill: $theme-gray-900; + } + + .navbar-sub-nav, + .navbar-nav { + > li { + > a:hover, + > a:focus { + color: $theme-gray-900; + } + + &.active > a { + color: $white-light; + + &:hover { + color: $white-light; + } + } + } + } + + .container-fluid { + .navbar-toggle, + .navbar-toggle:hover { + color: $theme-gray-700; + border-left: 1px solid $theme-gray-200; + } + } + } + + .search { + form { + background-color: $white-light; + box-shadow: inset 0 0 0 1px $border-color; + + &:hover { + background-color: $white-light; + box-shadow: inset 0 0 0 1px $blue-100; + + .location-badge { + box-shadow: inset 0 0 0 1px $blue-100; + } + } + } + + .search-input-wrap { + .search-icon { + color: $theme-gray-200; + } + } + + .location-badge { + color: $theme-gray-700; + box-shadow: inset 0 0 0 1px $border-color; + background-color: $nav-badge-bg; + border-right: 0; + } + } + + .nav-sidebar li.active { + > a { + color: $theme-gray-900; + } + + svg { + fill: $theme-gray-900; + } + } + } +} diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index b00a2d053e2..ab3c34df1fb 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -111,7 +111,6 @@ header { svg { height: 16px; width: 23px; - fill: currentColor; } } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index e300b006026..3857226cddb 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -74,6 +74,8 @@ $red-700: #a62d19; $red-800: #8b2615; $red-900: #711e11; +// GitLab themes + $indigo-50: #f7f7ff; $indigo-100: #ebebfa; $indigo-200: #d1d1f0; @@ -86,6 +88,43 @@ $indigo-800: #393982; $indigo-900: #292961; $indigo-950: #1a1a40; +$theme-gray-50: #fafafa; +$theme-gray-100: #f2f2f2; +$theme-gray-200: #dfdfdf; +$theme-gray-300: #cccccc; +$theme-gray-400: #bababa; +$theme-gray-500: #a7a7a7; +$theme-gray-600: #949494; +$theme-gray-700: #707070; +$theme-gray-800: #4f4f4f; +$theme-gray-900: #2e2e2e; +$theme-gray-950: #1f1f1f; + +$theme-blue-50: #f4f8fc; +$theme-blue-100: #e6edf5; +$theme-blue-200: #c8d7e6; +$theme-blue-300: #97b3cf; +$theme-blue-400: #648cb4; +$theme-blue-500: #4a79a8; +$theme-blue-600: #3e6fa0; +$theme-blue-700: #305c88; +$theme-blue-800: #25496e; +$theme-blue-900: #1a3652; +$theme-blue-950: #0f2235; + +$theme-green-50: #f2faf6; +$theme-green-100: #e4f3ea; +$theme-green-200: #c0dfcd; +$theme-green-300: #8ac2a1; +$theme-green-400: #52a274; +$theme-green-500: #35935c; +$theme-green-600: #288a50; +$theme-green-700: #1c7441; +$theme-green-800: #145d33; +$theme-green-900: #0d4524; +$theme-green-950: #072d16; + + $black: #000; $black-transparent: rgba(0, 0, 0, 0.3); $almost-black: #242424; diff --git a/app/assets/stylesheets/new_nav.scss b/app/assets/stylesheets/new_nav.scss index 2b6c0fc015c..8e095cbdd7e 100644 --- a/app/assets/stylesheets/new_nav.scss +++ b/app/assets/stylesheets/new_nav.scss @@ -9,10 +9,20 @@ header.navbar-gitlab-new { color: $white-light; - background: linear-gradient(to right, $indigo-900, $indigo-800); border-bottom: 0; min-height: $new-navbar-height; + .logo-text { + line-height: initial; + + svg { + width: 55px; + height: 14px; + margin: 0; + fill: $white-light; + } + } + .header-content { display: -webkit-flex; display: flex; @@ -38,10 +48,10 @@ header.navbar-gitlab-new { img { height: 28px; - margin-right: 10px; + margin-right: 8px; } - > a { + a { display: -webkit-flex; display: flex; align-items: center; @@ -54,22 +64,6 @@ header.navbar-gitlab-new { margin-right: 8px; } } - - .logo-text { - line-height: initial; - - svg { - width: 55px; - height: 14px; - margin: 0; - fill: $white-light; - } - } - - &:hover, - &:focus { - background-color: rgba($indigo-200, .2); - } } } @@ -106,7 +100,6 @@ header.navbar-gitlab-new { .navbar-collapse { padding-left: 0; - color: $indigo-200; box-shadow: 0; @media (max-width: $screen-xs-max) { @@ -132,7 +125,6 @@ header.navbar-gitlab-new { font-size: 14px; text-align: center; color: currentColor; - border-left: 1px solid lighten($indigo-700, 10%); &:hover, &:focus, @@ -167,63 +159,49 @@ header.navbar-gitlab-new { will-change: color; margin: 4px 2px; padding: 6px 8px; - color: $indigo-200; height: 32px; @media (max-width: $screen-xs-max) { padding: 0; } - svg { - fill: $indigo-200; - } - &.header-user-dropdown-toggle { margin-left: 2px; .header-user-avatar { - border-color: $indigo-200; margin-right: 0; } } - } - - .header-new-dropdown-toggle { - margin-right: 0; - } - > a:hover, - > a:focus { - text-decoration: none; - outline: 0; - opacity: 1; - color: $white-light; - - @media (min-width: $screen-sm-min) { - background-color: rgba($indigo-200, .2); - } + &:hover, + &:focus { + text-decoration: none; + outline: 0; + opacity: 1; + color: $white-light; - svg { - fill: currentColor; - } + svg { + fill: currentColor; + } - &.header-user-dropdown-toggle { - .header-user-avatar { - border-color: $white-light; + &.header-user-dropdown-toggle { + .header-user-avatar { + border-color: $white-light; + } } } } + .header-new-dropdown-toggle { + margin-right: 0; + } + .impersonated-user, .impersonated-user:hover { margin-right: 1px; background-color: $white-light; border-top-right-radius: 0; border-bottom-right-radius: 0; - - svg { - fill: $indigo-900; - } } .impersonation-btn, @@ -241,8 +219,6 @@ header.navbar-gitlab-new { &.active > a, &.dropdown.open > a { - color: $indigo-900; - background-color: $white-light; svg { fill: currentColor; @@ -256,7 +232,6 @@ header.navbar-gitlab-new { display: -webkit-flex; display: flex; margin: 0 0 0 6px; - color: $indigo-200; .dropdown-chevron { position: relative; @@ -274,17 +249,6 @@ header.navbar-gitlab-new { text-decoration: none; outline: 0; color: $white-light; - background-color: rgba($indigo-200, .2); - - svg { - fill: currentColor; - } - } - - &.active > a, - &.dropdown.open > a { - color: $indigo-900; - background-color: $white-light; svg { fill: currentColor; @@ -309,7 +273,6 @@ header.navbar-gitlab-new { } &.line-separator { - border-left: 1px solid rgba($indigo-200, .2); margin: 8px; } } @@ -339,17 +302,14 @@ header.navbar-gitlab-new { height: 32px; border: 0; border-radius: $border-radius-default; - background-color: rgba($indigo-200, .2); transition: border-color ease-in-out 0.15s, background-color ease-in-out 0.15s; &:hover { - background-color: rgba($indigo-200, .3); box-shadow: none; } } &.search-active form { - background-color: $white-light; box-shadow: none; .search-input { @@ -377,43 +337,26 @@ header.navbar-gitlab-new { } .search-input::placeholder { - color: rgba($indigo-200, .8); transition: color ease-in-out 0.15s; } .location-badge { font-size: 12px; - color: $indigo-100; - background-color: rgba($indigo-200, .1); - will-change: color; margin: -4px 4px -4px -4px; line-height: 25px; padding: 4px 8px; border-radius: 2px 0 0 2px; - border-right: 1px solid $indigo-800; height: 32px; transition: border-color ease-in-out 0.15s; } - .search-input-wrap { - .search-icon, - .clear-icon { - color: rgba($indigo-200, .8); - } - } - &.search-active { .location-badge { - color: $gl-text-color; background-color: $nav-badge-bg; border-color: $border-color; } .search-input-wrap { - .search-icon { - color: rgba($indigo-200, .8); - } - .clear-icon { color: $white-light; } @@ -517,8 +460,6 @@ header.navbar-gitlab-new { .btn-sign-in { margin-top: 3px; - background-color: $indigo-100; - color: $indigo-900; font-weight: $gl-font-weight-bold; &:hover { diff --git a/app/assets/stylesheets/new_sidebar.scss b/app/assets/stylesheets/new_sidebar.scss index 378ef8926d5..e4c12e46056 100644 --- a/app/assets/stylesheets/new_sidebar.scss +++ b/app/assets/stylesheets/new_sidebar.scss @@ -162,16 +162,9 @@ $new-sidebar-collapsed-width: 50px; } li.active { - box-shadow: inset 4px 0 0 $active-border; - > a { - color: $active-color; font-weight: $gl-font-weight-bold; } - - svg { - fill: $active-color; - } } @media (max-width: $screen-xs-max) { diff --git a/app/assets/stylesheets/pages/profiles/preferences.scss b/app/assets/stylesheets/pages/profiles/preferences.scss index 305feaacaa1..c197494b152 100644 --- a/app/assets/stylesheets/pages/profiles/preferences.scss +++ b/app/assets/stylesheets/pages/profiles/preferences.scss @@ -1,3 +1,67 @@ +@mixin application-theme-preview($color-1, $color-2, $color-3, $color-4) { + .one { + background-color: $color-1; + border-top-left-radius: $border-radius-default; + } + + .two { + background-color: $color-2; + border-top-right-radius: $border-radius-default; + } + + .three { + background-color: $color-3; + border-bottom-left-radius: $border-radius-default; + } + + .four { + background-color: $color-4; + border-bottom-right-radius: $border-radius-default; + } +} + +.application-theme { + label { + margin-right: 20px; + text-align: center; + } + + .preview { + font-size: 0; + margin-bottom: 10px; + + &.indigo { + @include application-theme-preview($indigo-900, $indigo-700, $indigo-800, $indigo-500); + } + + &.dark { + @include application-theme-preview($theme-gray-900, $theme-gray-700, $theme-gray-800, $theme-gray-600); + } + + &.light { + @include application-theme-preview($theme-gray-600, $theme-gray-200, $theme-gray-400, $theme-gray-100); + } + + &.blue { + @include application-theme-preview($theme-blue-900, $theme-blue-700, $theme-blue-800, $theme-blue-500); + } + + &.green { + @include application-theme-preview($theme-green-900, $theme-green-700, $theme-green-800, $theme-green-500); + } + } + + .preview-row { + display: block; + } + + .quadrant { + display: inline-block; + height: 50px; + width: 80px; + } +} + .syntax-theme { label { margin-right: 20px; diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 9ec7719fabb..cbcef70e957 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -211,6 +211,7 @@ class Admin::UsersController < Admin::ApplicationController :provider, :remember_me, :skype, + :theme_id, :twitter, :username, :website_url diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb index 1e557c47638..cce2a847b53 100644 --- a/app/controllers/profiles/preferences_controller.rb +++ b/app/controllers/profiles/preferences_controller.rb @@ -35,7 +35,8 @@ class Profiles::PreferencesController < Profiles::ApplicationController :color_scheme_id, :layout, :dashboard, - :project_view + :project_view, + :theme_id ) end end diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb index d36bb4ab074..0d7347ed30d 100644 --- a/app/helpers/preferences_helper.rb +++ b/app/helpers/preferences_helper.rb @@ -40,6 +40,10 @@ module PreferencesHelper ] end + def user_application_theme + @user_application_theme ||= Gitlab::Themes.for_user(current_user).css_class + end + def user_color_scheme Gitlab::ColorSchemes.for_user(current_user).css_class end diff --git a/app/models/user.rb b/app/models/user.rb index 358b04ac71f..09c9b3250eb 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -35,6 +35,7 @@ class User < ActiveRecord::Base default_value_for :project_view, :files default_value_for :notified_of_own_activity, false default_value_for :preferred_language, I18n.default_locale + default_value_for :theme_id, gitlab_config.default_theme attr_encrypted :otp_secret, key: Gitlab::Application.secrets.otp_key_base, diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 65ac8aaa59b..0ca34b276a7 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -1,7 +1,7 @@ !!! 5 %html{ lang: I18n.locale, class: page_class } = render "layouts/head" - %body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}", find_file: find_file_path } } + %body{ class: "#{user_application_theme} #{@body_class}", data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}", find_file: find_file_path } } = render "layouts/init_auto_complete" if @gfm_form = render 'peek/bar' = render "layouts/header/default" diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index 352c2d66bab..69885008ecd 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -3,6 +3,26 @@ = render 'profiles/head' = form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row prepend-top-default js-preferences-form' } do |f| + .col-lg-4.application-theme + %h4.prepend-top-0 + GitLab navigation theme + %p Customize the appearance of the application header and navigation sidebar. + .col-lg-8.application-theme + - Gitlab::Themes.each do |theme| + = label_tag do + .preview{ class: theme.name.downcase } + .preview-row + .quadrant.one + .quadrant.two + .preview-row + .quadrant.three + .quadrant.four + = f.radio_button :theme_id, theme.id + = theme.name + + .col-sm-12 + %hr + .col-lg-4.profile-settings-sidebar %h4.prepend-top-0 Syntax highlighting theme diff --git a/app/views/profiles/preferences/update.js.erb b/app/views/profiles/preferences/update.js.erb index 431ab9d052b..8966dd3fd86 100644 --- a/app/views/profiles/preferences/update.js.erb +++ b/app/views/profiles/preferences/update.js.erb @@ -1,3 +1,7 @@ +// Remove body class for any previous theme, re-add current one +$('body').removeClass('<%= Gitlab::Themes.body_classes %>') +$('body').addClass('<%= user_application_theme %>') + // Toggle container-fluid class if ('<%= current_user.layout %>' === 'fluid') { $('.content-wrapper .container-fluid').removeClass('container-limited') diff --git a/changelogs/unreleased/35012-navigation-add-option-to-change-navigation-color-palette.yml b/changelogs/unreleased/35012-navigation-add-option-to-change-navigation-color-palette.yml new file mode 100644 index 00000000000..74aa337a18c --- /dev/null +++ b/changelogs/unreleased/35012-navigation-add-option-to-change-navigation-color-palette.yml @@ -0,0 +1,5 @@ +--- +title: Add option in preferences to change navigation theme color +merge_request: +author: +type: added diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index e9661090844..cd44f888d3f 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -76,6 +76,13 @@ production: &base # default_can_create_group: false # default: true # username_changing_enabled: false # default: true - User can change her username/namespace + ## Default theme ID + ## 1 - Indigo + ## 2 - Dark + ## 3 - Light + ## 4 - Blue + ## 5 - Green + # default_theme: 1 # default: 1 ## Automatic issue closing # If a commit message matches this regular expression, all issues referenced from the matched text will be closed. diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 8a560d84f1f..94429ee91a9 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -232,6 +232,7 @@ Settings['gitlab'] ||= Settingslogic.new({}) Settings.gitlab['default_projects_limit'] ||= 100000 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::Themes::APPLICATION_DEFAULT if Settings.gitlab['default_theme'].nil? Settings.gitlab['host'] ||= ENV['GITLAB_HOST'] || 'localhost' Settings.gitlab['ssh_host'] ||= Settings.gitlab.host Settings.gitlab['https'] = false if Settings.gitlab['https'].nil? diff --git a/db/migrate/20170816234252_add_theme_id_to_users.rb b/db/migrate/20170816234252_add_theme_id_to_users.rb new file mode 100644 index 00000000000..5043f9ec591 --- /dev/null +++ b/db/migrate/20170816234252_add_theme_id_to_users.rb @@ -0,0 +1,10 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddThemeIdToUsers < ActiveRecord::Migration + DOWNTIME = false + + def change + add_column :users, :theme_id, :integer, limit: 2 + end +end diff --git a/db/schema.rb b/db/schema.rb index df941afa7d7..2149f5ad23d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1608,6 +1608,7 @@ ActiveRecord::Schema.define(version: 20170905112933) do t.boolean "notified_of_own_activity" t.string "preferred_language" t.string "rss_token" + t.integer "theme_id", limit: 2 end add_index "users", ["admin"], name: "index_users_on_admin", using: :btree diff --git a/doc/api/keys.md b/doc/api/keys.md index 376ac27df3a..ddcf7830621 100644 --- a/doc/api/keys.md +++ b/doc/api/keys.md @@ -32,6 +32,7 @@ Parameters: "twitter": "", "website_url": "", "email": "john@example.com", + "theme_id": 2, "color_scheme_id": 1, "projects_limit": 10, "current_sign_in_at": null, diff --git a/doc/api/session.md b/doc/api/session.md index f79eac11689..b97e26f34a2 100644 --- a/doc/api/session.md +++ b/doc/api/session.md @@ -39,6 +39,7 @@ Example response: "twitter": "", "website_url": "", "email": "john@example.com", + "theme_id": 1, "color_scheme_id": 1, "projects_limit": 10, "current_sign_in_at": "2015-07-07T07:10:58.392Z", diff --git a/doc/api/users.md b/doc/api/users.md index 9f3e4caf2f4..6d5db16b36a 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -72,6 +72,7 @@ GET /users "organization": "", "last_sign_in_at": "2012-06-01T11:41:01Z", "confirmed_at": "2012-05-23T09:05:22Z", + "theme_id": 1, "last_activity_on": "2012-05-23", "color_scheme_id": 2, "projects_limit": 100, @@ -105,6 +106,7 @@ GET /users "organization": "", "last_sign_in_at": null, "confirmed_at": "2012-05-30T16:53:06.148Z", + "theme_id": 1, "last_activity_on": "2012-05-23", "color_scheme_id": 3, "projects_limit": 100, @@ -215,6 +217,7 @@ Parameters: "organization": "", "last_sign_in_at": "2012-06-01T11:41:01Z", "confirmed_at": "2012-05-23T09:05:22Z", + "theme_id": 1, "last_activity_on": "2012-05-23", "color_scheme_id": 2, "projects_limit": 100, @@ -341,6 +344,7 @@ GET /user "organization": "", "last_sign_in_at": "2012-06-01T11:41:01Z", "confirmed_at": "2012-05-23T09:05:22Z", + "theme_id": 1, "last_activity_on": "2012-05-23", "color_scheme_id": 2, "projects_limit": 100, @@ -387,6 +391,7 @@ GET /user "organization": "", "last_sign_in_at": "2012-06-01T11:41:01Z", "confirmed_at": "2012-05-23T09:05:22Z", + "theme_id": 1, "last_activity_on": "2012-05-23", "color_scheme_id": 2, "projects_limit": 100, diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 216408064d1..52c49e5caa9 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -45,7 +45,7 @@ module API expose :confirmed_at expose :last_activity_on expose :email - expose :color_scheme_id, :projects_limit, :current_sign_in_at + expose :theme_id, :color_scheme_id, :projects_limit, :current_sign_in_at expose :identities, using: Entities::Identity expose :can_create_group?, as: :can_create_group expose :can_create_project?, as: :can_create_project diff --git a/lib/gitlab/themes.rb b/lib/gitlab/themes.rb new file mode 100644 index 00000000000..d43eff5ba4a --- /dev/null +++ b/lib/gitlab/themes.rb @@ -0,0 +1,84 @@ +module Gitlab + # Module containing GitLab's application theme definitions and helper methods + # for accessing them. + module Themes + extend self + + # Theme ID used when no `default_theme` configuration setting is provided. + APPLICATION_DEFAULT = 1 + + # Struct class representing a single Theme + Theme = Struct.new(:id, :name, :css_class) + + # All available Themes + THEMES = [ + Theme.new(1, 'Indigo', 'ui_indigo'), + Theme.new(2, 'Dark', 'ui_dark'), + Theme.new(3, 'Light', 'ui_light'), + Theme.new(4, 'Blue', 'ui_blue'), + Theme.new(5, 'Green', 'ui_green') + ].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 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 by_id(id) + THEMES.detect { |t| t.id == id } || default + end + + # Returns the number of defined Themes + def count + THEMES.size + end + + # Get the default Theme + # + # Returns a Theme + def default + by_id(default_id) + end + + # Iterate through each Theme + # + # Yields the Theme object + def each(&block) + THEMES.each(&block) + end + + # Get the Theme for the specified user, or the default + # + # user - User record + # + # Returns a Theme + def for_user(user) + if user + by_id(user.theme_id) + else + default + end + end + + private + + def default_id + @default_id ||= begin + id = Gitlab.config.gitlab.default_theme.to_i + theme_ids = THEMES.map(&:id) + + theme_ids.include?(id) ? id : APPLICATION_DEFAULT + end + end + end +end diff --git a/spec/controllers/profiles/preferences_controller_spec.rb b/spec/controllers/profiles/preferences_controller_spec.rb index a5f544b4f92..a66b4ab0902 100644 --- a/spec/controllers/profiles/preferences_controller_spec.rb +++ b/spec/controllers/profiles/preferences_controller_spec.rb @@ -25,7 +25,8 @@ describe Profiles::PreferencesController do def go(params: {}, format: :js) params.reverse_merge!( color_scheme_id: '1', - dashboard: 'stars' + dashboard: 'stars', + theme_id: '1' ) patch :update, user: params, format: format @@ -40,7 +41,8 @@ describe Profiles::PreferencesController do it "changes the user's preferences" do prefs = { color_scheme_id: '1', - dashboard: 'stars' + dashboard: 'stars', + theme_id: '2' }.with_indifferent_access expect(user).to receive(:assign_attributes).with(prefs) diff --git a/spec/fixtures/api/schemas/public_api/v4/user/login.json b/spec/fixtures/api/schemas/public_api/v4/user/login.json index 6181b3ccc86..e6c1d9c9d84 100644 --- a/spec/fixtures/api/schemas/public_api/v4/user/login.json +++ b/spec/fixtures/api/schemas/public_api/v4/user/login.json @@ -19,6 +19,7 @@ "organization", "last_sign_in_at", "confirmed_at", + "theme_id", "color_scheme_id", "projects_limit", "current_sign_in_at", diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb index a04c87b08eb..8b8080563d3 100644 --- a/spec/helpers/preferences_helper_spec.rb +++ b/spec/helpers/preferences_helper_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe PreferencesHelper do - describe 'dashboard_choices' do + describe '#dashboard_choices' do it 'raises an exception when defined choices may be missing' do expect(User).to receive(:dashboards).and_return(foo: 'foo') expect { helper.dashboard_choices }.to raise_error(RuntimeError) @@ -26,7 +26,33 @@ describe PreferencesHelper do end end - describe 'user_color_scheme' do + describe '#user_application_theme' do + context 'with a user' do + it "returns user's theme's css_class" do + stub_user(theme_id: 3) + + expect(helper.user_application_theme).to eq 'ui_light' + end + + it 'returns the default when id is invalid' do + stub_user(theme_id: Gitlab::Themes.count + 5) + + allow(Gitlab.config.gitlab).to receive(:default_theme).and_return(1) + + expect(helper.user_application_theme).to eq 'ui_indigo' + end + end + + context 'without a user' do + it 'returns the default theme' do + stub_user + + expect(helper.user_application_theme).to eq Gitlab::Themes.default.css_class + end + end + end + + describe '#user_color_scheme' do context 'with a user' do it "returns user's scheme's css_class" do allow(helper).to receive(:current_user) diff --git a/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb b/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb index b155c20d8d3..cb52d971047 100644 --- a/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb +++ b/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb @@ -215,9 +215,17 @@ end # to a specific version of the database where said table is still present. # describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads, :migration, schema: 20170825154015 do + let(:user_class) do + Class.new(ActiveRecord::Base) do + self.table_name = 'users' + end + end + let(:migration) { described_class.new } - let(:project) { create(:project_empty_repo) } - let(:author) { create(:user) } + let(:user_class) { table(:users) } + let(:author) { build(:user).becomes(user_class).tap(&:save!).becomes(User) } + let(:namespace) { create(:namespace, owner: author) } + let(:project) { create(:project_empty_repo, namespace: namespace, creator: author) } # We can not rely on FactoryGirl as the state of Event may change in ways that # the background migration does not expect, hence we use the Event class of diff --git a/spec/lib/gitlab/themes_spec.rb b/spec/lib/gitlab/themes_spec.rb new file mode 100644 index 00000000000..ecacea6bb35 --- /dev/null +++ b/spec/lib/gitlab/themes_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +describe Gitlab::Themes, lib: true 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_indigo') + expect(css).to include(' ui_dark ') + 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 'Indigo' + expect(described_class.by_id(3).name).to eq 'Light' + 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 + end + end +end diff --git a/spec/migrations/convert_custom_notification_settings_to_columns_spec.rb b/spec/migrations/convert_custom_notification_settings_to_columns_spec.rb index 1396d12e5a9..759e77ac9db 100644 --- a/spec/migrations/convert_custom_notification_settings_to_columns_spec.rb +++ b/spec/migrations/convert_custom_notification_settings_to_columns_spec.rb @@ -2,6 +2,8 @@ require 'spec_helper' require Rails.root.join('db', 'post_migrate', '20170607121233_convert_custom_notification_settings_to_columns') describe ConvertCustomNotificationSettingsToColumns, :migration do + let(:user_class) { table(:users) } + let(:settings_params) do [ { level: 0, events: [:new_note] }, # disabled, single event @@ -19,7 +21,7 @@ describe ConvertCustomNotificationSettingsToColumns, :migration do events[event] = true end - user = create(:user) + user = build(:user).becomes(user_class).tap(&:save!) create_params = { user_id: user.id, level: params[:level], events: events } notification_setting = described_class::NotificationSetting.create(create_params) @@ -35,7 +37,7 @@ describe ConvertCustomNotificationSettingsToColumns, :migration do events[event] = true end - user = create(:user) + user = build(:user).becomes(user_class).tap(&:save!) create_params = events.merge(user_id: user.id, level: params[:level]) notification_setting = described_class::NotificationSetting.create(create_params) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 3ba01313efb..c1affa812aa 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -716,6 +716,7 @@ describe User do it "applies defaults to user" do expect(user.projects_limit).to eq(Gitlab.config.gitlab.default_projects_limit) expect(user.can_create_group).to eq(Gitlab.config.gitlab.default_can_create_group) + expect(user.theme_id).to eq(Gitlab.config.gitlab.default_theme) expect(user.external).to be_falsey end end @@ -726,6 +727,7 @@ describe User do it "applies 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(1) end end diff --git a/spec/support/gitlab_stubs/session.json b/spec/support/gitlab_stubs/session.json index cd55d63125e..688175369ae 100644 --- a/spec/support/gitlab_stubs/session.json +++ b/spec/support/gitlab_stubs/session.json @@ -7,7 +7,7 @@ "skype":"aertert", "linkedin":"", "twitter":"", - "color_scheme_id":2, + "theme_id":2,"color_scheme_id":2, "state":"active", "created_at":"2012-12-21T13:02:20Z", "extern_uid":null, diff --git a/spec/support/gitlab_stubs/user.json b/spec/support/gitlab_stubs/user.json index cd55d63125e..ce8dfe5ae75 100644 --- a/spec/support/gitlab_stubs/user.json +++ b/spec/support/gitlab_stubs/user.json @@ -7,7 +7,7 @@ "skype":"aertert", "linkedin":"", "twitter":"", - "color_scheme_id":2, + "theme_id":2,"color_scheme_id":2, "state":"active", "created_at":"2012-12-21T13:02:20Z", "extern_uid":null, @@ -17,4 +17,4 @@ "can_create_project":false, "private_token":"Wvjy2Krpb7y8xi93owUz", "access_token":"Wvjy2Krpb7y8xi93owUz" -} +} \ No newline at end of file -- cgit v1.2.3