Commit b783387a authored by Douwe Maan's avatar Douwe Maan

Merge branch '2501-trial-and-license-purchases-inside-gitlab-ee' into 'master'

Trial and license purchases inside GitLab EE

Closes #2500

See merge request !2266
parents 1f1a183a 0a42967a
import Cookies from 'js-cookie';
export default class EETrialBanner {
constructor($trialBanner) {
this.COOKIE_KEY = 'show_ee_trial_banner';
this.$trialBanner = $trialBanner;
this.$mainNavbar = this.$trialBanner.siblings('.js-navbar-gitlab');
this.$secondaryNavbar = this.$mainNavbar.siblings('.js-page-with-sidebar');
this.licenseExpiresOn = new Date(this.$trialBanner.data('license-expiry'));
}
init() {
// Wait for navbars to render before querying
this.setCookies();
this.$trialBanner.on('close.bs.alert', e => this.handleTrialBannerDismiss(e));
}
/**
* Trial Expiring/Expired Banner has two stages;
* 1. Show banner when user enters last 7 days of trial
* 2. Show banner again when last 7 days are over and license has expired
*
* Stage 1:
* Banner is showed when `trial_license_message` is sent by backend
* for the first time (in `app/views/layouts/header/_default.html.haml`).
* Here, we perform following steps;
*
* 1. Set cookie `show_ee_trial_banner` with expiry same as license
* 2. Set cookie value to `true`
* 3. Show banner using `toggleBanner(true)`
*
* At this stage, if user dismisses banner, we set cookie value to `false`
* and everytime page is initialized, we check for cookie existence as
* well as its value, and decide show/hide status of banner
*
* Stage 2:
* At this point, Cookie we had set earlier will be expired and
* backend will now send updated message in `trial_license_message`.
* Here, we perform following steps;
*
* 1. Check if cookie is defined (it'll not be defined as it is expired now)
* 2. If cookie is gone, we re-set `show_ee_trial_banner` cookie but with
* expiry of 20 years
* 3. Set cookie value to `true`
* 4. Show banner using `toggleBanner(true)`, which now has updated message
*
* At this stage, if user dismisses banner, we set cookie value to `false`
* and our existing logic of show/hide banner based on cookie value continues
* to work. And since, cookie is set to expire after 20 years, user won't be
* seeing banner again.
*/
setCookies() {
const today = new Date();
// Check if Cookie is defined
if (!Cookies.get(this.COOKIE_KEY)) {
// Cookie was not defined, let's define with default value
// Check if License is yet to expire
if (today < this.licenseExpiresOn) {
// License has not expired yet, we show initial banner of 7 days
// with cookie set to validity same as license expiry
Cookies.set(this.COOKIE_KEY, 'true', { expires: this.licenseExpiresOn });
} else {
// License is already expired so we show final Banner with cookie set to 20 years validity.
Cookies.set(this.COOKIE_KEY, 'true', { expires: 7300 });
}
this.toggleBanner(true);
} else {
// Cookie was defined, let's read value and show/hide banner
this.toggleBanner(Cookies.get(this.COOKIE_KEY) === 'true');
}
}
toggleMainNavbarMargin(state) {
if (this.$mainNavbar.length) {
this.$mainNavbar.toggleClass('has-trial-banner', state);
}
}
toggleSecondaryNavbarMargin(state) {
if (this.$secondaryNavbar.length) {
this.$secondaryNavbar.toggleClass('has-trial-banner', state);
}
}
toggleBanner(state) {
this.$trialBanner.toggleClass('hidden', !state);
this.toggleMainNavbarMargin(state);
this.toggleSecondaryNavbarMargin(state);
}
handleTrialBannerDismiss() {
this.toggleMainNavbarMargin(false);
this.toggleSecondaryNavbarMargin(false);
if (Cookies.get(this.COOKIE_KEY)) {
Cookies.set(this.COOKIE_KEY, 'false');
}
}
}
import EETrialBanner from './ee_trial_banner';
$(() => {
const $trialBanner = $('.js-gitlab-ee-license-banner');
if ($trialBanner.length) {
const eeTrialBanner = new EETrialBanner($trialBanner);
eeTrialBanner.init();
}
});
...@@ -150,6 +150,7 @@ import './syntax_highlight'; ...@@ -150,6 +150,7 @@ import './syntax_highlight';
import './admin_email_select'; import './admin_email_select';
import './application_settings'; import './application_settings';
import './approvals'; import './approvals';
import './ee_trial_banner';
import './ldap_groups_select'; import './ldap_groups_select';
import './path_locks'; import './path_locks';
import './weight_select'; import './weight_select';
...@@ -374,4 +375,9 @@ $(function () { ...@@ -374,4 +375,9 @@ $(function () {
event.preventDefault(); event.preventDefault();
gl.utils.visitUrl(`${action}${$(this).serialize()}`); gl.utils.visitUrl(`${action}${$(this).serialize()}`);
}); });
/**
* EE specific scripts
*/
$('#modal-upload-trial-license').modal('show');
}); });
...@@ -48,3 +48,23 @@ ...@@ -48,3 +48,23 @@
font-size: 14px; font-size: 14px;
} }
} }
/* EE-specific Styles */
@media (min-width: $screen-md-min) {
.blank-state-parent-container.has-start-trial-container {
display: flex;
}
}
.section-ee-trial {
.section-body {
display: flex;
align-items: center;
justify-content: center;
.blank-state {
padding: 20px;
text-align: center;
}
}
}
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
.prepend-top-0 { margin-top: 0; } .prepend-top-0 { margin-top: 0; }
.prepend-top-5 { margin-top: 5px; } .prepend-top-5 { margin-top: 5px; }
.prepend-top-10 { margin-top: 10px; } .prepend-top-10 { margin-top: 10px; }
.prepend-top-15 { margin-top: 15px; }
.prepend-top-default { margin-top: $gl-padding !important; } .prepend-top-default { margin-top: $gl-padding !important; }
.prepend-top-20 { margin-top: 20px; } .prepend-top-20 { margin-top: 20px; }
.prepend-left-5 { margin-left: 5px; } .prepend-left-5 { margin-left: 5px; }
......
/* EE Trial Specific Stylesheet */
/* Main Nav Overrides */
.navbar-gitlab.has-trial-banner {
top: 52px;
}
/* Secondary Nav Overrides - Old Nav */
.page-with-sidebar.has-trial-banner {
margin-top: 102px;
}
/* Sidebar Nav Overrides - New Nav */
.page-with-sidebar.has-trial-banner .nav-sidebar {
top: 102px;
}
/* Trial Banner */
.gitlab-ee-license-banner.alert {
display: flex;
align-content: center;
justify-content: center;
padding: 15px 35px;
position: fixed;
top: 0;
width: 100%;
background: $red-400;
color: $white-light;
text-align: center;
z-index: 400;
@media (max-width: $screen-md-min) {
padding: 10px 35px;
}
.close {
color: $white-dark;
opacity: 0.7;
&:hover {
color: $black;
}
}
p a {
text-decoration: none;
font-weight: bold;
&:hover {
color: $white-dark;
text-decoration: underline;
}
}
.close {
position: absolute;
top: 15%;
right: 10px;
}
}
/* Trial License Install Modal */
.modal-upload-trial-license {
.modal-body-contents {
.trial-activated-graphic {
display: flex;
justify-content: center;
svg {
width: 90px;
height: 90px;
}
}
.confirmation-title,
.confirmation-desc {
text-align: center;
}
.confirmation-desc {
max-width: 80%;
margin: auto;
}
.confirmation-desc {
color: $gl-text-color-secondary;
}
.trial-license-preview {
max-height: 300px;
word-wrap: break-word;
word-break: break-all;
overflow-y: auto;
}
}
}
/* Pure CSS License Key Preview Toggle */
.modal-upload-trial-license {
.trial-license-preview,
.show-license,
.hide-license:target {
display: none;
}
.hide-license,
.hide-license:target + .show-license,
.hide-license:target ~ .trial-license-preview {
display: block;
}
.show-license,
.hide-license {
margin-top: 10px;
text-align: center;
font-size: 18px;
color: $gl-gray-light;
&:hover {
text-decoration: none;
color: $gl-gray-dark;
}
}
}
.blank-state-container {
margin-top: 35px;
.trial-description {
color: $gl-gray-light;
}
}
class Admin::LicensesController < Admin::ApplicationController class Admin::LicensesController < Admin::ApplicationController
before_action :license, only: [:show, :download, :destroy] before_action :license, only: [:show, :download, :destroy]
before_action :require_license, only: [:show, :download, :destroy] before_action :require_license, only: [:download, :destroy]
respond_to :html respond_to :html
def show def show
@previous_licenses = License.previous if @license.blank?
render :missing
else
@previous_licenses = License.previous
end
end end
def download def download
...@@ -13,7 +17,7 @@ class Admin::LicensesController < Admin::ApplicationController ...@@ -13,7 +17,7 @@ class Admin::LicensesController < Admin::ApplicationController
end end
def new def new
@license = License.new build_license
end end
def create def create
...@@ -62,6 +66,10 @@ class Admin::LicensesController < Admin::ApplicationController ...@@ -62,6 +66,10 @@ class Admin::LicensesController < Admin::ApplicationController
redirect_to new_admin_license_path redirect_to new_admin_license_path
end end
def build_license
@license ||= License.new(data: params[:trial_key])
end
def license_params def license_params
license_params = params.require(:license).permit(:data_file, :data) license_params = params.require(:license).permit(:data_file, :data)
license_params.delete(:data) if license_params[:data_file] license_params.delete(:data) if license_params[:data_file]
......
...@@ -12,66 +12,30 @@ module LicenseHelper ...@@ -12,66 +12,30 @@ module LicenseHelper
HistoricalData.max_historical_user_count HistoricalData.max_historical_user_count
end end
# in_html is set to false from an initializer, which shouldn't try to render def license_message(signed_in: signed_in?, is_admin: current_user&.admin?)
# HTML links. return unless current_license
#
def license_message(signed_in: signed_in?, is_admin: (current_user && current_user.admin?), in_html: true)
@license_message =
if License.current
yes_license_message(signed_in, is_admin)
else
no_license_message(is_admin, in_html: in_html)
end
end
private
def no_license_message(is_admin, in_html: true)
upload_a_license =
if in_html
link_to('Upload a license', new_admin_license_path)
else
'Upload a license'
end
message = []
message << 'No GitLab Enterprise Edition license has been provided yet.'
message << 'Pushing code and creation of issues and merge requests has been disabled.'
message <<
if is_admin
"#{upload_a_license} in the admin area to activate this functionality."
else
'Ask an admin to upload a license to activate this functionality.'
end
if in_html
content_tag(:p, message.join(' ').html_safe)
else
message.join(' ')
end
end
def yes_license_message(signed_in, is_admin)
license = License.current
return unless signed_in return unless signed_in
return unless (is_admin && current_license.notify_admins?) || current_license.notify_users?
return unless (is_admin && license.notify_admins?) || license.notify_users? is_trial = current_license.trial?
message = ["Your Enterprise Edition #{'trial ' if is_trial}license"]
message = [] message << if current_license.expired?
"expired on #{current_license.expires_at}."
else
"will expire in #{pluralize(current_license.remaining_days, 'day')}."
end
message << 'The GitLab Enterprise Edition license' message << link_to('Buy now!', Gitlab::SUBSCRIPTIONS_PLANS_URL, target: '_blank') if is_trial
message << (license.expired? ? 'expired' : 'will expire')
message << "on #{license.expires_at}."
if license.expired? && license.will_block_changes? if current_license.expired? && current_license.will_block_changes?
message << 'Pushing code and creation of issues and merge requests' message << 'Pushing code and creation of issues and merge requests'
message << message <<
if license.block_changes? if current_license.block_changes?
'has been disabled.' 'has been disabled.'
else else
"will be disabled on #{license.block_changes_at}." "will be disabled on #{current_license.block_changes_at}."
end end
message << message <<
...@@ -82,11 +46,25 @@ module LicenseHelper ...@@ -82,11 +46,25 @@ module LicenseHelper
end end
message << 'to' message << 'to'
message << (license.block_changes? ? 'restore' : 'ensure uninterrupted') message << (current_license.block_changes? ? 'restore' : 'ensure uninterrupted')
message << 'service.' message << 'service.'
end end
message.join(' ') message.join(' ').html_safe
end
def current_license
return @current_license if defined?(@current_license)
@current_license = License.current
end
def new_trial_url
return_to_url = URI.encode(Gitlab.config.gitlab.url)
uri = URI.parse(Gitlab::SUBSCRIPTIONS_URL)
uri.path = '/trials/new'
uri.query = "return_to=#{return_to_url}"
uri.to_s
end end
extend self extend self
......
...@@ -185,7 +185,7 @@ class License < ActiveRecord::Base ...@@ -185,7 +185,7 @@ class License < ActiveRecord::Base
end end
end end
delegate :feature_available?, to: :current, allow_nil: true delegate :block_changes?, :feature_available?, to: :current, allow_nil: true
def reset_current def reset_current
RequestStore.delete(:current_license) RequestStore.delete(:current_license)
...@@ -198,10 +198,6 @@ class License < ActiveRecord::Base ...@@ -198,10 +198,6 @@ class License < ActiveRecord::Base
features[feature].to_i > 0 features[feature].to_i > 0
end end
def block_changes?
!current || current.block_changes?
end
def load_license def load_license
license = self.last license = self.last
...@@ -272,6 +268,8 @@ class License < ActiveRecord::Base ...@@ -272,6 +268,8 @@ class License < ActiveRecord::Base
end end
def feature_available?(code) def feature_available?(code)
return false if trial? && expired?
feature = FEATURE_CODES.fetch(code) feature = FEATURE_CODES.fetch(code)
add_ons[feature].to_i > 0 add_ons[feature].to_i > 0
end end
...@@ -302,6 +300,16 @@ class License < ActiveRecord::Base ...@@ -302,6 +300,16 @@ class License < ActiveRecord::Base
restricted_attr(:trial) restricted_attr(:trial)
end end
def active?
!expired?
end
def remaining_days
return 0 if expired?
(expires_at - Date.today).to_i
end
private private
def restricted_attr(name, default = nil) def restricted_attr(name, default = nil)
......
= link_to 'Buy License', Gitlab::SUBSCRIPTIONS_PLANS_URL, target: '_blank', rel: 'noopener noreferrer nofollow', class: "btn btn-new btn-inverted pull-right btn-buy-license"
= link_to 'Upload New License', new_admin_license_path, class: "btn pull-right btn-upload-license append-right-10"
- license_key = params[:trial_key]
#modal-upload-trial-license.modal-upload-trial-license.modal.fade.in{ tabindex: -1, role: 'dialog' }
.modal-dialog
.modal-content
.modal-body
%button.close{ type: 'button', data: { dismiss: 'modal' }, aria: { label: 'Close' } }
%span &times;
.modal-body-contents
.trial-activated-graphic.prepend-top-15
= custom_icon('ee_trial_license_activated', size: 100)
%h3.confirmation-title
Your trial license was issued!
%p.confirmation-desc.lead
Your trial license was issued and activated. Install it
to enjoy GitLab Enterprise Edition Premium for 30 days.
= form_for License.new, url: admin_license_path, html: { multipart: true, class: 'form-horizontal fieldset-form' } do |f|
= f.hidden_field :data, value: license_key
%a#hide-license.hide-license{ href: '#hide-license' }
Show license key
= icon('chevron-down')
%a#show-license.show-license{ href: '#show-license' }
Hide license key
= icon('chevron-up')
.well.trial-license-preview.prepend-top-15
= license_key
.modal-footer.form-actions
%button.btn.btn-default{ type: 'button', data: { dismiss: 'modal' } } Cancel
= f.submit 'Install license', class: 'btn btn-primary'
- page_title "License"
%h3.page-title
Your License
= render "upload_buy_license"
- if params[:trial_key].present?
= render "upload_trial_license"
%hr
.container.blank-state-container
.text-center
= custom_icon("missing_license")
%h4 You do not have a license.
%p.trial-description You can start a free trial of GitLab Enterprise Edition without any obligation or payment details.
= link_to 'Start free trial', new_trial_url, class: "btn btn-new btn-start-trial prepend-top-10"
- page_title "License" - page_title "License"
%h3.page-title %h3.page-title
Your License Your License
= link_to 'Upload New License', new_admin_license_path, class: "btn btn-new pull-right" - if current_license.trial?
= render "upload_buy_license"
- else
= link_to 'Upload New License', new_admin_license_path, class: "btn btn-new pull-right"
%hr %hr
...@@ -32,11 +35,12 @@ ...@@ -32,11 +35,12 @@
Expired: Expired:
- else - else
Expires: Expires:
%strong - if @license.will_expire? && @license.active?
- if @license.will_expire? %strong= time_ago_with_tooltip(@license.expires_at)
= time_ago_with_tooltip @license.expires_at - if @license.trial?
- else %span Free trial will expire in #{pluralize(@license.remaining_days, 'day')}
Never - else
%strong Never
- if @license.expired? - if @license.expired?
%span.label.label-danger.pull-right %span.label.label-danger.pull-right
......
.blank-state
.blank-state-icon
= custom_icon("ee_trial", size: 50)
.blank-state-body
%h3.blank-state-title
Unlock more features with GitLab Enterprise Edition
%p.blank-state-text
GitLab is free to use.
Many features for larger teams are part of
our paid
= succeed "." do
= link_to "Enterprise Edition products", "https://about.gitlab.com/products/", target: "_blank", rel: "noopener noreferrer nofollow"
You can try these out for free without
any obligation or payment details.
= link_to new_trial_url, class: "btn btn-new" do
Start free trial
.row.blank-state-parent-container - admin_without_ee_license = !current_license && current_user.admin?
.section-container.section-welcome{ class: "#{ 'section-admin-welcome' if current_user.admin? }" }
.row.blank-state-parent-container{ class: ('has-start-trial-container' if admin_without_ee_license) }
.section-container.section-welcome{ class: ('col-md-6' if admin_without_ee_license) }
.container.section-body .container.section-body
.blank-state.blank-state-welcome .blank-state.blank-state-welcome
%h2.blank-state-welcome-title %h2.blank-state-welcome-title
...@@ -10,3 +12,7 @@ ...@@ -10,3 +12,7 @@
= render "blank_state_admin_welcome" = render "blank_state_admin_welcome"
- else - else
= render "blank_state_welcome" = render "blank_state_welcome"
- if admin_without_ee_license
.col-md-6.section-container.section-ee-trial
.container.section-body
= render "blank_state_ee_trial"
- if license_message.present?
.broadcast-message
= icon('bullhorn')
= license_message
- BroadcastMessage.current&.each do |message| - BroadcastMessage.current&.each do |message|
= broadcast_message(message) = broadcast_message(message)
.page-with-sidebar{ class: page_with_sidebar_class } .page-with-sidebar.js-page-with-sidebar{ class: [('page-with-new-sidebar' if @new_sidebar), page_gutter_class] }
- if show_new_nav? - if show_new_nav?
- if defined?(nav) && nav - if defined?(nav) && nav
= render "layouts/nav/#{nav}" = render "layouts/nav/#{nav}"
......
%header.navbar.navbar-gitlab{ class: nav_header_class } = render "layouts/header/ee_license_banner"
%header.navbar.navbar-gitlab.js-navbar-gitlab{ class: nav_header_class }
.navbar-border .navbar-border
%a.sr-only.gl-accessibility{ href: "#content-body", tabindex: "1" } Skip to content %a.sr-only.gl-accessibility{ href: "#content-body", tabindex: "1" } Skip to content
.container-fluid .container-fluid
......
- if license_message.present?
.alert.alert-dismissible.gitlab-ee-license-banner.hidden.js-gitlab-ee-license-banner{ role: 'alert', data: { license_expiry: current_license.expires_at } }
-# Show dismiss button only when license expiry is about trial license
- if current_license.trial?
%button.close{ type: 'button', 'data-dismiss' => 'alert', 'aria-label' => 'Dismiss banner' }
= icon('times', 'aria-hidden' => 'true')
%p
= license_message
%header.navbar.navbar-gitlab.navbar-gitlab-new{ class: nav_header_class } = render "layouts/header/ee_license_banner"
%header.navbar.navbar-gitlab.navbar-gitlab-new.js-navbar-gitlab{ class: nav_header_class }
%a.sr-only.gl-accessibility{ href: "#content-body", tabindex: "1" } Skip to content %a.sr-only.gl-accessibility{ href: "#content-body", tabindex: "1" } Skip to content
.container-fluid .container-fluid
.header-content .header-content
......
<svg xmlns="http://www.w3.org/2000/svg" width="330" height="132" viewBox="0 0 330 132">
<g fill="none" fill-rule="evenodd">
<path fill="#000" fill-opacity=".03" d="M174.12 42c-.08 1-.12 2-.12 3 0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3-1.53 19.03-17.46 34-36.88 34s-35.35-14.97-36.88-34z"/>
<path fill="#EEE" fill-rule="nonzero" d="M211 78c-21.54 0-39-17.46-39-39s17.46-39 39-39 39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S230.33 4 211 4s-35 15.67-35 35 15.67 35 35 35z"/>
<g fill-rule="nonzero">
<path fill="#FEE1D3" d="M211.5 51c-6.42 0-12.26-2.84-17.43-8.4-1.32-1.43-1.43-3.58-.27-5.13C199 30.57 204.92 27 211.5 27s12.5 3.56 17.7 10.47c1.16 1.55 1.05 3.7-.27 5.12-5.17 5.53-11 8.4-17.43 8.4zm0-4c5.25 0 10.05-2.34 14.5-7.13-4.5-5.98-9.3-8.87-14.5-8.87-5.2 0-10 2.9-14.5 8.87 4.45 4.8 9.25 7.13 14.5 7.13z"/>
<path fill="#FC6D26" d="M211 47c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm0-4c2.2 0 4-1.8 4-4s-1.8-4-4-4-4 1.8-4 4 1.8 4 4 4zm0-1c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3z"/>
</g>
<path fill="#000" fill-opacity=".03" d="M88.12 83c-.08 1-.12 2-.12 3 0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3-1.53 19.03-17.46 34-36.88 34s-35.35-14.97-36.88-34z"/>
<path fill="#EEE" fill-rule="nonzero" d="M125 119c-21.54 0-39-17.46-39-39s17.46-39 39-39 39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35s-15.67-35-35-35-35 15.67-35 35 15.67 35 35 35z"/>
<path fill="#FEE1D3" fill-rule="nonzero" d="M116 86.34c2.33.83 4 3.05 4 5.66 0 3.3-2.7 6-6 6s-6-2.7-6-6c0-2.6 1.67-4.83 4-5.66V72h4v14.34zM128 66c5.52 0 10 4.48 10 10v12h-4V76c0-3.3-2.7-6-6-6v1.83c0 .55-.45 1-1 1-.24 0-.47-.1-.65-.24l-4.46-3.87c-.46-.36-.5-1-.15-1.4.03-.05.07-.1.1-.12l4.47-3.82c.42-.35 1.05-.3 1.4.1.16.2.25.43.25.66V66zm-14 28c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z"/>
<path fill="#FC6D26" fill-rule="nonzero" d="M114 74c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6zm0-4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm22 28c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6zm0-4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z"/>
<path fill="#000" fill-opacity=".03" d="M2.12 52C2.04 53 2 54 2 55c0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3C74.35 71.03 58.42 86 39 86S3.65 71.03 2.12 52z"/>
<path fill="#EEE" fill-rule="nonzero" d="M39 88C17.46 88 0 70.54 0 49s17.46-39 39-39 39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 14 39 14 4 29.67 4 49s15.67 35 35 35z"/>
<path fill="#6B4FBB" fill-rule="nonzero" d="M48 41h-4c0-2.76-2.24-5-5-5s-5 2.24-5 5h-4c0-4.97 4.03-9 9-9s9 4.03 9 9zm-18 0h4v3h-4v-3zm14 0h4v3h-4v-3z"/>
<path fill="#E1DBF2" fill-rule="nonzero" d="M30 47c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h18c.55 0 1-.45 1-1V48c0-.55-.45-1-1-1H30zm0-4h18c2.76 0 5 2.24 5 5v12c0 2.76-2.24 5-5 5H30c-2.76 0-5-2.24-5-5V48c0-2.76 2.24-5 5-5z"/>
<path fill="#6B4FBB" d="M38 53.73c-.6-.34-1-1-1-1.73 0-1.1.9-2 2-2s2 .9 2 2c0 .74-.4 1.4-1 1.73V55c0 .55-.45 1-1 1s-1-.45-1-1v-1.27z"/>
<path fill="#000" fill-opacity=".03" d="M254.12 92c-.08 1-.12 2-.12 3 0 20.43 16.57 37 37 37s37-16.57 37-37c0-1-.04-2-.12-3-1.53 19.03-17.46 34-36.88 34s-35.35-14.97-36.88-34z"/>
<path fill="#EEE" fill-rule="nonzero" d="M291 128c-21.54 0-39-17.46-39-39s17.46-39 39-39 39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35s-15.67-35-35-35-35 15.67-35 35 15.67 35 35 35z"/>
<path fill="#6B4BBE" fill-rule="nonzero" d="M292 78c5.52 0 10 4.48 10 10 0 2.28-.76 4.43-2.14 6.18-1.03 1.3-.8 3.2.5 4.22 1.3 1.02 3.2.8 4.2-.5 2.22-2.8 3.44-6.26 3.44-9.9 0-8.84-7.16-16-16-16v-3.13c0-.2-.06-.4-.17-.56-.3-.42-.93-.54-1.38-.23l-9.2 6.13c-.1.06-.2.16-.28.27-.3.45-.18 1.08.28 1.38l9.2 6.13c.16.1.35.17.55.17.55 0 1-.45 1-1V78z"/>
<path fill="#E1DBF2" fill-rule="nonzero" d="M290 100c-5.52 0-10-4.48-10-10 0-2.25.74-4.38 2.1-6.12 1-1.3.77-3.2-.54-4.2-1.3-1.02-3.2-.78-4.2.53C275.18 83 274 86.4 274 90c0 8.84 7.16 16 16 16v3.13c0 .55.45 1 1 1 .2 0 .4-.06.55-.17l9.2-6.13c.46-.3.6-.93.28-1.38-.07-.1-.17-.2-.28-.28l-9.2-6.13c-.45-.3-1.08-.2-1.38.27-.1.2-.17.4-.17.6v3.1z"/>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="78" height="82" viewBox="0 0 78 82"><g fill="none" fill-rule="evenodd"><path fill="#F9F9F9" d="M2.12 42c-.08.99-.12 1.99-.12 3 0 20.435 16.565 37 37 37s37-16.565 37-37c0-1.01-.04-2.01-.12-3C74.353 61.032 58.425 76 39 76 19.575 76 3.647 61.032 2.12 42z"/><path fill="#EEE" fill-rule="nonzero" d="M39 78C17.46 78 0 60.54 0 39S17.46 0 39 0s39 17.46 39 39-17.46 39-39 39zm0-4c19.33 0 35-15.67 35-35S58.33 4 39 4 4 19.67 4 39s15.67 35 35 35z"/><path fill="#6B4FBB" d="M36.6 44.687l-7.314-6.82a3 3 0 1 0-4.092 4.388l9.508 8.866a2.99 2.99 0 0 0 2.15.804 2.99 2.99 0 0 0 2.09-.952l15.686-16.821a3 3 0 1 0-4.388-4.092L36.6 44.687z"/></g></svg>
\ No newline at end of file
This diff is collapsed.
...@@ -9,7 +9,7 @@ end ...@@ -9,7 +9,7 @@ end
# Needed to run migration # Needed to run migration
if ActiveRecord::Base.connected? && ActiveRecord::Base.connection.table_exists?('licenses') if ActiveRecord::Base.connected? && ActiveRecord::Base.connection.table_exists?('licenses')
message = LicenseHelper.license_message(signed_in: true, is_admin: true, in_html: false) message = LicenseHelper.license_message(signed_in: true, is_admin: true, in_html: false)
if message.present? if ::License.block_changes? && message.present?
warn "WARNING: #{message}" warn "WARNING: #{message}"
end end
end end
...@@ -8,12 +8,6 @@ Feature: Admin license ...@@ -8,12 +8,6 @@ Feature: Admin license
And I visit admin license page And I visit admin license page
Then I should see to whom the license is licensed Then I should see to whom the license is licensed
Scenario: Viewing license when there is none
Given There is no license
And I visit admin license page
Then I should see a warning telling me there is no license
And I should be redirected to the license upload page
Scenario: Viewing expired license Scenario: Viewing expired license
Given there is a license Given there is a license
And the current license is expired And the current license is expired
......
...@@ -14,10 +14,6 @@ class Spinach::Features::AdminLicense < Spinach::FeatureSteps ...@@ -14,10 +14,6 @@ class Spinach::Features::AdminLicense < Spinach::FeatureSteps
License.destroy_all License.destroy_all
end end
step 'I should see a warning telling me there is no license' do
expect(page).to have_content "No GitLab Enterprise Edition license has been provided yet."
end
step 'I should be redirected to the license upload page' do step 'I should be redirected to the license upload page' do
expect(current_path).to eq(new_admin_license_path) expect(current_path).to eq(new_admin_license_path)
end end
...@@ -27,7 +23,7 @@ class Spinach::Features::AdminLicense < Spinach::FeatureSteps ...@@ -27,7 +23,7 @@ class Spinach::Features::AdminLicense < Spinach::FeatureSteps
end end
step 'I should see a warning telling me the license has expired' do step 'I should see a warning telling me the license has expired' do
expect(page).to have_content "The GitLab Enterprise Edition license expired" expect(page).to have_content "Your Enterprise Edition license expired"
end end
step 'the current license blocks changes' do step 'the current license blocks changes' do
......
...@@ -3,6 +3,8 @@ require_dependency 'gitlab/git' ...@@ -3,6 +3,8 @@ require_dependency 'gitlab/git'
module Gitlab module Gitlab
SUBDOMAIN_REGEX = %r{\Ahttps://[a-z0-9]+\.gitlab\.com\z} SUBDOMAIN_REGEX = %r{\Ahttps://[a-z0-9]+\.gitlab\.com\z}
COM_URL = 'https://gitlab.com'.freeze COM_URL = 'https://gitlab.com'.freeze
SUBSCRIPTIONS_URL = 'https://customers.gitlab.com'.freeze
SUBSCRIPTIONS_PLANS_URL = "#{SUBSCRIPTIONS_URL}/plans".freeze
def self.com? def self.com?
# Check `gl_subdomain?` as well to keep parity with gitlab.com # Check `gl_subdomain?` as well to keep parity with gitlab.com
......
...@@ -15,4 +15,26 @@ describe Admin::LicensesController do ...@@ -15,4 +15,26 @@ describe Admin::LicensesController do
expect(flash[:alert]).to include 'Please enter or upload a license.' expect(flash[:alert]).to include 'Please enter or upload a license.'
end end
end end
describe 'GET show' do
context 'with an existent license' do
it 'renders the license details' do
allow(License).to receive(:current).and_return(create(:license))
get :show
expect(response).to render_template(:show)
end
end
context 'without a license' do
it 'renders missing license page' do
allow(License).to receive(:current).and_return(nil)
get :show
expect(response).to render_template(:missing)
end
end
end
end end
...@@ -2,10 +2,26 @@ FactoryGirl.define do ...@@ -2,10 +2,26 @@ FactoryGirl.define do
factory :gitlab_license, class: "Gitlab::License" do factory :gitlab_license, class: "Gitlab::License" do
skip_create skip_create
trait :trial do
block_changes_at nil
restrictions do
{ trial: true }
end
end
trait :expired do
expires_at { 3.weeks.ago.to_date }
end
transient do
plan License::STARTER_PLAN
end
starts_at { Date.today - 1.month } starts_at { Date.today - 1.month }
expires_at { Date.today + 11.months } expires_at { Date.today + 11.months }
notify_users_at { |l| l.expires_at } block_changes_at { expires_at + 2.weeks }
notify_admins_at { |l| l.expires_at } notify_users_at { expires_at }
notify_admins_at { expires_at }
licensee do licensee do
{ "Name" => generate(:name) } { "Name" => generate(:name) }
...@@ -20,27 +36,25 @@ FactoryGirl.define do ...@@ -20,27 +36,25 @@ FactoryGirl.define do
plan: plan plan: plan
} }
end end
transient do
plan License::STARTER_PLAN
end
trait :trial do
restrictions do
{ trial: true }
end
end
end end
factory :license do factory :license do
transient do transient do
plan nil plan nil
expired false
trial false
end end
data { build(:gitlab_license, plan: plan).export } data do
end attrs = [:gitlab_license]
attrs << :trial if trial
attrs << :expired if expired
attrs << { plan: plan }
build(*attrs).export
end
factory :trial_license, class: License do # Disable validations when creating an expired license key
data { build(:gitlab_license, :trial).export } to_create {|instance| instance.save(validate: !expired) }
end end
end end
...@@ -26,5 +26,42 @@ feature "License Admin" do ...@@ -26,5 +26,42 @@ feature "License Admin" do
end end
end end
end end
context 'with an expired trial license' do
let!(:license) { create(:license, trial: true, expired: true) }
it 'does not mention blocking of changes' do
visit admin_license_path
page.within '.gitlab-ee-license-banner' do
expect(page).to have_content('Your Enterprise Edition trial license expired on')
expect(page).not_to have_content('Pushing code and creation of issues and merge requests has been disabled')
end
end
end
context 'when license key is provided in the query string' do
let(:license) { build(:license, data: build(:gitlab_license, restrictions: { active_user_count: 2000 }).export) }
before do
License.destroy_all
end
it 'shows the modal to install the license' do
visit admin_license_path(trial_key: license.data)
page.within '#modal-upload-trial-license' do
expect(page).to have_content('Your trial license was issued')
expect(page).to have_button('Install license')
end
end
it 'can install the license' do
visit admin_license_path(trial_key: license.data)
click_button 'Install license'
expect(page).to have_content('The license was successfully uploaded and is now active')
end
end
end end
end end
...@@ -11,18 +11,14 @@ describe LicenseHelper do ...@@ -11,18 +11,14 @@ describe LicenseHelper do
let(:is_admin) { true } let(:is_admin) { true }
it 'displays correct error message for admin user' do it 'displays correct error message for admin user' do
admin_msg = '<p>No GitLab Enterprise Edition license has been provided yet. Pushing code and creation of issues and merge requests has been disabled. <a href="/admin/license/new">Upload a license</a> in the admin area to activate this functionality.</p>' expect(license_message(signed_in: true, is_admin: is_admin)).to be_blank
expect(license_message(signed_in: true, is_admin: is_admin)).to eq(admin_msg)
end end
end end
context 'normal user' do context 'normal user' do
let(:is_admin) { false } let(:is_admin) { false }
it 'displays correct error message for normal user' do it 'displays correct error message for normal user' do
user_msg = '<p>No GitLab Enterprise Edition license has been provided yet. Pushing code and creation of issues and merge requests has been disabled. Ask an admin to upload a license to activate this functionality.</p>' expect(license_message(signed_in: true, is_admin: is_admin)).to be_blank
expect(license_message(signed_in: true, is_admin: is_admin)).to eq(user_msg)
end end
end end
end end
......
...@@ -302,8 +302,24 @@ describe License do ...@@ -302,8 +302,24 @@ describe License do
allow(described_class).to receive(:current).and_return(nil) allow(described_class).to receive(:current).and_return(nil)
end end
it "returns true" do it "returns false" do
expect(described_class.block_changes?).to be_truthy expect(described_class.block_changes?).to be_falsey
end
end
context 'with an expired trial license' do
let!(:license) { create(:license, trial: true) }
it 'returns false' do
expect(described_class.block_changes?).to be_falsey
end
end
context 'with an expired normal license' do
let!(:license) { create(:license, expired: true) }
it 'returns true' do
expect(described_class.block_changes?).to eq(true)
end end
end end
...@@ -451,6 +467,19 @@ describe License do ...@@ -451,6 +467,19 @@ describe License do
expect { license.feature_available?(:invalid) }.to raise_error(KeyError) expect { license.feature_available?(:invalid) }.to raise_error(KeyError)
end end
context 'with an expired trial license' do
before(:all) do
described_class.destroy_all
create(:license, trial: true, expired: true)
end
::License::FEATURE_CODES.keys do |feature_code|
it "returns false for #{feature_code}" do
expect(license.feature_available?(feature_code)).to eq(false)
end
end
end
end end
def build_license_with_add_ons(add_ons, plan: nil) def build_license_with_add_ons(add_ons, plan: nil)
......
...@@ -781,18 +781,13 @@ describe 'Git HTTP requests' do ...@@ -781,18 +781,13 @@ describe 'Git HTTP requests' do
let(:env) { { user: user.username, password: user.password } } let(:env) { { user: user.username, password: user.password } }
before do before do
project.team << [user, :master]
end
it 'responds with status 403 Forbidden' do
msg = 'No GitLab Enterprise Edition license has been provided yet. Pushing code and creation of issues and merge requests has been disabled. Ask an admin to upload a license to activate this functionality.'
allow(License).to receive(:current).and_return(nil) allow(License).to receive(:current).and_return(nil)
upload(path, env) do |response| project.team << [user, :master]
expect(response).to have_http_status(:forbidden)
expect(response.body).to eq(msg)
end
end end
it_behaves_like 'pulls are allowed'
it_behaves_like 'pushes are allowed'
end end
end end
......
...@@ -6,7 +6,7 @@ describe HistoricalDataWorker do ...@@ -6,7 +6,7 @@ describe HistoricalDataWorker do
describe '#perform' do describe '#perform' do
context 'with a trial license' do context 'with a trial license' do
before do before do
FactoryGirl.create(:trial_license) FactoryGirl.create(:license, trial: true)
end end
it 'does not track historical data' do it 'does not track historical data' do
......
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