Commit 236da91d authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'ee/master' into 2302-environment-specific-variables

* ee/master: (25 commits)
  Introduce namespace license checks for Push Rules (EES)
  Use a named subject in `models/ee/board_spec.rb`
  Hide the milestone variable on board when the feature is disabled
  Hide the milestone in the API when the feature is not available
  Don't update milestones on boards if the feature is not available
  Hide editing/creating milestones from the board UI
  Add `issue_board_milestone` feature to license
  Split Projects:Settings::RepositoryController into CE and EE sections
  Refactor Projects::CreateService and specs to make EE-only code clearer
  Fix EE conflicts for "Allow unauthenticated access to the `/api/v4/users` API"
  Introduce namespace license checks for merge request approvers (EES)
  Remove an unnecessary "included do ... end" block in app/models/concerns/approvable.rb
  Raise an error if an unknown feature is passed to stub_licensed_features
  Don't show Issue/MR template Setting if feature not available
  Only set MR description from template when feature available
  Only set issues template from setting if feature available
  Add Issuable Default Template feature to License
  Update CHANGELOG.md for 9.3.4
  Update CHANGELOG-EE.md for 9.3.4-ee
  Hide `Focus mode` on issue boards
  ...
parents f3c65bc7 ddd47d9e
Please view this file on the master branch, on stable branches it's out of date.
## 9.3.4 (2017-07-03)
- No changes.
## 9.3.3 (2017-06-30)
- Add metrics to both remote and non remote mirroring. !2118
......
......@@ -2,6 +2,10 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 9.3.4 (2017-07-03)
- No changes.
## 9.3.3 (2017-06-30)
- Fix shared runners minutes query to update only projects with used allowance.
......
......@@ -141,6 +141,7 @@ $(() => {
modal: ModalStore.store,
store: Store.state,
isFullscreen: false,
focusModeAvailable: gl.utils.convertPermissionToBoolean($boardApp.dataset.focusModeAvailable),
},
watch: {
disabled() {
......@@ -177,6 +178,8 @@ $(() => {
}
},
toggleFocusMode() {
if (!this.focusModeAvailable) { return; }
$(this.$refs.toggleFocusModeButton).tooltip('hide');
issueBoardsContent.classList.toggle('is-focused');
......@@ -206,6 +209,7 @@ $(() => {
aria-label="Toggle focus mode"
title="Toggle focus mode"
ref="toggleFocusModeButton"
v-if="focusModeAvailable"
@click="toggleFocusMode">
<span v-show="isFullscreen">
${collapseIcon}
......
class Admin::PushRulesController < Admin::ApplicationController
before_action :check_push_rules_available!
before_action :push_rule
respond_to :html
......@@ -18,6 +19,10 @@ class Admin::PushRulesController < Admin::ApplicationController
private
def check_push_rules_available!
render_404 unless License.feature_available?(:push_rules)
end
def push_rule_params
params.require(:push_rule).permit(:deny_delete_tag, :delete_branch_regex,
:commit_message_regex, :force_push_regex, :author_email_regex, :member_check,
......
module EE
module Groups
module ApplicationController
def check_group_feature_available!(feature)
render_404 unless group.feature_available?(feature)
end
def method_missing(method_sym, *arguments, &block)
case method_sym.to_s
when /\Acheck_group_(.*)_available!\z/
check_group_feature_available!($1.to_sym)
else
super
end
end
end
end
end
module EE
module Projects
module Settings
module RepositoryController
extend ActiveSupport::Concern
prepended do
before_action :push_rule, only: [:show]
before_action :remote_mirror, only: [:show]
end
private
def push_rule
return unless project.feature_available?(:push_rules)
project.create_push_rule unless project.push_rule
@push_rule = project.push_rule
end
def remote_mirror
@remote_mirror = @project.remote_mirrors.first_or_initialize
end
def acces_levels_options
super.merge(
selected_merge_access_levels: @protected_branch.merge_access_levels.map { |access_level| access_level.user_id || access_level.access_level },
selected_push_access_levels: @protected_branch.push_access_levels.map { |access_level| access_level.user_id || access_level.access_level },
selected_create_access_levels: @protected_tag.create_access_levels.map { |access_level| access_level.user_id || access_level.access_level }
)
end
def load_gon_index
super
gon.push(current_project_id: @project.id) if @project
end
end
end
end
end
class Groups::AnalyticsController < Groups::ApplicationController
before_action :group
before_action :check_group_contribution_analytics_available!
layout 'group'
......
class Groups::ApplicationController < ApplicationController
include RoutableActions
prepend EE::Groups::ApplicationController
layout 'group'
......
......@@ -3,6 +3,7 @@ class Projects::PushRulesController < Projects::ApplicationController
# Authorize
before_action :authorize_admin_project!
before_action :check_push_rules_available!
respond_to :html
......
......@@ -2,23 +2,17 @@ module Projects
module Settings
class RepositoryController < Projects::ApplicationController
before_action :authorize_admin_project!
before_action :remote_mirror, only: [:show]
prepend ::EE::Projects::Settings::RepositoryController
def show
@deploy_keys = DeployKeysPresenter.new(@project, current_user: current_user)
define_protected_refs
project.create_push_rule unless project.push_rule
@push_rule = project.push_rule
end
private
def remote_mirror
@remote_mirror = @project.remote_mirrors.first_or_initialize
end
def define_protected_refs
@protected_branches = @project.protected_branches.order(:name).page(params[:page])
@protected_tags = @project.protected_tags.order(:name).page(params[:page])
......@@ -29,9 +23,6 @@ module Projects
def access_levels_options
{
selected_merge_access_levels: @protected_branch.merge_access_levels.map { |access_level| access_level.user_id || access_level.access_level },
selected_push_access_levels: @protected_branch.push_access_levels.map { |access_level| access_level.user_id || access_level.access_level },
selected_create_access_levels: @protected_tag.create_access_levels.map { |access_level| access_level.user_id || access_level.access_level },
create_access_levels: levels_for_dropdown(ProtectedTag::CreateAccessLevel),
push_access_levels: levels_for_dropdown(ProtectedBranch::PushAccessLevel),
merge_access_levels: levels_for_dropdown(ProtectedBranch::MergeAccessLevel)
......@@ -57,7 +48,6 @@ module Projects
gon.push(protectable_tags_for_dropdown)
gon.push(protectable_branches_for_dropdown)
gon.push(access_levels_options)
gon.push(current_project_id: @project.id) if @project
end
end
end
......
......@@ -62,13 +62,13 @@ class UsersFinder
end
def by_external_identity(users)
return users unless current_user.admin? && params[:extern_uid] && params[:provider]
return users unless current_user&.admin? && params[:extern_uid] && params[:provider]
users.joins(:identities).merge(Identity.with_extern_uid(params[:provider], params[:extern_uid]))
end
def by_external(users)
return users = users.where.not(external: true) unless current_user.admin?
return users = users.where.not(external: true) unless current_user&.admin?
return users unless params[:external]
users.external
......
module BoardsHelper
prepend EE::BoardsHelper
def board_data
board = @board || @boards.first
......
module EE
module BoardsHelper
def board_data
super.merge(focus_mode_available: @project.feature_available?(:issue_board_focus_mode).to_s)
end
end
end
class Board < ActiveRecord::Base
prepend EE::Board
belongs_to :project
belongs_to :milestone
has_many :lists, -> { order(:list_type, :position) }, dependent: :delete_all
......@@ -13,26 +14,4 @@ class Board < ActiveRecord::Base
def closed_list
lists.merge(List.closed).take
end
def milestone
if milestone_id == Milestone::Upcoming.id
Milestone::Upcoming
else
super
end
end
def as_json(options = {})
milestone_attrs = options.fetch(:include, {})
.extract!(:milestone)
.dig(:milestone, :only)
super(options).tap do |json|
if milestone.present? && milestone_attrs.present?
json[:milestone] = milestone_attrs.each_with_object({}) do |attr, json|
json[attr] = milestone.public_send(attr)
end
end
end
end
end
module Approvable
extend ActiveSupport::Concern
included do
def requires_approve?
approvals_required.nonzero?
end
......@@ -25,6 +22,12 @@ module Approvable
approvals_before_merge || target_project.approvals_before_merge
end
def approvals_before_merge
return 0 unless project&.feature_available?(:merge_request_approvers)
super
end
# An MR can potentially be approved by:
# - anyone in the approvers list
# - any other project member with developer access or higher (if there are no approvers
......@@ -155,5 +158,4 @@ module Approvable
approver_groups.find_or_initialize_by(group_id: group_id, target_id: id)
end
end
end
end
module EE
module Board
extend ActiveSupport::Concern
prepended do
belongs_to :milestone
end
def milestone
return nil unless project.feature_available?(:issue_board_milestone)
if milestone_id == ::Milestone::Upcoming.id
::Milestone::Upcoming
else
super
end
end
def as_json(options = {})
milestone_attrs = options.fetch(:include, {})
.extract!(:milestone)
.dig(:milestone, :only)
super(options).tap do |json|
if milestone.present? && milestone_attrs.present?
json[:milestone] = milestone_attrs.each_with_object({}) do |attr, json|
json[attr] = milestone.public_send(attr)
end
end
end
end
end
end
module EE
module MergeRequest
include ::Approvable
def ff_merge_possible?
project.repository.is_ancestor?(target_branch_sha, diff_head_sha)
end
......
......@@ -25,7 +25,7 @@ module EE
belongs_to :mirror_user, foreign_key: 'mirror_user_id', class_name: 'User'
has_one :mirror_data, dependent: :delete, autosave: true, class_name: 'ProjectMirrorData'
has_one :push_rule, dependent: :destroy
has_one :push_rule, ->(project) { project&.feature_available?(:push_rules) ? all : none }, dependent: :destroy
has_one :index_status, dependent: :destroy
has_one :jenkins_service, dependent: :destroy
has_one :jenkins_deprecated_service, dependent: :destroy
......@@ -296,6 +296,17 @@ module EE
default_issues_tracker? || jira_tracker_active?
end
def approvals_before_merge
return 0 unless feature_available?(:merge_request_approvers)
super
end
def reset_approvals_on_push
super && feature_available?(:merge_request_approvers)
end
alias_method :reset_approvals_on_push?, :reset_approvals_on_push
def approver_ids=(value)
value.split(",").map(&:strip).each do |user_id|
approvers.find_or_create_by(user_id: user_id, target_id: id)
......
......@@ -3,16 +3,22 @@ class License < ActiveRecord::Base
AUDITOR_USER_FEATURE = 'GitLab_Auditor_User'.freeze
BURNDOWN_CHARTS_FEATURE = 'BurndownCharts'.freeze
CONTRIBUTION_ANALYTICS_FEATURE = 'ContributionAnalytics'.freeze
DEPLOY_BOARD_FEATURE = 'GitLab_DeployBoard'.freeze
ELASTIC_SEARCH_FEATURE = 'GitLab_ElasticSearch'.freeze
EXPORT_ISSUES_FEATURE = 'GitLab_ExportIssues'.freeze
FAST_FORWARD_MERGE_FEATURE = 'GitLab_FastForwardMerge'.freeze
FILE_LOCK_FEATURE = 'GitLab_FileLocks'.freeze
GEO_FEATURE = 'GitLab_Geo'.freeze
ISSUABLE_DEFAULT_TEMPLATES_FEATURE = 'GitLab_IssuableDefaultTemplates'.freeze
ISSUE_BOARDS_FOCUS_MODE_FEATURE = 'IssueBoardsFocusMode'.freeze
ISSUE_BOARD_MILESTONE_FEATURE = 'IssueBoardMilestone'.freeze
ISSUE_WEIGHTS_FEATURE = 'GitLab_IssueWeights'.freeze
MERGE_REQUEST_APPROVERS_FEATURE = 'GitLab_MergeRequestApprovers'.freeze
MERGE_REQUEST_REBASE_FEATURE = 'GitLab_MergeRequestRebase'.freeze
MERGE_REQUEST_SQUASH_FEATURE = 'GitLab_MergeRequestSquash'.freeze
OBJECT_STORAGE_FEATURE = 'GitLab_ObjectStorage'.freeze
PUSH_RULES_FEATURE = 'GitLab_PushRules'.freeze
RELATED_ISSUES_FEATURE = 'RelatedIssues'.freeze
SERVICE_DESK_FEATURE = 'GitLab_ServiceDesk'.freeze
VARIABLE_ENVIRONMENT_SCOPE_FEATURE = 'VariableEnvironmentScope'.freeze
......@@ -28,13 +34,19 @@ class License < ActiveRecord::Base
# Features that make sense to Namespace:
burndown_charts: BURNDOWN_CHARTS_FEATURE,
contribution_analytics: CONTRIBUTION_ANALYTICS_FEATURE,
deploy_board: DEPLOY_BOARD_FEATURE,
export_issues: EXPORT_ISSUES_FEATURE,
fast_forward_merge: FAST_FORWARD_MERGE_FEATURE,
file_lock: FILE_LOCK_FEATURE,
issuable_default_templates: ISSUABLE_DEFAULT_TEMPLATES_FEATURE,
issue_board_focus_mode: ISSUE_BOARDS_FOCUS_MODE_FEATURE,
issue_board_milestone: ISSUE_BOARD_MILESTONE_FEATURE,
issue_weights: ISSUE_WEIGHTS_FEATURE,
merge_request_approvers: MERGE_REQUEST_APPROVERS_FEATURE,
merge_request_rebase: MERGE_REQUEST_REBASE_FEATURE,
merge_request_squash: MERGE_REQUEST_SQUASH_FEATURE
merge_request_squash: MERGE_REQUEST_SQUASH_FEATURE,
push_rules: PUSH_RULES_FEATURE
}.freeze
STARTER_PLAN = 'starter'.freeze
......@@ -44,12 +56,18 @@ class License < ActiveRecord::Base
EES_FEATURES = [
{ BURNDOWN_CHARTS_FEATURE => 1 },
{ CONTRIBUTION_ANALYTICS_FEATURE => 1 },
{ ELASTIC_SEARCH_FEATURE => 1 },
{ EXPORT_ISSUES_FEATURE => 1 },
{ FAST_FORWARD_MERGE_FEATURE => 1 },
{ ISSUABLE_DEFAULT_TEMPLATES_FEATURE => 1 },
{ ISSUE_BOARDS_FOCUS_MODE_FEATURE => 1 },
{ ISSUE_BOARD_MILESTONE_FEATURE => 1 },
{ ISSUE_WEIGHTS_FEATURE => 1 },
{ MERGE_REQUEST_APPROVERS_FEATURE => 1 },
{ MERGE_REQUEST_REBASE_FEATURE => 1 },
{ MERGE_REQUEST_SQUASH_FEATURE => 1 },
{ PUSH_RULES_FEATURE => 1 },
{ RELATED_ISSUES_FEATURE => 1 }
].freeze
......@@ -76,19 +94,23 @@ class License < ActiveRecord::Base
# Early adopters should not earn new features as they're
# introduced.
EARLY_ADOPTER_FEATURES = [
# TODO: Add EES features
# https://gitlab.com/gitlab-org/gitlab-ee/issues/2335)
{ AUDITOR_USER_FEATURE => 1 },
{ BURNDOWN_CHARTS_FEATURE => 1 },
{ CONTRIBUTION_ANALYTICS_FEATURE => 1 },
{ DEPLOY_BOARD_FEATURE => 1 },
{ EXPORT_ISSUES_FEATURE => 1 },
{ FAST_FORWARD_MERGE_FEATURE => 1 },
{ FILE_LOCK_FEATURE => 1 },
{ GEO_FEATURE => 1 },
{ ISSUABLE_DEFAULT_TEMPLATES_FEATURE => 1 },
{ ISSUE_BOARDS_FOCUS_MODE_FEATURE => 1 },
{ ISSUE_BOARD_MILESTONE_FEATURE => 1 },
{ ISSUE_WEIGHTS_FEATURE => 1 },
{ MERGE_REQUEST_APPROVERS_FEATURE => 1 },
{ MERGE_REQUEST_REBASE_FEATURE => 1 },
{ MERGE_REQUEST_SQUASH_FEATURE => 1 },
{ OBJECT_STORAGE_FEATURE => 1 },
{ PUSH_RULES_FEATURE => 1 },
{ SERVICE_DESK_FEATURE => 1 }
].freeze
......
......@@ -5,7 +5,6 @@ class MergeRequest < ActiveRecord::Base
include Referable
include Sortable
include Elastic::MergeRequestsSearch
include Approvable
include IgnorableColumn
ignore_column :position
......
require_dependency 'declarative_policy'
class BasePolicy < DeclarativePolicy::Base
include Gitlab::CurrentSettings
desc "User is an instance admin"
with_options scope: :user, score: 0
condition(:admin) { @user&.admin? }
......@@ -11,6 +13,11 @@ class BasePolicy < DeclarativePolicy::Base
with_options scope: :user, score: 0
condition(:can_create_group) { @user&.can_create_group }
desc "The application is restricted from public visibility"
condition(:restricted_public_level, scope: :global) do
current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
end
# EE Extensions
with_scope :user
condition(:auditor, score: 0) { @user&.auditor? }
......
......@@ -11,10 +11,16 @@ class GlobalPolicy < BasePolicy
with_options scope: :user, score: 0
condition(:access_locked) { @user.access_locked? }
rule { anonymous }.prevent_all
rule { anonymous }.policy do
prevent :log_in
prevent :access_api
prevent :access_git
prevent :receive_notifications
prevent :use_quick_actions
prevent :create_group
end
rule { default }.policy do
enable :read_users_list
enable :log_in
enable :access_api
enable :access_git
......@@ -37,4 +43,8 @@ class GlobalPolicy < BasePolicy
rule { access_locked }.policy do
prevent :log_in
end
rule { ~restricted_public_level }.policy do
enable :read_users_list
end
end
class UserPolicy < BasePolicy
include Gitlab::CurrentSettings
desc "The application is restricted from public visibility"
condition(:restricted_public_level, scope: :global) do
current_application_settings.restricted_visibility_levels.include?(Gitlab::VisibilityLevel::PUBLIC)
end
desc "The current user is the user in question"
condition(:user_is_self, score: 0) { @subject == @user }
......
module Boards
class UpdateService < BaseService
def execute(board)
board.update(name: params[:name], milestone_id: params[:milestone_id])
params.delete(:milestone_id) unless project.feature_available?(:issue_board_milestone)
board.update(params)
end
end
end
module EE
module Issues
module BuildService
def issue_params_from_template
return {} unless project.feature_available?(:issuable_default_templates)
{ description: project.issues_template }
end
# Issue params can be built from 3 types of passed params,
# They take precedence over eachother like this
# passed params > discussion params > template params
# The template params are filled in here, and might be overwritten by super
def issue_params
@issue_params ||= issue_params_from_template.merge(super)
end
end
end
end
module EE
module MergeRequests
module BuildService
def assign_title_and_description
super
assign_description_from_template
end
# Set MR description based on project template
def assign_description_from_template
return unless target_project.feature_available?(:issuable_default_templates) &&
target_project.merge_requests_template.present?
merge_request.description = target_project.merge_requests_template
append_closes_description
end
end
end
end
module Issues
class BuildService < Issues::BaseService
include ResolveDiscussions
prepend ::EE::Issues::BuildService
def execute
filter_resolve_discussion_params
@issue = project.issues.new(issue_params)
end
def issue_params_from_template
{ description: project.issues_template }
end
def issue_params_with_info_from_discussions
return {} unless merge_request_to_resolve_discussions_of
......@@ -58,13 +55,8 @@ module Issues
[discussion_info, quote].join("\n\n")
end
# Issue params can be built from 3 types of passed params,
# They take precedence over eachother like this
# passed params > discussion params > template params
def issue_params
@issue_params ||= issue_params_from_template
.merge(issue_params_with_info_from_discussions)
.merge(whitelisted_issue_params)
@issue_params ||= issue_params_with_info_from_discussions.merge(whitelisted_issue_params)
end
def whitelisted_issue_params
......
module MergeRequests
class BuildService < MergeRequests::BaseService
prepend EE::MergeRequests::BuildService
def execute
self.merge_request = MergeRequest.new(params)
merge_request.compare_commits = []
......@@ -105,33 +107,42 @@ module MergeRequests
# more than one commit in the MR
#
def assign_title_and_description
if match = source_branch.match(/\A(\d+)-/)
iid = match[1]
assign_title_and_description_from_single_commit
assign_title_from_issue
merge_request.title ||= source_branch.titleize.humanize
merge_request.title = wip_title if compare_commits.empty?
append_closes_description
end
def assign_title_and_description_from_single_commit
commits = compare_commits
if commits && commits.count == 1
return unless commits && commits.count == 1
commit = commits.first
merge_request.title = commit.title
merge_request.title ||= commit.title
merge_request.description ||= commit.description.try(:strip)
elsif iid && issue = target_project.get_issue(iid, current_user)
end
def assign_title_from_issue
return unless issue
case issue
when Issue
merge_request.title = "Resolve \"#{issue.title}\""
merge_request.title ||= "Resolve \"#{issue.title}\""
when ExternalIssue
merge_request.title = "Resolve #{issue.title}"
merge_request.title ||= "Resolve #{issue.title}"
end
else
merge_request.title = source_branch.titleize.humanize
end
# Set MR description based on project template
if merge_request.target_project.merge_requests_template.present?
merge_request.description = merge_request.target_project.merge_requests_template
end
def append_closes_description
return unless issue_iid
if iid
closes_issue = "Closes ##{iid}"
closes_issue = "Closes ##{issue_iid}"
if description.present?
merge_request.description += closes_issue.prepend("\n\n")
......@@ -140,7 +151,16 @@ module MergeRequests
end
end
merge_request.title = wip_title if commits.empty?
def issue_iid
return @issue_iid if defined?(@issue_iid)
@issue_iid = source_branch[/\A(\d+)-/, 1]
end
def issue
return @issue if defined?(@issue)
@issue = target_project.get_issue(issue_iid, current_user)
end
end
end
......@@ -44,6 +44,7 @@ module MergeRequests
@merge_request = merge_request
return true if project.merge_requests_ff_only_enabled
return true unless project.feature_available?(:push_rules)
push_rule = merge_request.project.push_rule
return true unless push_rule
......
......@@ -22,7 +22,7 @@ module Projects
return @project
end
# Repository size limit comes as MB from the view
# EE-only: Repository size limit comes as MB from the view
set_repository_size_limit_as_bytes
set_project_name_from_path
......@@ -103,12 +103,8 @@ module Projects
@project.add_master(owners, current_user: current_user)
end
predefined_push_rule = PushRule.find_by(is_sample: true)
if predefined_push_rule
push_rule = predefined_push_rule.dup.tap{ |gh| gh.is_sample = false }
project.push_rule = push_rule
end
# EE-only
create_predefined_push_rule
@project.group&.refresh_members_authorized_projects
end
......@@ -166,5 +162,16 @@ module Projects
@project.path = @project.name.dup.parameterize
end
end
def create_predefined_push_rule
return unless project.feature_available?(:push_rules)
predefined_push_rule = PushRule.find_by(is_sample: true)
if predefined_push_rule
push_rule = predefined_push_rule.dup.tap{ |gh| gh.is_sample = false }
project.push_rule = push_rule
end
end
end
end
......@@ -34,23 +34,10 @@
Abuse Reports
%span.badge.count= number_with_delimiter(AbuseReport.count(:all))
= nav_link(controller: :licenses) do
= link_to admin_license_path, title: 'License' do
%span
License
- if akismet_enabled?
= nav_link(controller: :spam_logs) do
= link_to admin_spam_logs_path, title: "Spam Logs" do
%span
Spam Logs
= nav_link(controller: :push_rules) do
= link_to admin_push_rule_path, title: 'Push Rules' do
%span
Push Rules
= nav_link(controller: :geo_nodes) do
= link_to admin_geo_nodes_path, title: 'Geo Nodes' do
%span
Geo Nodes
= render 'layouts/nav/admin_ee'
= nav_link(controller: :licenses) do
= link_to admin_license_path, title: 'License' do
%span
License
- if License.feature_available?(:push_rules)
= nav_link(controller: :push_rules) do
= link_to admin_push_rule_path, title: 'Push Rules' do
%span
Push Rules
= nav_link(controller: :geo_nodes) do
= link_to admin_geo_nodes_path, title: 'Geo Nodes' do
%span
Geo Nodes
......@@ -24,6 +24,7 @@
= link_to group_group_members_path(@group), title: 'Members' do
%span
Members
- if @group.feature_available?(:contribution_analytics)
= nav_link(path: 'analytics#show') do
= link_to group_analytics_path(@group), title: 'Contribution Analytics', data: {placement: 'right'} do
%span
......
%fieldset.features.append-bottom-0.issues-feature
%hr
%h5.prepend-top-0
Issues
.form-group
= f.label :issues_template, class: 'label-light' do
Default description template for issues
= link_to icon('question-circle'), help_page_path('user/project/description_templates', anchor: 'setting-a-default-template-for-issues-and-merge-requests'), target: '_blank'
= f.text_area :issues_template, class: "form-control", rows: 3
.hint
Description parsed with #{link_to "GitLab Flavored Markdown", help_page_path('user/markdown'), target: '_blank'}.
......@@ -49,6 +49,7 @@
%li
%a{ "href" => "#", "@click.stop.prevent" => "showPage('edit')" }
Edit board name
- if @project.feature_available?(:issue_board_milestone, current_user)
%li
%a{ "href" => "#", "@click.stop.prevent" => "showPage('milestone')" }
Edit board milestone
......
......@@ -10,6 +10,7 @@
%input.form-control{ type: "text",
id: "board-new-name",
"v-model" => "board.name" }
- if @project.feature_available?(:issue_board_milestone, current_user)
.dropdown.board-inner-milestone-dropdown{ ":class" => "{ open: milestoneDropdownOpen }",
"v-if" => "currentPage === 'new'" }
%label.label-light{ for: "board-milestone" }
......
......@@ -128,7 +128,7 @@
%span.descr Enable Container Registry for this project
= link_to icon('question-circle'), help_page_path('user/project/container_registry'), target: '_blank'
= render 'issues_settings', f: f
= render 'projects/ee/issues_settings', form: f
= render 'merge_request_settings', form: f
......
- if @project.feature_available?(:issuable_default_templates)
%fieldset.features.append-bottom-0.issues-feature
%hr
%h5.prepend-top-0
Issues
.form-group
= form.label :issues_template, class: 'label-light' do
Default description template for issues
= link_to icon('question-circle'), help_page_path('user/project/description_templates', anchor: 'setting-a-default-template-for-issues-and-merge-requests'), target: '_blank'
= form.text_area :issues_template, class: "form-control", rows: 3
.hint
Description parsed with #{link_to "GitLab Flavored Markdown", help_page_path('user/markdown'), target: '_blank'}.
- return unless project.feature_available?(:merge_request_approvers)
.form-group.reset-approvals-on-push
.checkbox
= label_tag :require_approvals do
= check_box_tag :require_approvals, nil, project.approvals_before_merge.nonzero?, class: 'js-require-approvals-toggle'
%strong Activate merge request approvals
= link_to icon('question-circle'), help_page_path("user/project/merge_requests/merge_request_approvals"), target: '_blank'
.descr Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that you will need to approve every merge request in a project.
.nested-settings{ class: project.approvals_before_merge.nonzero? ? '' : 'hidden' }
.form-group
= form.label :approver_ids, class: 'label-light' do
Approvers
= hidden_field_tag "project[approver_ids]"
= hidden_field_tag "project[approver_group_ids]"
.input-group.input-btn-group
= hidden_field_tag :approver_user_and_group_ids, '', { class: 'js-select-user-and-group input-large', tabindex: 1, 'data-name': 'project' }
%button.btn.btn-success.js-add-approvers{ type: 'button', title: 'Add approvers(s)' }
Add
.help-block
Add an approver or group suggestion for each merge request
.panel.panel-default.prepend-top-10.js-current-approvers
.panel-heading
Approvers
%span.badge
- ids = []
- project.approvers.each do |user|
- ids << user.user_id
- project.approver_groups.each do |group|
- group.users.each do |user|
- unless ids.include?(user.id)
- ids << user.id
= ids.count
%ul.well-list.approver-list
.load-wrapper.hidden
= icon('spinner spin', class: 'approver-list-loader')
- project.approvers.each do |approver|
%li.approver.settings-flex-row.js-approver{ data: { id: approver.user_id } }
= link_to approver.user.name, approver.user
.pull-right
%button{ href: namespace_project_approver_path(project.namespace, project, approver), data: { confirm: "Are you sure you want to remove approver #{approver.user.name}"}, class: "btn btn-remove js-approver-remove", title: 'Remove approver' }
= icon("trash")
- project.approver_groups.each do |approver_group|
%li.approver-group.settings-flex-row.js-approver-group{ data: { id: approver_group.group.id } }
.span
%span.light
Group:
= link_to approver_group.group.name, approver_group.group
%span.badge
= approver_group.group.members.count
.pull-right
%button{ href: namespace_project_approver_group_path(project.namespace, project, approver_group), data: { confirm: "Are you sure you want to remove group #{approver_group.group.name}" }, class: "btn btn-remove js-approver-remove", title: 'Remove group' }
= icon("trash")
- if project.approvers.empty? && project.approver_groups.empty?
%li There are no approvers
.form-group
= form.label :approvals_before_merge, class: 'label-light' do
Approvals required
= form.number_field :approvals_before_merge, class: "form-control", min: 0
.help-block
.form-group.reset-approvals-on-push
.checkbox
= form.label :reset_approvals_on_push do
= form.check_box :reset_approvals_on_push
%strong Reset approvals on push
.descr Approvals are reset when new data is pushed to the merge request
......@@ -39,7 +39,8 @@
%span.descr
When fast-forward merge is not possible, the user is given the option to rebase.
.form-group
- if @project.feature_available?(:issuable_default_templates)
.form-group
= form.label :merge_requests_template, class: 'label-light' do
Default description template for merge requests
= link_to icon('question-circle'), help_page_path('user/project/description_templates', anchor: 'setting-a-default-template-for-issues-and-merge-requests'), target: '_blank'
......@@ -47,74 +48,7 @@
.hint
Description parsed with #{link_to "GitLab Flavored Markdown", help_page_path('user/markdown'), target: '_blank'}.
.form-group.reset-approvals-on-push
.checkbox
= label_tag :require_approvals do
= check_box_tag :require_approvals, nil, project.approvals_before_merge.nonzero?, class: 'js-require-approvals-toggle'
%strong Activate merge request approvals
= link_to icon('question-circle'), help_page_path("user/project/merge_requests/merge_request_approvals"), target: '_blank'
.descr Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that you will need to approve every merge request in a project.
.nested-settings{ class: project.approvals_before_merge.nonzero? ? '' : 'hidden' }
.form-group
= form.label :approver_ids, class: 'label-light' do
Approvers
= hidden_field_tag "project[approver_ids]"
= hidden_field_tag "project[approver_group_ids]"
.input-group.input-btn-group
= hidden_field_tag :approver_user_and_group_ids, '', { class: 'js-select-user-and-group input-large', tabindex: 1, 'data-name': 'project' }
%button.btn.btn-success.js-add-approvers{ type: 'button', title: 'Add approvers(s)' }
Add
.help-block
Add an approver or group suggestion for each merge request
.panel.panel-default.prepend-top-10.js-current-approvers
.panel-heading
Approvers
%span.badge
- ids = []
- project.approvers.each do |user|
- ids << user.user_id
- project.approver_groups.each do |group|
- group.users.each do |user|
- unless ids.include?(user.id)
- ids << user.id
= ids.count
%ul.well-list.approver-list
.load-wrapper.hidden
= icon('spinner spin', class: 'approver-list-loader')
- project.approvers.each do |approver|
%li.approver.settings-flex-row.js-approver{ data: { id: approver.user_id } }
= link_to approver.user.name, approver.user
.pull-right
%button{ href: namespace_project_approver_path(project.namespace, project, approver), data: { confirm: "Are you sure you want to remove approver #{approver.user.name}"}, class: "btn btn-remove js-approver-remove", title: 'Remove approver' }
= icon("trash")
- project.approver_groups.each do |approver_group|
%li.approver-group.settings-flex-row.js-approver-group{ data: { id: approver_group.group.id } }
.span
%span.light
Group:
= link_to approver_group.group.name, approver_group.group
%span.badge
= approver_group.group.members.count
.pull-right
%button{ href: namespace_project_approver_group_path(project.namespace, project, approver_group), data: { confirm: "Are you sure you want to remove group #{approver_group.group.name}" }, class: "btn btn-remove js-approver-remove", title: 'Remove group' }
= icon("trash")
- if project.approvers.empty? && project.approver_groups.empty?
%li There are no approvers
.form-group
= form.label :approvals_before_merge, class: 'label-light' do
Approvals required
= form.number_field :approvals_before_merge, class: "form-control", min: 0
.help-block
.form-group.reset-approvals-on-push
.checkbox
= form.label :reset_approvals_on_push do
= form.check_box :reset_approvals_on_push
%strong Reset approvals on push
.descr Approvals are reset when new data is pushed to the merge request
= render 'projects/ee/merge_request_approvals_settings', project: project, form: form
:javascript
new GroupsSelect();
- return unless @project.feature_available?(:push_rules)
- expanded = Rails.env.test?
%section.settings
.settings-header
......
---
title: Introduce namespace license checks for merge request approvers (EES)
merge_request: 2324
author:
---
title: Introduce namespace license checks for Push Rules (EES)
merge_request: 2335
author:
---
title: Add license checks for focus mode on the issue board
merge_request: 2303
author:
---
title: Add license checks for issue boards with milestones
merge_request: 2315
author:
---
title: Add namespace license checks for Contribution Analytics
merge_request: 2302
author:
---
title: Namespace license checks Issue & MR template
merge_request: 2321
author:
---
title: Allow unauthenticated access to the /api/v4/users API
merge_request: 12445
author:
......@@ -13,6 +13,13 @@ In order for the report to show in the merge request, you need to specify a
`codeclimate.json` as an artifact. GitLab will then check this file and show
the information inside the merge request.
>**Note:**
If the Code Climate report doesn't have anything to compare to, no information
will be displayed in the merge request area. That is the case when you add the
`codeclimate` job in your `.gitlab-ci.yml` for the very first time.
Consecutive merge requests will have something to compare to and the code quality
diff will be shown properly.
For more information on how the `codeclimate` job should look like, check the
example on [analyzing a project's code quality with Code Climate CLI][cc-docs].
......
......@@ -132,7 +132,7 @@ module API
expose :printing_merge_request_link_enabled
# EE only
expose :approvals_before_merge
expose :approvals_before_merge, if: ->(project, _) { project.feature_available?(:merge_request_approvers) }
expose :statistics, using: 'API::Entities::ProjectStatistics', if: :statistics
end
......@@ -619,7 +619,8 @@ module API
expose :id
expose :name
expose :project, using: Entities::BasicProjectDetails
expose :milestone
expose :milestone,
if: -> (board, _) { board.project.feature_available?(:issue_board_milestone) }
expose :lists, using: Entities::List do |board|
board.lists.destroyable
end
......
......@@ -2,6 +2,7 @@ module API
class ProjectPushRule < Grape::API
before { authenticate! }
before { authorize_admin_project }
before { check_project_feature_available!(:push_rules) }
params do
requires :id, type: String, desc: 'The ID of a project'
......
......@@ -4,10 +4,13 @@ module API
before do
allow_access_with_scope :read_user if request.get?
authenticate!
end
resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do
before do
authenticate_non_get!
end
helpers do
def find_user(params)
id = params[:user_id] || params[:id]
......@@ -57,15 +60,22 @@ module API
use :pagination
end
get do
unless can?(current_user, :read_users_list)
render_api_error!("Not authorized.", 403)
end
authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?)
users = UsersFinder.new(current_user, params).execute
entity = current_user.admin? ? Entities::UserWithAdmin : Entities::UserBasic
authorized = can?(current_user, :read_users_list)
# When `current_user` is not present, require that the `username`
# parameter is passed, to prevent an unauthenticated user from accessing
# a list of all the users on the GitLab instance. `UsersFinder` performs
# an exact match on the `username` parameter, so we are guaranteed to
# get either 0 or 1 `users` here.
authorized &&= params[:username].present? if current_user.blank?
forbidden!("Not authorized to access /api/v4/users") unless authorized
entity = current_user&.admin? ? Entities::UserWithAdmin : Entities::UserBasic
present paginate(users), with: entity
end
......@@ -404,6 +414,10 @@ module API
end
resource :user do
before do
authenticate!
end
desc 'Get the currently authenticated user' do
success Entities::UserPublic
end
......
......@@ -116,7 +116,7 @@ module API
expose :repository_storage, if: lambda { |_project, options| options[:current_user].try(:admin?) }
expose :request_access_enabled
expose :only_allow_merge_if_all_discussions_are_resolved
expose :approvals_before_merge
expose :approvals_before_merge, if: ->(project, _) { project.feature_available?(:merge_request_approvers) }
expose :statistics, using: '::API::V3::Entities::ProjectStatistics', if: :statistics
end
......
......@@ -3,6 +3,7 @@ module API
class ProjectGitHook < Grape::API
before { authenticate! }
before { authorize_admin_project }
before { check_project_feature_available!(:push_rules) }
DEPRECATION_MESSAGE = 'This endpoint is deprecated, replaced with push_rules, and will be removed in GitLab 9.0.'.freeze
......
......@@ -3,6 +3,7 @@ module API
class ProjectPushRule < Grape::API
before { authenticate! }
before { authorize_admin_project }
before { check_project_feature_available!(:push_rules) }
params do
requires :id, type: String, desc: 'The ID of a project'
......
......@@ -9,6 +9,10 @@ module EE
user
end
def check_project_feature_available!(feature)
not_found! unless user_project.feature_available?(feature)
end
end
end
end
......@@ -142,7 +142,7 @@ module Gitlab
end
def push_rule_check
return unless @newrev && @oldrev
return unless @newrev && @oldrev && project.feature_available?(:push_rules)
push_rule = project.push_rule
......
......@@ -8,17 +8,52 @@ describe Admin::PushRulesController do
end
describe '#update' do
it 'updates sample push rule' do
params =
{ deny_delete_tag: true, delete_branch_regex: "any", commit_message_regex: "any",
let(:params) do
{
deny_delete_tag: true, delete_branch_regex: "any", commit_message_regex: "any",
force_push_regex: "any", author_email_regex: "any", member_check: true, file_name_regex: "any",
max_file_size: "0", prevent_secrets: true }
max_file_size: "0", prevent_secrets: true
}
end
it 'updates sample push rule' do
expect_any_instance_of(PushRule).to receive(:update_attributes).with(params)
patch :update, push_rule: params
expect(response).to redirect_to(admin_push_rule_path)
end
context 'push rules unlicensed' do
before do
stub_licensed_features(push_rules: false)
end
it 'returns 404' do
patch :update, push_rule: params
expect(response).to have_http_status(404)
end
end
end
describe '#show' do
it 'returns 200' do
get :show
expect(response).to have_http_status(200)
end
context 'push rules unlicensed' do
before do
stub_licensed_features(push_rules: false)
end
it 'returns 404' do
get :show
expect(response).to have_http_status(404)
end
end
end
end
......@@ -39,6 +39,14 @@ describe Groups::AnalyticsController do
create_push_event(user3, project)
end
it 'returns 404 when feature is not available' do
stub_licensed_features(contribution_analytics: false)
get :show, group_id: group.path
expect(response).to have_http_status(404)
end
it 'sets instance variables properly' do
get :show, group_id: group.path
......
require 'spec_helper'
describe Projects::PushRulesController do
let(:project) { create(:empty_project, push_rule: create(:push_rule, prevent_secrets: false)) }
let(:user) { create(:user) }
before do
project.add_master(user)
sign_in(user)
end
describe '#update' do
def do_update
patch :update, namespace_id: project.namespace, project_id: project, id: 1, push_rule: { prevent_secrets: true }
end
it 'updates the push rule' do
do_update
expect(response).to have_http_status(302)
expect(project.push_rule(true).prevent_secrets).to be_truthy
end
context 'push rules unlicensed' do
before do
stub_licensed_features(push_rules: false)
end
it 'returns 404' do
do_update
expect(response).to have_http_status(404)
end
end
end
end
require 'spec_helper'
describe Projects::Settings::RepositoryController do
let(:project) { create(:project_empty_repo, :public) }
let(:user) { create(:user) }
before do
project.add_master(user)
sign_in(user)
end
describe 'GET show' do
context 'push rule' do
subject(:push_rule) { assigns(:push_rule) }
it 'is created' do
get :show, namespace_id: project.namespace, project_id: project
is_expected.to be_persisted
end
context 'unlicensed' do
before do
stub_licensed_features(push_rules: false)
end
it 'is not created' do
get :show, namespace_id: project.namespace, project_id: project
is_expected.to be_nil
end
end
end
end
end
......@@ -10,7 +10,12 @@ describe 'Board with milestone', :feature, :js do
before do
project.team << [user, :master]
gitlab_sign_in(user)
login_as(user)
end
context 'with the feature enabled' do
before do
stub_licensed_features(issue_board_milestone: true)
end
context 'new board' do
......@@ -162,6 +167,38 @@ describe 'Board with milestone', :feature, :js do
expect(page).to have_content(milestone.title)
end
end
end
context 'with the feature disabled' do
before do
stub_licensed_features(issue_board_milestone: false)
visit namespace_project_boards_path(project.namespace, project)
end
it "doesn't show the input when creating a board" do
page.within '#js-multiple-boards-switcher' do
find('.dropdown-menu-toggle').click
click_link 'Create new board'
# To make sure the form is shown
expect(page).to have_selector('#board-new-name')
expect(page).not_to have_button('Milestone')
end
end
it "doesn't show the option to edit the milestone" do
page.within '#js-multiple-boards-switcher' do
find('.dropdown-menu-toggle').click
# To make sure the dropdown is open
expect(page).to have_link('Edit board name')
expect(page).not_to have_link('Edit board milestone')
end
end
end
def create_board_with_milestone
page.within '#js-multiple-boards-switcher' do
......
require 'spec_helper'
describe 'issue boards', feature: true, js: true do
let(:user) { create(:user) }
let(:project) { create(:empty_project, :public) }
let!(:board) { create(:board, project: project) }
before do
project.add_developer(user)
login_as(user)
end
context 'issue board focus mode' do
it 'shows the button when the feature is enabled' do
stub_licensed_features(issue_board_focus_mode: true)
visit_board_page
expect(page).to have_link('Toggle focus mode')
end
it 'hides the button when the feature is enabled' do
stub_licensed_features(issue_board_focus_mode: false)
visit_board_page
expect(page).not_to have_link('Toggle focus mode')
end
end
def visit_board_page
visit namespace_project_boards_path(project.namespace, project)
wait_for_requests
end
end
require 'spec_helper'
describe 'Project settings > [EE] Merge Requests', feature: true, js: true do
include GitlabRoutingHelper
let(:user) { create(:user) }
let(:project) { create(:empty_project, approvals_before_merge: 1) }
before do
gitlab_sign_in(user)
project.team << [user, :master]
end
context 'issuable default templates feature not available' do
before do
stub_licensed_features(issuable_default_templates: false)
end
scenario 'input to configure issue template is not shown' do
visit edit_project_path(project)
expect(page).not_to have_selector('#project_issues_template')
end
end
context 'issuable default templates feature is available' do
before do
stub_licensed_features(issuable_default_templates: true)
end
scenario 'input to configure issue template is not shown' do
visit edit_project_path(project)
expect(page).to have_selector('#project_issues_template')
end
end
end
......@@ -83,4 +83,28 @@ describe 'Project settings > [EE] Merge Requests', feature: true, js: true do
expect(find('.js-current-approvers')).not_to have_content(group.name)
end
end
context 'issuable default templates feature not available' do
before do
stub_licensed_features(issuable_default_templates: false)
end
scenario 'input to configure merge request template is not shown' do
visit edit_project_path(project)
expect(page).not_to have_selector('#project_merge_requests_template')
end
end
context 'issuable default templates feature is available' do
before do
stub_licensed_features(issuable_default_templates: true)
end
scenario 'input to configure merge request template is not shown' do
visit edit_project_path(project)
expect(page).to have_selector('#project_merge_requests_template')
end
end
end
......@@ -15,6 +15,7 @@ describe 'Project settings > [EE] repository', feature: true do
let(:commit_message) { 'Required part of every message' }
let(:input_id) { 'push_rule_commit_message_regex' }
context 'push rules licensed' do
before do
visit namespace_project_settings_repository_path(project.namespace, project)
......@@ -31,6 +32,19 @@ describe 'Project settings > [EE] repository', feature: true do
end
end
context 'push rules unlicensed' do
before do
stub_licensed_features(push_rules: false)
visit namespace_project_settings_repository_path(project.namespace, project)
end
it 'hides push rule settings' do
expect(page).not_to have_content('Push Rules')
end
end
end
describe 'mirror settings', :js do
let(:user2) { create(:user) }
......
......@@ -165,6 +165,14 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
end
context 'push rules checks' do
shared_examples 'check ignored when push rule unlicensed' do
before do
stub_licensed_features(push_rules: false)
end
it { is_expected.to be_truthy }
end
let(:project) { create(:project, :public, push_rule: push_rule) }
before do
......@@ -183,6 +191,8 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
project.add_master(user)
end
it_behaves_like 'check ignored when push rule unlicensed'
it 'returns an error if the rule denies tag deletion' do
expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You cannot delete a tag')
end
......@@ -199,6 +209,8 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
context 'commit message rules' do
let(:push_rule) { create(:push_rule, :commit_message) }
it_behaves_like 'check ignored when push rule unlicensed'
it 'returns an error if the rule fails' do
expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, "Commit message does not follow the pattern '#{push_rule.commit_message_regex}'")
end
......@@ -212,6 +224,8 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
allow_any_instance_of(Commit).to receive(:author_email).and_return('mike@valid.com')
end
it_behaves_like 'check ignored when push rule unlicensed'
it 'returns an error if the rule fails for the committer' do
allow_any_instance_of(Commit).to receive(:committer_email).and_return('ana@invalid.com')
......@@ -233,6 +247,8 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
allow_any_instance_of(Commit).to receive(:author_email).and_return('some@mail.com')
end
it_behaves_like 'check ignored when push rule unlicensed'
it 'returns an error if the commit author is not a GitLab member' do
expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, "Author 'some@mail.com' is not a member of team")
end
......@@ -243,6 +259,8 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
context 'file name regex check' do
let(:push_rule) { create(:push_rule, file_name_regex: 'READ*') }
it_behaves_like 'check ignored when push rule unlicensed'
it "returns an error if a new or renamed filed doesn't match the file name regex" do
expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, "File name README was blacklisted by the pattern READ*.")
end
......@@ -251,6 +269,8 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
context 'blacklisted files check' do
let(:push_rule) { create(:push_rule, prevent_secrets: true) }
it_behaves_like 'check ignored when push rule unlicensed'
it "returns true if there is no blacklisted files" do
new_rev = nil
......@@ -305,6 +325,8 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
allow_any_instance_of(Blob).to receive(:size).and_return(2.megabytes)
end
it_behaves_like 'check ignored when push rule unlicensed'
it 'returns an error if file exceeds the maximum file size' do
expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, "File \"README\" is larger than the allowed size of 1 MB")
end
......
......@@ -12,27 +12,4 @@ describe Board do
it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_presence_of(:project) }
end
describe 'milestone' do
subject { described_class.new }
it 'returns Milestone::Upcoming for upcoming milestone id' do
subject.milestone_id = Milestone::Upcoming.id
expect(subject.milestone).to eq Milestone::Upcoming
end
it 'returns milestone for valid milestone id' do
milestone = create(:milestone)
subject.milestone_id = milestone.id
expect(subject.milestone).to eq milestone
end
it 'returns nil for invalid milestone id' do
subject.milestone_id = -1
expect(subject.milestone).to be_nil
end
end
end
require 'spec_helper'
describe Board do
describe 'milestone' do
subject(:board) { build(:board) }
context 'when the feature is available' do
before do
stub_licensed_features(issue_board_milestone: true)
end
it 'returns Milestone::Upcoming for upcoming milestone id' do
board.milestone_id = Milestone::Upcoming.id
expect(board.milestone).to eq Milestone::Upcoming
end
it 'returns milestone for valid milestone id' do
milestone = create(:milestone)
board.milestone_id = milestone.id
expect(board.milestone).to eq milestone
end
it 'returns nil for invalid milestone id' do
board.milestone_id = -1
expect(board.milestone).to be_nil
end
end
it 'returns nil when the feature is not available' do
stub_licensed_features(issue_board_milestone: false)
milestone = create(:milestone)
board.milestone_id = milestone.id
expect(board.milestone).to be_nil
end
end
end
......@@ -127,4 +127,26 @@ describe MergeRequest, models: true do
end
end
end
describe '#approvals_before_merge' do
[
{ license: true, database: 5, expected: 5 },
{ license: true, database: 0, expected: 0 },
{ license: false, database: 5, expected: 0 },
{ license: false, database: 0, expected: 0 }
].each do |spec|
context spec.inspect do
let(:spec) { spec }
let(:merge_request) { build(:merge_request, approvals_before_merge: spec[:database]) }
subject { merge_request.approvals_before_merge }
before do
stub_licensed_features(merge_request_approvers: spec[:license])
end
it { is_expected.to eq(spec[:expected]) }
end
end
end
end
......@@ -11,6 +11,22 @@ describe Project, models: true do
it { is_expected.to delegate_method(:shared_runners_minutes_used?).to(:namespace) }
end
describe '#push_rule' do
let(:project) { create(:project, push_rule: create(:push_rule)) }
subject(:push_rule) { project.push_rule(true) }
it { is_expected.not_to be_nil }
context 'push rules unlicensed' do
before do
stub_licensed_features(push_rules: false)
end
it { is_expected.to be_nil }
end
end
describe '#feature_available?' do
let(:namespace) { build_stubbed(:namespace) }
let(:project) { build_stubbed(:project, namespace: namespace) }
......@@ -295,6 +311,7 @@ describe Project, models: true do
end
end
<<<<<<< HEAD
describe '#secret_variables_for' do
let(:project) { create(:empty_project) }
......@@ -461,6 +478,72 @@ describe Project, models: true do
end
end
describe '#approvals_before_merge' do
[
{ license: true, database: 5, expected: 5 },
{ license: true, database: 0, expected: 0 },
{ license: false, database: 5, expected: 0 },
{ license: false, database: 0, expected: 0 }
].each do |spec|
context spec.inspect do
let(:spec) { spec }
let(:project) { build(:project, approvals_before_merge: spec[:database]) }
subject { project.approvals_before_merge }
before do
stub_licensed_features(merge_request_approvers: spec[:license])
end
it { is_expected.to eq(spec[:expected]) }
end
end
end
describe "#reset_approvals_on_push?" do
[
{ license: true, database: true, expected: true },
{ license: true, database: false, expected: false },
{ license: false, database: true, expected: false },
{ license: false, database: false, expected: false }
].each do |spec|
context spec.inspect do
let(:spec) { spec }
let(:project) { build(:project, reset_approvals_on_push: spec[:database]) }
subject { project.reset_approvals_on_push? }
before do
stub_licensed_features(merge_request_approvers: spec[:license])
end
it { is_expected.to eq(spec[:expected]) }
end
end
end
describe '#approvals_before_merge' do
[
{ license: true, database: 5, expected: 5 },
{ license: true, database: 0, expected: 0 },
{ license: false, database: 5, expected: 0 },
{ license: false, database: 0, expected: 0 }
].each do |spec|
context spec.inspect do
let(:spec) { spec }
let(:project) { build(:project, approvals_before_merge: spec[:database]) }
subject { project.approvals_before_merge }
before do
stub_licensed_features(merge_request_approvers: spec[:license])
end
it { is_expected.to eq(spec[:expected]) }
end
end
end
describe '#merge_method' do
[
{ ff: true, rebase: true, ff_licensed: true, rebase_licensed: true, method: :ff },
......
require 'spec_helper'
describe GlobalPolicy, models: true do
let(:current_user) { create(:user) }
let(:user) { create(:user) }
subject { GlobalPolicy.new(current_user, [user]) }
describe "reading the list of users" do
context "for a logged in user" do
it { is_expected.to be_allowed(:read_users_list) }
end
context "for an anonymous user" do
let(:current_user) { nil }
context "when the public level is restricted" do
before do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
end
it { is_expected.not_to be_allowed(:read_users_list) }
end
context "when the public level is not restricted" do
before do
stub_application_setting(restricted_visibility_levels: [])
end
it { is_expected.to be_allowed(:read_users_list) }
end
end
end
end
......@@ -57,6 +57,24 @@ describe API::Boards do
expect(response).to match_response_schema('public_api/v4/boards')
end
end
context 'with the issue_board_milestone-feature available' do
it 'returns the milestone when the `issue_board_milestone`-feature is enabled' do
stub_licensed_features(issue_board_milestone: true)
get api(base_url, user)
expect(json_response.first["milestone"]).not_to be_nil
end
it 'hides the milestone when the `issue_board_milestone`-feature is disabled' do
stub_licensed_features(issue_board_milestone: false)
get api(base_url, user)
expect(json_response.first["milestone"]).to be_nil
end
end
end
describe "GET /projects/:id/boards/:board_id/lists" do
......
......@@ -13,9 +13,40 @@ describe API::Users do
describe 'GET /users' do
context "when unauthenticated" do
it "returns authentication error" do
it "returns authorization error when the `username` parameter is not passed" do
get api("/users")
expect(response).to have_http_status(401)
expect(response).to have_http_status(403)
end
it "returns the user when a valid `username` parameter is passed" do
user = create(:user)
get api("/users"), username: user.username
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.size).to eq(1)
expect(json_response[0]['id']).to eq(user.id)
expect(json_response[0]['username']).to eq(user.username)
end
it "returns authorization error when the `username` parameter refers to an inaccessible user" do
user = create(:user)
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
get api("/users"), username: user.username
expect(response).to have_http_status(403)
end
it "returns an empty response when an invalid `username` parameter is passed" do
get api("/users"), username: 'invalid'
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.size).to eq(0)
end
end
......@@ -150,6 +181,7 @@ describe API::Users do
describe "GET /users/:id" do
it "returns a user by id" do
get api("/users/#{user.id}", user)
expect(response).to have_http_status(200)
expect(json_response['username']).to eq(user.username)
end
......@@ -160,9 +192,22 @@ describe API::Users do
expect(json_response['is_admin']).to be_nil
end
it "returns a 401 if unauthenticated" do
get api("/users/9998")
expect(response).to have_http_status(401)
context 'for an anonymous user' do
it "returns a user by id" do
get api("/users/#{user.id}")
expect(response).to have_http_status(200)
expect(json_response['username']).to eq(user.username)
end
it "returns a 404 if the target user is present but inaccessible" do
allow(Ability).to receive(:allowed?).and_call_original
allow(Ability).to receive(:allowed?).with(nil, :read_user, user).and_return(false)
get api("/users/#{user.id}")
expect(response).to have_http_status(404)
end
end
it "returns a 404 error if user id not found" do
......
......@@ -24,5 +24,25 @@ describe Boards::UpdateService, services: true do
expect(service.execute(board)).to eq false
end
it 'udpates the milestone with issue board milestones enabled' do
stub_licensed_features(issue_board_milestone: true)
milestone = create(:milestone, project: project)
service = described_class.new(project, double, milestone_id: milestone.id)
service.execute(board)
expect(board.reload.milestone).to eq(milestone)
end
it 'udpates the milestone with the issue board milestones feature enabled' do
stub_licensed_features(issue_board_milestone: false)
milestone = create(:milestone, project: project)
service = described_class.new(project, double, milestone_id: milestone.id)
service.execute(board)
expect(board.reload.milestone).to be_nil
end
end
end
require 'spec_helper.rb'
describe Issues::BuildService, services: true do # rubocop:disable RSpec/FilePath
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
before do
project.team << [user, :developer]
end
context 'with an issue template' do
describe '#execute' do
it 'fills in the template in the description' do
project = build(:project, issues_template: 'Work hard, play hard!')
service = described_class.new(project, user)
issue = service.execute
expect(issue.description).to eq('Work hard, play hard!')
end
end
end
context 'for a single discussion' do
describe '#execute' do
let(:merge_request) { create(:merge_request, title: "Hello world", source_project: project) }
let(:discussion) { create(:diff_note_on_merge_request, project: project, noteable: merge_request, note: "Almost done").to_discussion }
let(:service) { described_class.new(project, user, merge_request_to_resolve_discussions_of: merge_request.iid, discussion_to_resolve: discussion.id) }
context 'with an issue template' do
let(:project) { create(:project, :repository, issues_template: 'Work hard, play hard!') }
it 'picks the discussion description over the issue template' do
issue = service.execute
expect(issue.description).to include('Almost done')
end
end
end
end
end
require 'spec_helper'
describe MergeRequests::BuildService, services: true do # rubocop:disable RSpec/FilePath
let(:source_project) { project }
let(:target_project) { project }
let(:user) { create(:user) }
let(:description) { nil }
let(:source_branch) { 'feature-branch' }
let(:target_branch) { 'master' }
let(:merge_request) { service.execute }
let(:compare) { double(:compare, commits: commits) }
let(:commit_1) { double(:commit_1, safe_message: "Initial commit\n\nCreate the app") }
let(:commit_2) { double(:commit_2, safe_message: 'This is a bad commit message!') }
let(:commits) { nil }
let(:service) do
MergeRequests::BuildService.new(project, user,
description: description,
source_branch: source_branch,
target_branch: target_branch,
source_project: source_project,
target_project: target_project)
end
before do
allow(service).to receive(:branches_valid?) { true }
end
context 'project default template configured' do
let(:template) { "I am the template, you fill me in" }
let(:project) { create(:empty_project, merge_requests_template: template) }
context 'issuable default templates feature not available' do
before do
stub_licensed_features(issuable_default_templates: false)
end
it 'does not set the MR description from template' do
expect(merge_request.description).not_to eq(template)
end
end
context 'issuable default templates feature available' do
before do
stub_licensed_features(issuable_default_templates: true)
end
it 'sets the MR description from template' do
expect(merge_request.description).to eq(template)
end
end
end
end
require 'spec_helper'
describe Projects::CreateService, '#execute', services: true do
let(:user) { create :user }
let(:opts) do
{
name: "GitLab",
namespace: user.namespace
}
end
context 'repository_size_limit assignment as Bytes' do
let(:admin_user) { create(:user, admin: true) }
context 'when param present' do
let(:opts) { { repository_size_limit: '100' } }
it 'assign repository_size_limit as Bytes' do
project = create_project(admin_user, opts)
expect(project.repository_size_limit).to eql(100 * 1024 * 1024)
end
end
context 'when param not present' do
let(:opts) { { repository_size_limit: '' } }
it 'assign nil value' do
project = create_project(admin_user, opts)
expect(project.repository_size_limit).to be_nil
end
end
end
context 'git hook sample' do
let!(:sample) { create(:push_rule_sample) }
subject(:push_rule) { create_project(user, opts).push_rule }
it 'creates git hook from sample' do
is_expected.to have_attributes(
force_push_regex: sample.force_push_regex,
deny_delete_tag: sample.deny_delete_tag,
delete_branch_regex: sample.delete_branch_regex,
commit_message_regex: sample.commit_message_regex
)
end
context 'push rules unlicensed' do
before do
stub_licensed_features(push_rules: false)
end
it 'ignores the push rule sample' do
is_expected.to be_nil
end
end
end
def create_project(user, opts)
Projects::CreateService.new(user, opts).execute
end
end
......@@ -8,19 +8,6 @@ describe Issues::BuildService, services: true do
project.team << [user, :developer]
end
context 'with an issue template' do
describe '#execute' do
it 'fills in the template in the description' do
project = build(:project, issues_template: 'Work hard, play hard!')
service = described_class.new(project, user)
issue = service.execute
expect(issue.description).to eq('Work hard, play hard!')
end
end
end
context 'for a single discussion' do
describe '#execute' do
let(:merge_request) { create(:merge_request, title: "Hello world", source_project: project) }
......@@ -38,16 +25,6 @@ describe Issues::BuildService, services: true do
expect(issue.description).to include('Almost done')
end
context 'with an issue template' do
let(:project) { create(:project, :repository, issues_template: 'Work hard, play hard!') }
it 'picks the discussion description over the issue template' do
issue = service.execute
expect(issue.description).to include('Almost done')
end
end
end
end
......
......@@ -242,6 +242,16 @@ describe MergeRequests::MergeService, services: true do
end
describe '#hooks_validation_pass?' do
shared_examples 'hook validations are skipped when push rules unlicensed' do
subject { service.hooks_validation_pass?(merge_request) }
before do
stub_licensed_features(push_rules: false)
end
it { is_expected.to be_truthy }
end
let(:service) { MergeRequests::MergeService.new(project, user, commit_message: 'Awesome message') }
it 'returns true when valid' do
......@@ -253,6 +263,8 @@ describe MergeRequests::MergeService, services: true do
allow(project).to receive(:push_rule) { build(:push_rule, commit_message_regex: 'unmatched pattern .*') }
end
it_behaves_like 'hook validations are skipped when push rules unlicensed'
it 'returns false and saves error when invalid' do
expect(service.hooks_validation_pass?(merge_request)).to be_falsey
expect(merge_request.merge_error).not_to be_empty
......@@ -264,6 +276,8 @@ describe MergeRequests::MergeService, services: true do
allow(project).to receive(:push_rule) { build(:push_rule, author_email_regex: '.*@unmatchedemaildomain.com') }
end
it_behaves_like 'hook validations are skipped when push rules unlicensed'
it 'returns false and saves error when invalid' do
expect(service.hooks_validation_pass?(merge_request)).to be_falsey
expect(merge_request.merge_error).not_to be_empty
......
......@@ -110,30 +110,6 @@ describe Projects::CreateService, '#execute', services: true do
end
end
context 'repository_size_limit assignment as Bytes' do
let(:admin_user) { create(:user, admin: true) }
context 'when param present' do
let(:opts) { { repository_size_limit: '100' } }
it 'assign repository_size_limit as Bytes' do
project = create_project(admin_user, opts)
expect(project.repository_size_limit).to eql(100 * 1024 * 1024)
end
end
context 'when param not present' do
let(:opts) { { repository_size_limit: '' } }
it 'assign nil value' do
project = create_project(admin_user, opts)
expect(project.repository_size_limit).to be_nil
end
end
end
context 'restricted visibility level' do
before do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
......@@ -161,18 +137,6 @@ describe Projects::CreateService, '#execute', services: true do
end
end
context 'git hook sample' do
it 'creates git hook from sample' do
push_rule_sample = create(:push_rule_sample)
push_rule = create_project(user, opts).push_rule
[:force_push_regex, :deny_delete_tag, :delete_branch_regex, :commit_message_regex].each do |attr_name|
expect(push_rule.send(attr_name)).to eq push_rule_sample.send(attr_name)
end
end
end
context 'repository creation' do
it 'synchronously creates the repository' do
expect_any_instance_of(Project).to receive(:create_repository)
......
......@@ -9,6 +9,9 @@ module EE
# This enables `geo` and disables `deploy_board` features for a spec.
# Other features are still enabled/disabled as defined in the licence.
def stub_licensed_features(features)
unknown_features = features.keys - License::FEATURE_CODES.keys
raise "Unknown features: #{unknown_features.inspect}" unless unknown_features.empty?
allow(License).to receive(:feature_available?).and_call_original
features.each do |feature, enabled|
......
require 'spec_helper'
describe 'layouts/nav/_group' do
before do
assign(:group, create(:group))
end
describe 'contribution analytics tab' do
it 'is not visible when there is no valid license' do
stub_licensed_features(contribution_analytics: false)
render
expect(rendered).not_to have_text 'Contribution Analytics'
end
it 'is not visible when there is no valid license' do
stub_licensed_features(contribution_analytics: true)
render
expect(rendered).to have_text 'Contribution Analytics'
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