Commit 695df38f authored by Sean McGivern's avatar Sean McGivern

Merge branch '55057-system-message-to-core' into 'master'

Port EE System Header And Footer feature to CE

Closes #55057

See merge request gitlab-org/gitlab-ce!25241
parents 6fa88ed7 28e1739a
...@@ -65,3 +65,4 @@ ...@@ -65,3 +65,4 @@
@import 'framework/terms'; @import 'framework/terms';
@import 'framework/read_more'; @import 'framework/read_more';
@import 'framework/flex_grid'; @import 'framework/flex_grid';
@import 'framework/system_messages';
.header-message,
.footer-message {
padding: 0 15px;
border: 1px solid transparent;
border-radius: 0;
position: fixed;
left: 0;
width: 100%;
text-align: center;
margin: 0;
z-index: 1000;
p {
@include str-truncated(100%);
margin-top: 0;
margin-bottom: 0;
}
}
.header-message {
top: 0;
height: $system-header-height;
line-height: $system-header-height;
}
.footer-message {
bottom: 0;
height: $system-footer-height;
line-height: $system-footer-height;
}
.with-performance-bar {
.header-message {
top: $performance-bar-height;
}
}
// System Header
.with-system-header {
// main navigation
// login page
.navbar-gitlab,
.fixed-top {
top: $system-header-height;
}
// left sidebar eg: project page
// right sidebar eg: MR page
.nav-sidebar,
.right-sidebar {
top: $system-header-height + $header-height;
}
.content-wrapper {
margin-top: $system-header-height + $header-height;
}
// Performance Bar
// System Header
&.with-performance-bar {
// main navigation
header.navbar-gitlab {
top: $performance-bar-height + $system-header-height;
}
.layout-page {
margin-top: $header-height + $performance-bar-height + $system-header-height;
}
// left sidebar eg: project page
// right sidebar eg: MR page
.nav-sidebar,
.right-sidebar {
top: $header-height + $performance-bar-height + $system-header-height;
}
}
}
// System Footer
.with-system-footer {
// left sidebar eg: project page
// right sidebar eg: mr page
.nav-sidebar,
.right-sidebar,
// navless pages' footer eg: login page
// navless pages' footer border eg: login page
&.devise-layout-html body .footer-container,
&.devise-layout-html body hr.footer-fixed {
bottom: $system-footer-height;
}
}
.fullscreen-layout {
.header-message,
.footer-message {
position: static;
top: auto;
bottom: auto;
}
.content-wrapper {
.with-system-header & {
margin-top: 0;
}
.with-system-footer & {
margin-top: 0;
}
}
}
...@@ -276,6 +276,8 @@ $general-hover-transition-duration: 100ms; ...@@ -276,6 +276,8 @@ $general-hover-transition-duration: 100ms;
$general-hover-transition-curve: linear; $general-hover-transition-curve: linear;
$highlight-changes-color: rgb(235, 255, 232); $highlight-changes-color: rgb(235, 255, 232);
$performance-bar-height: 35px; $performance-bar-height: 35px;
$system-header-height: 35px;
$system-footer-height: $system-header-height;
$flash-height: 52px; $flash-height: 52px;
$context-header-height: 60px; $context-header-height: 60px;
$breadcrumb-min-height: 48px; $breadcrumb-min-height: 48px;
......
...@@ -74,6 +74,10 @@ class Admin::AppearancesController < Admin::ApplicationController ...@@ -74,6 +74,10 @@ class Admin::AppearancesController < Admin::ApplicationController
favicon_cache favicon_cache
new_project_guidelines new_project_guidelines
updated_by updated_by
header_message
footer_message
message_background_color
message_font_color
] ]
end end
end end
...@@ -40,4 +40,36 @@ module AppearancesHelper ...@@ -40,4 +40,36 @@ module AppearancesHelper
render 'shared/logo_type.svg' render 'shared/logo_type.svg'
end end
end end
def header_message
return unless current_appearance&.show_header?
class_names = []
class_names << 'with-performance-bar' if performance_bar_enabled?
render_message(:header_message, class_names)
end
def footer_message
return unless current_appearance&.show_footer?
render_message(:footer_message)
end
private
def render_message(field_sym, class_names = [])
class_names << field_sym.to_s.dasherize
content_tag :div, class: class_names, style: message_style do
markdown_field(current_appearance, field_sym)
end
end
def message_style
style = []
style << "background-color: #{current_appearance.message_background_color};"
style << "color: #{current_appearance.message_font_color}"
style.join
end
end end
...@@ -214,12 +214,19 @@ module ApplicationHelper ...@@ -214,12 +214,19 @@ module ApplicationHelper
class_names = [] class_names = []
class_names << 'issue-boards-page' if current_controller?(:boards) class_names << 'issue-boards-page' if current_controller?(:boards)
class_names << 'with-performance-bar' if performance_bar_enabled? class_names << 'with-performance-bar' if performance_bar_enabled?
class_names << system_message_class
class_names class_names
end end
# EE feature: System header and footer, unavailable in CE
def system_message_class def system_message_class
class_names = []
return class_names unless appearance
class_names << 'with-system-header' if appearance.show_header?
class_names << 'with-system-footer' if appearance.show_footer?
class_names
end end
# Returns active css class when condition returns true # Returns active css class when condition returns true
...@@ -292,4 +299,10 @@ module ApplicationHelper ...@@ -292,4 +299,10 @@ module ApplicationHelper
snippets: snippets_project_autocomplete_sources_path(object) snippets: snippets_project_autocomplete_sources_path(object)
} }
end end
private
def appearance
::Appearance.current
end
end end
...@@ -8,12 +8,19 @@ class Appearance < ActiveRecord::Base ...@@ -8,12 +8,19 @@ class Appearance < ActiveRecord::Base
cache_markdown_field :description cache_markdown_field :description
cache_markdown_field :new_project_guidelines cache_markdown_field :new_project_guidelines
cache_markdown_field :header_message, pipeline: :broadcast_message
cache_markdown_field :footer_message, pipeline: :broadcast_message
validates :logo, file_size: { maximum: 1.megabyte } validates :logo, file_size: { maximum: 1.megabyte }
validates :header_logo, file_size: { maximum: 1.megabyte } validates :header_logo, file_size: { maximum: 1.megabyte }
validates :message_background_color, allow_blank: true, color: true
validates :message_font_color, allow_blank: true, color: true
validate :single_appearance_row, on: :create validate :single_appearance_row, on: :create
default_value_for :message_background_color, '#E75E40'
default_value_for :message_font_color, '#FFFFFF'
mount_uploader :logo, AttachmentUploader mount_uploader :logo, AttachmentUploader
mount_uploader :header_logo, AttachmentUploader mount_uploader :header_logo, AttachmentUploader
mount_uploader :favicon, FaviconUploader mount_uploader :favicon, FaviconUploader
...@@ -41,6 +48,14 @@ class Appearance < ActiveRecord::Base ...@@ -41,6 +48,14 @@ class Appearance < ActiveRecord::Base
logo_system_path(favicon, 'favicon') logo_system_path(favicon, 'favicon')
end end
def show_header?
header_message.present?
end
def show_footer?
footer_message.present?
end
private private
def logo_system_path(logo, mount_type) def logo_system_path(logo, mount_type)
......
...@@ -42,6 +42,7 @@ ...@@ -42,6 +42,7 @@
%br %br
Images with incorrect dimensions are not resized automatically, and may result in unexpected behavior. Images with incorrect dimensions are not resized automatically, and may result in unexpected behavior.
= render partial: 'admin/appearances/system_header_footer_form', locals: { form: f }
%hr %hr
.row .row
......
- form = local_assigns.fetch(:form)
%hr
.row
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
= _('System header and footer')
.col-lg-8
.form-group
= form.label :header_message, _('Header message'), class: 'col-form-label label-bold'
= form.text_area :header_message, placeholder: _('State your message to activate'), class: "form-control js-autosize"
.form-group
= form.label :footer_message, _('Footer message'), class: 'col-form-label label-bold'
= form.text_area :footer_message, placeholder: _('State your message to activate'), class: "form-control js-autosize"
.form-group.js-toggle-colors-container
%button.btn.btn-link.js-toggle-colors-link{ type: 'button' }
= _('Customize colors')
.form-group.js-toggle-colors-container.hide
= form.label :message_background_color, _('Background Color'), class: 'col-form-label label-bold'
= form.color_field :message_background_color, class: "form-control"
.form-group.js-toggle-colors-container.hide
= form.label :message_font_color, _('Font Color'), class: 'col-form-label label-bold'
= form.color_field :message_font_color, class: "form-control"
...@@ -5,7 +5,9 @@ ...@@ -5,7 +5,9 @@
= render "layouts/init_auto_complete" if @gfm_form = render "layouts/init_auto_complete" if @gfm_form
= render "layouts/init_client_detection_flags" = render "layouts/init_client_detection_flags"
= render 'peek/bar' = render 'peek/bar'
= render partial: "layouts/header/default", locals: { project: @project, group: @group } = header_message
= render partial: "layouts/header/default", locals: { project: @project, group: @group }
= render 'layouts/page', sidebar: sidebar, nav: nav = render 'layouts/page', sidebar: sidebar, nav: nav
= footer_message
= yield :scripts_body = yield :scripts_body
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
%html.devise-layout-html{ class: system_message_class } %html.devise-layout-html{ class: system_message_class }
= render "layouts/head" = render "layouts/head"
%body.ui-indigo.login-page.application.navless.qa-login-page{ data: { page: body_data_page } } %body.ui-indigo.login-page.application.navless.qa-login-page{ data: { page: body_data_page } }
= header_message
.page-wrap .page-wrap
= render "layouts/header/empty" = render "layouts/header/empty"
.login-page-broadcast .login-page-broadcast
...@@ -34,3 +35,4 @@ ...@@ -34,3 +35,4 @@
= link_to _("Explore"), explore_root_path = link_to _("Explore"), explore_root_path
= link_to _("Help"), help_path = link_to _("Help"), help_path
= link_to _("About GitLab"), "https://about.gitlab.com/" = link_to _("About GitLab"), "https://about.gitlab.com/"
= footer_message
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
%html{ lang: "en", class: system_message_class } %html{ lang: "en", class: system_message_class }
= render "layouts/head" = render "layouts/head"
%body.ui-indigo.login-page.application.navless %body.ui-indigo.login-page.application.navless
= header_message
= render "layouts/header/empty" = render "layouts/header/empty"
= render "layouts/broadcast" = render "layouts/broadcast"
.container.navless-container .container.navless-container
...@@ -15,3 +16,4 @@ ...@@ -15,3 +16,4 @@
= link_to _("Explore"), explore_root_path = link_to _("Explore"), explore_root_path
= link_to _("Help"), help_path = link_to _("Help"), help_path
= link_to _("About GitLab"), "https://about.gitlab.com/" = link_to _("About GitLab"), "https://about.gitlab.com/"
= footer_message
---
title: Port System Header and Footer feature to Core
merge_request: 25241
author:
type: added
# frozen_string_literal: true
class AddHeaderAndFooterBannersToAppearancesTable < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :appearances, :header_message, :text
add_column :appearances, :header_message_html, :text
add_column :appearances, :footer_message, :text
add_column :appearances, :footer_message_html, :text
add_column :appearances, :message_background_color, :text
add_column :appearances, :message_font_color, :text
end
end
...@@ -37,6 +37,12 @@ ActiveRecord::Schema.define(version: 20190204115450) do ...@@ -37,6 +37,12 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.integer "cached_markdown_version" t.integer "cached_markdown_version"
t.text "new_project_guidelines" t.text "new_project_guidelines"
t.text "new_project_guidelines_html" t.text "new_project_guidelines_html"
t.text "header_message"
t.text "header_message_html"
t.text "footer_message"
t.text "footer_message_html"
t.text "message_background_color"
t.text "message_font_color"
t.string "favicon" t.string "favicon"
end end
......
# Adding a system message to every page
> [Introduced][ee-1283] in [GitLab Premium][eep] 10.7.
Navigate to the **Admin** area and go to the **Appearance** page.
Under **System header and footer** insert your header message and/or footer message.
Both background and font color of the header and footer are customizable.
![appearance](system_header_and_footer_messages/appearance.png)
After saving, all GitLab pages will contain the custom system header and/or footer messages:
![custom_header_footer](system_header_and_footer_messages/custom_header_footer.png)
The GitLab sign in page will also show the header and the footer messages:
![sign_up_custom_header_and_footer](system_header_and_footer_messages/sign_up_custom_header_and_footer.png)
...@@ -951,6 +951,9 @@ msgstr "" ...@@ -951,6 +951,9 @@ msgstr ""
msgid "Average per day: %{average}" msgid "Average per day: %{average}"
msgstr "" msgstr ""
msgid "Background Color"
msgstr ""
msgid "Background Jobs" msgid "Background Jobs"
msgstr "" msgstr ""
...@@ -2450,6 +2453,9 @@ msgstr "" ...@@ -2450,6 +2453,9 @@ msgstr ""
msgid "Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}." msgid "Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}."
msgstr "" msgstr ""
msgid "Customize colors"
msgstr ""
msgid "Customize how FogBugz email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import." msgid "Customize how FogBugz email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import."
msgstr "" msgstr ""
...@@ -3432,6 +3438,12 @@ msgstr "" ...@@ -3432,6 +3438,12 @@ msgstr ""
msgid "Follow the steps below to export your Google Code project data." msgid "Follow the steps below to export your Google Code project data."
msgstr "" msgstr ""
msgid "Font Color"
msgstr ""
msgid "Footer message"
msgstr ""
msgid "For internal projects, any logged in user can view pipelines and access job details (output logs and artifacts)" msgid "For internal projects, any logged in user can view pipelines and access job details (output logs and artifacts)"
msgstr "" msgstr ""
...@@ -3759,6 +3771,9 @@ msgstr "" ...@@ -3759,6 +3771,9 @@ msgstr ""
msgid "GroupsTree|Search by name" msgid "GroupsTree|Search by name"
msgstr "" msgstr ""
msgid "Header message"
msgstr ""
msgid "Health Check" msgid "Health Check"
msgstr "" msgstr ""
...@@ -7051,6 +7066,9 @@ msgstr "" ...@@ -7051,6 +7066,9 @@ msgstr ""
msgid "Starts at (UTC)" msgid "Starts at (UTC)"
msgstr "" msgstr ""
msgid "State your message to activate"
msgstr ""
msgid "Status" msgid "Status"
msgstr "" msgstr ""
...@@ -7132,6 +7150,9 @@ msgstr "" ...@@ -7132,6 +7150,9 @@ msgstr ""
msgid "System default (%{default})" msgid "System default (%{default})"
msgstr "" msgstr ""
msgid "System header and footer"
msgstr ""
msgid "System metrics (Custom)" msgid "System metrics (Custom)"
msgstr "" msgstr ""
......
require 'spec_helper'
describe Admin::AppearancesController do
let(:admin) { create(:admin) }
let(:header_message) { "Header message" }
let(:footer_message) { "Footer" }
describe 'POST #create' do
let(:create_params) do
{
title: "Foo",
description: "Bar",
header_message: header_message,
footer_message: footer_message
}
end
before do
sign_in(admin)
end
it 'creates appearance with footer and header message' do
post :create, params: { appearance: create_params }
expect(Appearance.current).to have_attributes(
header_message: header_message,
footer_message: footer_message
)
end
end
describe 'PUT #update' do
let(:update_params) do
{
header_message: header_message,
footer_message: footer_message
}
end
before do
create(:appearance)
sign_in(admin)
end
it 'updates appearance with footer and header message' do
put :update, params: { appearance: update_params }
expect(Appearance.current).to have_attributes(
header_message: header_message,
footer_message: footer_message
)
end
end
end
...@@ -39,6 +39,38 @@ describe 'Admin Appearance' do ...@@ -39,6 +39,38 @@ describe 'Admin Appearance' do
expect_custom_new_project_appearance(appearance) expect_custom_new_project_appearance(appearance)
end end
context 'Custom system header and footer' do
before do
sign_in(create(:admin))
end
context 'when system header and footer messages are empty' do
it 'shows custom system header and footer fields' do
visit admin_appearances_path
expect(page).to have_field('appearance_header_message', with: '')
expect(page).to have_field('appearance_footer_message', with: '')
expect(page).to have_field('appearance_message_background_color')
expect(page).to have_field('appearance_message_font_color')
end
end
context 'when system header and footer messages are not empty' do
before do
appearance.update(header_message: 'Foo', footer_message: 'Bar')
end
it 'shows custom system header and footer fields' do
visit admin_appearances_path
expect(page).to have_field('appearance_header_message', with: appearance.header_message)
expect(page).to have_field('appearance_footer_message', with: appearance.footer_message)
expect(page).to have_field('appearance_message_background_color')
expect(page).to have_field('appearance_message_font_color')
end
end
end
it 'Custom sign-in page' do it 'Custom sign-in page' do
visit new_user_session_path visit new_user_session_path
......
# frozen_string_literal: true
require 'rails_helper'
describe 'Display system header and footer bar' do
let(:header_message) { "Foo" }
let(:footer_message) { "Bar" }
shared_examples 'system header is configured' do
it 'shows system header' do
expect(page).to have_css('.header-message')
end
it 'shows the correct content' do
page.within('.header-message') do
expect(page).to have_content(header_message)
end
end
end
shared_examples 'system footer is configured' do
it 'shows system footer' do
expect(page).to have_css('.footer-message')
end
it 'shows the correct content' do
page.within('.footer-message') do
expect(page).to have_content(footer_message)
end
end
end
shared_examples 'system header is not configured' do
it 'does not show system header' do
expect(page).not_to have_css('.header-message')
end
end
shared_examples 'system footer is not configured' do
it 'does not show system footer' do
expect(page).not_to have_css('.footer-message')
end
end
context 'when authenticated' do
context 'when system header and footer are not configured' do
before do
sign_in(create(:user))
visit root_path
end
it_behaves_like 'system header is not configured'
it_behaves_like 'system footer is not configured'
end
context 'when only system header is defined' do
before do
create(:appearance, header_message: header_message)
sign_in(create(:user))
visit root_path
end
it_behaves_like 'system header is configured'
it_behaves_like 'system footer is not configured'
end
context 'when only system footer is defined' do
before do
create(:appearance, footer_message: footer_message)
sign_in(create(:user))
visit root_path
end
it_behaves_like 'system header is not configured'
it_behaves_like 'system footer is configured'
end
context 'when system header and footer are defined' do
before do
create(:appearance, header_message: header_message, footer_message: footer_message)
sign_in(create(:user))
visit root_path
end
it_behaves_like 'system header is configured'
it_behaves_like 'system footer is configured'
end
end
context 'when not authenticated' do
context 'when system header and footer are not configured' do
before do
visit root_path
end
it_behaves_like 'system header is not configured'
it_behaves_like 'system footer is not configured'
end
context 'when only system header is defined' do
before do
create(:appearance, header_message: header_message)
visit root_path
end
it_behaves_like 'system header is configured'
it_behaves_like 'system footer is not configured'
end
context 'when only system footer is defined' do
before do
create(:appearance, footer_message: footer_message)
visit root_path
end
it_behaves_like 'system header is not configured'
it_behaves_like 'system footer is configured'
end
context 'when system header and footer are defined' do
before do
create(:appearance, header_message: header_message, footer_message: footer_message)
visit root_path
end
it_behaves_like 'system header is configured'
it_behaves_like 'system footer is configured'
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe AppearancesHelper do
before do
user = create(:user)
allow(helper).to receive(:current_user).and_return(user)
end
describe '#header_message' do
it 'returns nil when header message field is not set' do
create(:appearance)
expect(helper.header_message).to be_nil
end
context 'when header message is set' do
it 'includes current message' do
message = "Foo bar"
create(:appearance, header_message: message)
expect(helper.header_message).to include(message)
end
end
end
describe '#footer_message' do
it 'returns nil when footer message field is not set' do
create(:appearance)
expect(helper.footer_message).to be_nil
end
context 'when footer message is set' do
it 'includes current message' do
message = "Foo bar"
create(:appearance, footer_message: message)
expect(helper.footer_message).to include(message)
end
end
end
describe '#brand_image' do
let!(:appearance) { create(:appearance, :with_logo) }
context 'when there is a logo' do
it 'returns a path' do
expect(helper.brand_image).to match(%r(img data-src="/uploads/-/system/appearance/.*png))
end
end
context 'when there is a logo but no associated upload' do
before do
# Legacy attachments were not tracked in the uploads table
appearance.logo.upload.destroy
appearance.reload
end
it 'falls back to using the original path' do
expect(helper.brand_image).to match(%r(img data-src="/uploads/-/system/appearance/.*png))
end
end
end
describe '#brand_title' do
it 'returns the default CE title when no appearance is present' do
allow(helper)
.to receive(:current_appearance)
.and_return(nil)
expect(helper.brand_title).to eq('GitLab Community Edition')
end
end
end
...@@ -63,4 +63,19 @@ describe Appearance do ...@@ -63,4 +63,19 @@ describe Appearance do
%i(logo header_logo favicon).each do |logo_type| %i(logo header_logo favicon).each do |logo_type|
it_behaves_like 'logo paths', logo_type it_behaves_like 'logo paths', logo_type
end end
describe 'validations' do
let(:triplet) { '#000' }
let(:hex) { '#AABBCC' }
it { is_expected.to allow_value(nil).for(:message_background_color) }
it { is_expected.to allow_value(triplet).for(:message_background_color) }
it { is_expected.to allow_value(hex).for(:message_background_color) }
it { is_expected.not_to allow_value('000').for(:message_background_color) }
it { is_expected.to allow_value(nil).for(:message_font_color) }
it { is_expected.to allow_value(triplet).for(:message_font_color) }
it { is_expected.to allow_value(hex).for(:message_font_color) }
it { is_expected.not_to allow_value('000').for(:message_font_color) }
end
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