Commit 1ca6581d authored by Martin Hanzel's avatar Martin Hanzel

Merge branch 'master' into mh/editor-indents-ee

parents fac8f949 e10c552d
...@@ -262,25 +262,6 @@ Naming/HeredocDelimiterNaming: ...@@ -262,25 +262,6 @@ Naming/HeredocDelimiterNaming:
Naming/RescuedExceptionsVariableName: Naming/RescuedExceptionsVariableName:
Enabled: false Enabled: false
# Offense count: 6
# Cop supports --auto-correct.
Performance/InefficientHashSearch:
Exclude:
- 'app/controllers/concerns/sessionless_authentication.rb'
- 'app/models/note.rb'
- 'app/models/user_preference.rb'
- 'ee/app/models/ee/project.rb'
- 'lib/gitlab/import_export/members_mapper.rb'
- 'qa/spec/spec_helper.rb'
# Offense count: 3
# Cop supports --auto-correct.
Performance/ReverseEach:
Exclude:
- 'app/models/commit.rb'
- 'db/migrate/20190222051615_add_indexes_for_merge_request_diffs_query.rb'
- 'lib/gitlab/profiler.rb'
# Offense count: 7081 # Offense count: 7081
# Configuration parameters: Prefixes. # Configuration parameters: Prefixes.
# Prefixes: when, with, without # Prefixes: when, with, without
......
import $ from 'jquery'; import $ from 'jquery';
import { pluralize } from './lib/utils/text_utility'; import { n__ } from '~/locale';
import { localTimeAgo } from './lib/utils/datetime_utility'; import { localTimeAgo } from './lib/utils/datetime_utility';
import Pager from './pager'; import Pager from './pager';
import axios from './lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
...@@ -90,9 +90,10 @@ export default class CommitsList { ...@@ -90,9 +90,10 @@ export default class CommitsList {
.first() .first()
.find('li.commit').length, .find('li.commit').length,
); );
$commitsHeadersLast $commitsHeadersLast
.find('span.commits-count') .find('span.commits-count')
.text(`${commitsCount} ${pluralize('commit', commitsCount)}`); .text(n__('%d commit', '%d commits', commitsCount));
} }
localTimeAgo($processedData.find('.js-timeago')); localTimeAgo($processedData.find('.js-timeago'));
......
...@@ -8,22 +8,26 @@ export default class CycleAnalyticsService { ...@@ -8,22 +8,26 @@ export default class CycleAnalyticsService {
} }
fetchCycleAnalyticsData(options = { startDate: 30 }) { fetchCycleAnalyticsData(options = { startDate: 30 }) {
const { startDate, projectIds } = options;
return this.axios return this.axios
.get('', { .get('', {
params: { params: {
'cycle_analytics[start_date]': options.startDate, 'cycle_analytics[start_date]': startDate,
'cycle_analytics[project_ids]': projectIds,
}, },
}) })
.then(x => x.data); .then(x => x.data);
} }
fetchStageData(options) { fetchStageData(options) {
const { stage, startDate } = options; const { stage, startDate, projectIds } = options;
return this.axios return this.axios
.get(`events/${stage.name}.json`, { .get(`events/${stage.name}.json`, {
params: { params: {
'cycle_analytics[start_date]': startDate, 'cycle_analytics[start_date]': startDate,
'cycle_analytics[project_ids]': projectIds,
}, },
}) })
.then(x => x.data); .then(x => x.data);
......
<script> <script>
import { n__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import { pluralize, truncate } from '~/lib/utils/text_utility'; import { truncate } from '~/lib/utils/text_utility';
import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue'; import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
import { GlTooltipDirective } from '@gitlab/ui'; import { GlTooltipDirective } from '@gitlab/ui';
import { COUNT_OF_AVATARS_IN_GUTTER, LENGTH_OF_AVATAR_TOOLTIP } from '../constants'; import { COUNT_OF_AVATARS_IN_GUTTER, LENGTH_OF_AVATAR_TOOLTIP } from '../constants';
...@@ -42,7 +43,7 @@ export default { ...@@ -42,7 +43,7 @@ export default {
return ''; return '';
} }
return pluralize(`${this.moreCount} more comment`, this.moreCount); return n__('%d more comment', '%d more comments', this.moreCount);
}, },
}, },
methods: { methods: {
......
...@@ -2,8 +2,7 @@ import $ from 'jquery'; ...@@ -2,8 +2,7 @@ import $ from 'jquery';
import _ from 'underscore'; import _ from 'underscore';
import timeago from 'timeago.js'; import timeago from 'timeago.js';
import dateFormat from 'dateformat'; import dateFormat from 'dateformat';
import { pluralize } from './text_utility'; import { languageCode, s__, __, n__ } from '../../locale';
import { languageCode, s__, __ } from '../../locale';
window.timeago = timeago; window.timeago = timeago;
...@@ -231,14 +230,10 @@ export const timeIntervalInWords = intervalInSeconds => { ...@@ -231,14 +230,10 @@ export const timeIntervalInWords = intervalInSeconds => {
const secondsInteger = parseInt(intervalInSeconds, 10); const secondsInteger = parseInt(intervalInSeconds, 10);
const minutes = Math.floor(secondsInteger / 60); const minutes = Math.floor(secondsInteger / 60);
const seconds = secondsInteger - minutes * 60; const seconds = secondsInteger - minutes * 60;
let text = ''; const secondsText = n__('%d second', '%d seconds', seconds);
return minutes >= 1
if (minutes >= 1) { ? [n__('%d minute', '%d minutes', minutes), secondsText].join(' ')
text = `${minutes} ${pluralize('minute', minutes)} ${seconds} ${pluralize('second', seconds)}`; : secondsText;
} else {
text = `${seconds} ${pluralize('second', seconds)}`;
}
return text;
}; };
export const dateInWords = (date, abbreviated = false, hideYear = false) => { export const dateInWords = (date, abbreviated = false, hideYear = false) => {
......
...@@ -28,14 +28,6 @@ export const highCountTrim = count => (count > 99 ? '99+' : count); ...@@ -28,14 +28,6 @@ export const highCountTrim = count => (count > 99 ? '99+' : count);
export const humanize = string => export const humanize = string =>
string.charAt(0).toUpperCase() + string.replace(/_/g, ' ').slice(1); string.charAt(0).toUpperCase() + string.replace(/_/g, ' ').slice(1);
/**
* Adds an 's' to the end of the string when count is bigger than 0
* @param {String} str
* @param {Number} count
* @returns {String}
*/
export const pluralize = (str, count) => str + (count > 1 || count === 0 ? 's' : '');
/** /**
* Replaces underscores with dashes * Replaces underscores with dashes
* @param {*} str * @param {*} str
......
.tooltip-inner { .tooltip-inner {
font-size: $tooltip-font-size; font-size: $gl-font-size-small;
border-radius: $border-radius-default; border-radius: $border-radius-default;
line-height: 16px; line-height: $gl-line-height;
font-weight: $gl-font-weight-normal; font-weight: $gl-font-weight-normal;
padding: 8px;
} }
...@@ -48,3 +48,7 @@ $spacers: ( ...@@ -48,3 +48,7 @@ $spacers: (
9: ($spacer * 8) 9: ($spacer * 8)
); );
$pagination-color: $gl-text-color; $pagination-color: $gl-text-color;
$tooltip-padding-y: 0.5rem;
$tooltip-padding-x: 0.75rem;
$tooltip-arrow-height: 0.5rem;
$tooltip-arrow-width: 1rem;
...@@ -106,6 +106,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -106,6 +106,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:lets_encrypt_notification_email, :lets_encrypt_notification_email,
:lets_encrypt_terms_of_service_accepted, :lets_encrypt_terms_of_service_accepted,
:domain_blacklist_file, :domain_blacklist_file,
:raw_blob_request_limit,
disabled_oauth_sign_in_sources: [], disabled_oauth_sign_in_sources: [],
import_sources: [], import_sources: [],
repository_storages: [], repository_storages: [],
......
...@@ -13,7 +13,7 @@ module SessionlessAuthentication ...@@ -13,7 +13,7 @@ module SessionlessAuthentication
end end
def sessionless_user? def sessionless_user?
current_user && !session.keys.include?('warden.user.user.key') current_user && !session.key?('warden.user.user.key')
end end
def sessionless_sign_in(user) def sessionless_sign_in(user)
......
...@@ -10,7 +10,7 @@ class Import::GithubController < Import::BaseController ...@@ -10,7 +10,7 @@ class Import::GithubController < Import::BaseController
rescue_from Octokit::Unauthorized, with: :provider_unauthorized rescue_from Octokit::Unauthorized, with: :provider_unauthorized
def new def new
if github_import_configured? && logged_in_with_provider? if !ci_cd_only? && github_import_configured? && logged_in_with_provider?
go_to_provider_for_permissions go_to_provider_for_permissions
elsif session[access_token_key] elsif session[access_token_key]
redirect_to status_import_url redirect_to status_import_url
...@@ -169,11 +169,15 @@ class Import::GithubController < Import::BaseController ...@@ -169,11 +169,15 @@ class Import::GithubController < Import::BaseController
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
def provider_auth def provider_auth
if session[access_token_key].blank? if !ci_cd_only? && session[access_token_key].blank?
go_to_provider_for_permissions go_to_provider_for_permissions
end end
end end
def ci_cd_only?
%w[1 true].include?(params[:ci_cd_only])
end
def client_options def client_options
{} {}
end end
......
...@@ -8,10 +8,30 @@ class Projects::RawController < Projects::ApplicationController ...@@ -8,10 +8,30 @@ class Projects::RawController < Projects::ApplicationController
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :assign_ref_vars before_action :assign_ref_vars
before_action :authorize_download_code! before_action :authorize_download_code!
before_action :show_rate_limit, only: [:show]
def show def show
@blob = @repository.blob_at(@commit.id, @path) @blob = @repository.blob_at(@commit.id, @path)
send_blob(@repository, @blob, inline: (params[:inline] != 'false')) send_blob(@repository, @blob, inline: (params[:inline] != 'false'))
end end
private
def show_rate_limit
limiter = ::Gitlab::ActionRateLimiter.new(action: :show_raw_controller)
return unless limiter.throttled?([@project, @commit, @path], raw_blob_request_limit)
limiter.log_request(request, :raw_blob_request_limit, current_user)
flash[:alert] = _('You cannot access the raw file. Please wait a minute.')
redirect_to project_blob_path(@project, File.join(@ref, @path))
end
def raw_blob_request_limit
Gitlab::CurrentSettings
.current_application_settings
.raw_blob_request_limit
end
end end
...@@ -177,6 +177,7 @@ module ApplicationSettingsHelper ...@@ -177,6 +177,7 @@ module ApplicationSettingsHelper
:domain_blacklist_enabled, :domain_blacklist_enabled,
:domain_blacklist_raw, :domain_blacklist_raw,
:domain_whitelist_raw, :domain_whitelist_raw,
:outbound_local_requests_whitelist_raw,
:dsa_key_restriction, :dsa_key_restriction,
:ecdsa_key_restriction, :ecdsa_key_restriction,
:ed25519_key_restriction, :ed25519_key_restriction,
......
...@@ -52,7 +52,7 @@ module AvatarsHelper ...@@ -52,7 +52,7 @@ module AvatarsHelper
user: commit_or_event.author, user: commit_or_event.author,
user_name: commit_or_event.author_name, user_name: commit_or_event.author_name,
user_email: commit_or_event.author_email, user_email: commit_or_event.author_email,
css_class: 'd-none d-sm-inline' css_class: 'd-none d-sm-inline-block'
})) }))
end end
......
...@@ -4,11 +4,10 @@ module LabelsHelper ...@@ -4,11 +4,10 @@ module LabelsHelper
extend self extend self
include ActionView::Helpers::TagHelper include ActionView::Helpers::TagHelper
def show_label_issuables_link?(label, issuables_type, current_user: nil, project: nil) def show_label_issuables_link?(label, issuables_type, current_user: nil)
return true unless label.project_label? return true unless label.project_label?
return true unless project
project.feature_available?(issuables_type, current_user) label.project.feature_available?(issuables_type, current_user)
end end
# Link to a Label # Link to a Label
......
...@@ -41,6 +41,11 @@ class ApplicationSetting < ApplicationRecord ...@@ -41,6 +41,11 @@ class ApplicationSetting < ApplicationRecord
validates :uuid, presence: true validates :uuid, presence: true
validates :outbound_local_requests_whitelist,
length: { maximum: 1_000, message: N_('is too long (maximum is 1000 entries)') }
validates :outbound_local_requests_whitelist, qualified_domain_array: true, allow_blank: true
validates :session_expire_delay, validates :session_expire_delay,
presence: true, presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 0 } numericality: { only_integer: true, greater_than_or_equal_to: 0 }
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
module ApplicationSettingImplementation module ApplicationSettingImplementation
extend ActiveSupport::Concern extend ActiveSupport::Concern
include Gitlab::Utils::StrongMemoize
DOMAIN_LIST_SEPARATOR = %r{\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace DOMAIN_LIST_SEPARATOR = %r{\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace
| # or | # or
...@@ -96,7 +97,9 @@ module ApplicationSettingImplementation ...@@ -96,7 +97,9 @@ module ApplicationSettingImplementation
diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES, diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES,
commit_email_hostname: default_commit_email_hostname, commit_email_hostname: default_commit_email_hostname,
protected_ci_variables: false, protected_ci_variables: false,
local_markdown_version: 0 local_markdown_version: 0,
outbound_local_requests_whitelist: [],
raw_blob_request_limit: 300
} }
end end
...@@ -131,31 +134,52 @@ module ApplicationSettingImplementation ...@@ -131,31 +134,52 @@ module ApplicationSettingImplementation
end end
def domain_whitelist_raw def domain_whitelist_raw
self.domain_whitelist&.join("\n") array_to_string(self.domain_whitelist)
end end
def domain_blacklist_raw def domain_blacklist_raw
self.domain_blacklist&.join("\n") array_to_string(self.domain_blacklist)
end end
def domain_whitelist_raw=(values) def domain_whitelist_raw=(values)
self.domain_whitelist = [] self.domain_whitelist = domain_strings_to_array(values)
self.domain_whitelist = values.split(DOMAIN_LIST_SEPARATOR)
self.domain_whitelist.reject! { |d| d.empty? }
self.domain_whitelist
end end
def domain_blacklist_raw=(values) def domain_blacklist_raw=(values)
self.domain_blacklist = [] self.domain_blacklist = domain_strings_to_array(values)
self.domain_blacklist = values.split(DOMAIN_LIST_SEPARATOR)
self.domain_blacklist.reject! { |d| d.empty? }
self.domain_blacklist
end end
def domain_blacklist_file=(file) def domain_blacklist_file=(file)
self.domain_blacklist_raw = file.read self.domain_blacklist_raw = file.read
end end
def outbound_local_requests_whitelist_raw
array_to_string(self.outbound_local_requests_whitelist)
end
def outbound_local_requests_whitelist_raw=(values)
self.outbound_local_requests_whitelist = domain_strings_to_array(values)
end
def outbound_local_requests_whitelist_arrays
strong_memoize(:outbound_local_requests_whitelist_arrays) do
ip_whitelist = []
domain_whitelist = []
self.outbound_local_requests_whitelist.each do |str|
ip_obj = Gitlab::Utils.string_to_ip_object(str)
if ip_obj
ip_whitelist << ip_obj
else
domain_whitelist << str
end
end
[ip_whitelist, domain_whitelist]
end
end
def repository_storages def repository_storages
Array(read_attribute(:repository_storages)) Array(read_attribute(:repository_storages))
end end
...@@ -255,6 +279,17 @@ module ApplicationSettingImplementation ...@@ -255,6 +279,17 @@ module ApplicationSettingImplementation
private private
def array_to_string(arr)
arr&.join("\n")
end
def domain_strings_to_array(values)
values
.split(DOMAIN_LIST_SEPARATOR)
.reject(&:empty?)
.uniq
end
def ensure_uuid! def ensure_uuid!
return if uuid? return if uuid?
......
...@@ -346,7 +346,7 @@ class Commit ...@@ -346,7 +346,7 @@ class Commit
if commits_in_merge_request.present? if commits_in_merge_request.present?
message_body << "" message_body << ""
commits_in_merge_request.reverse.each do |commit_in_merge| commits_in_merge_request.reverse_each do |commit_in_merge|
message_body << "#{commit_in_merge.short_id} #{commit_in_merge.title}" message_body << "#{commit_in_merge.short_id} #{commit_in_merge.title}"
end end
end end
......
# frozen_string_literal: true # frozen_string_literal: true
# This module makes it possible to handle items as a list, where the order of items can be easily altered
# Requirements:
#
# - Only works for ActiveRecord models
# - relative_position integer field must present on the model
# - This module uses GROUP BY: the model should have a parent relation, example: project -> issues, project is the parent relation (issues table has a parent_id column)
#
# Setup like this in the body of your class:
#
# include RelativePositioning
#
# # base query used for the position calculation
# def self.relative_positioning_query_base(issue)
# where(deleted: false)
# end
#
# # column that should be used in GROUP BY
# def self.relative_positioning_parent_column
# :project_id
# end
#
module RelativePositioning module RelativePositioning
extend ActiveSupport::Concern extend ActiveSupport::Concern
...@@ -93,7 +114,7 @@ module RelativePositioning ...@@ -93,7 +114,7 @@ module RelativePositioning
return move_after(before) unless after return move_after(before) unless after
return move_before(after) unless before return move_before(after) unless before
# If there is no place to insert an issue we need to create one by moving the before issue closer # If there is no place to insert an item we need to create one by moving the before item closer
# to its predecessor. This process will recursively move all the predecessors until we have a place # to its predecessor. This process will recursively move all the predecessors until we have a place
if (after.relative_position - before.relative_position) < 2 if (after.relative_position - before.relative_position) < 2
before.move_before before.move_before
...@@ -108,11 +129,11 @@ module RelativePositioning ...@@ -108,11 +129,11 @@ module RelativePositioning
pos_after = before.next_relative_position pos_after = before.next_relative_position
if before.shift_after? if before.shift_after?
issue_to_move = self.class.in_parents(parent_ids).find_by!(relative_position: pos_after) item_to_move = self.class.relative_positioning_query_base(self).find_by!(relative_position: pos_after)
issue_to_move.move_after item_to_move.move_after
@positionable_neighbours = [issue_to_move] # rubocop:disable Gitlab/ModuleWithInstanceVariables @positionable_neighbours = [item_to_move] # rubocop:disable Gitlab/ModuleWithInstanceVariables
pos_after = issue_to_move.relative_position pos_after = item_to_move.relative_position
end end
self.relative_position = self.class.position_between(pos_before, pos_after) self.relative_position = self.class.position_between(pos_before, pos_after)
...@@ -123,11 +144,11 @@ module RelativePositioning ...@@ -123,11 +144,11 @@ module RelativePositioning
pos_before = after.prev_relative_position pos_before = after.prev_relative_position
if after.shift_before? if after.shift_before?
issue_to_move = self.class.in_parents(parent_ids).find_by!(relative_position: pos_before) item_to_move = self.class.relative_positioning_query_base(self).find_by!(relative_position: pos_before)
issue_to_move.move_before item_to_move.move_before
@positionable_neighbours = [issue_to_move] # rubocop:disable Gitlab/ModuleWithInstanceVariables @positionable_neighbours = [item_to_move] # rubocop:disable Gitlab/ModuleWithInstanceVariables
pos_before = issue_to_move.relative_position pos_before = item_to_move.relative_position
end end
self.relative_position = self.class.position_between(pos_before, pos_after) self.relative_position = self.class.position_between(pos_before, pos_after)
...@@ -141,13 +162,13 @@ module RelativePositioning ...@@ -141,13 +162,13 @@ module RelativePositioning
self.relative_position = self.class.position_between(min_relative_position || START_POSITION, MIN_POSITION) self.relative_position = self.class.position_between(min_relative_position || START_POSITION, MIN_POSITION)
end end
# Indicates if there is an issue that should be shifted to free the place # Indicates if there is an item that should be shifted to free the place
def shift_after? def shift_after?
next_pos = next_relative_position next_pos = next_relative_position
next_pos && (next_pos - relative_position) == 1 next_pos && (next_pos - relative_position) == 1
end end
# Indicates if there is an issue that should be shifted to free the place # Indicates if there is an item that should be shifted to free the place
def shift_before? def shift_before?
prev_pos = prev_relative_position prev_pos = prev_relative_position
prev_pos && (relative_position - prev_pos) == 1 prev_pos && (relative_position - prev_pos) == 1
...@@ -159,7 +180,7 @@ module RelativePositioning ...@@ -159,7 +180,7 @@ module RelativePositioning
def save_positionable_neighbours def save_positionable_neighbours
return unless @positionable_neighbours return unless @positionable_neighbours
status = @positionable_neighbours.all? { |issue| issue.save(touch: false) } status = @positionable_neighbours.all? { |item| item.save(touch: false) }
@positionable_neighbours = nil @positionable_neighbours = nil
status status
...@@ -170,16 +191,15 @@ module RelativePositioning ...@@ -170,16 +191,15 @@ module RelativePositioning
# When calculating across projects, this is much more efficient than # When calculating across projects, this is much more efficient than
# MAX(relative_position) without the GROUP BY, due to index usage: # MAX(relative_position) without the GROUP BY, due to index usage:
# https://gitlab.com/gitlab-org/gitlab-ce/issues/54276#note_119340977 # https://gitlab.com/gitlab-org/gitlab-ce/issues/54276#note_119340977
relation = self.class relation = self.class.relative_positioning_query_base(self)
.in_parents(parent_ids)
.order(Gitlab::Database.nulls_last_order('position', 'DESC')) .order(Gitlab::Database.nulls_last_order('position', 'DESC'))
.group(self.class.relative_positioning_parent_column)
.limit(1) .limit(1)
.group(self.class.parent_column)
relation = yield relation if block_given? relation = yield relation if block_given?
relation relation
.pluck(self.class.parent_column, Arel.sql("#{calculation}(relative_position) AS position")) .pluck(self.class.relative_positioning_parent_column, Arel.sql("#{calculation}(relative_position) AS position"))
.first&. .first&.
last last
end end
......
...@@ -86,4 +86,9 @@ class ContainerRepository < ApplicationRecord ...@@ -86,4 +86,9 @@ class ContainerRepository < ApplicationRecord
def self.build_root_repository(project) def self.build_root_repository(project)
self.new(project: project, name: '') self.new(project: project, name: '')
end end
def self.find_by_path!(path)
self.find_by!(project: path.repository_project,
name: path.repository_name)
end
end end
...@@ -91,11 +91,11 @@ class Issue < ApplicationRecord ...@@ -91,11 +91,11 @@ class Issue < ApplicationRecord
end end
end end
class << self def self.relative_positioning_query_base(issue)
alias_method :in_parents, :in_projects in_projects(issue.parent_ids)
end end
def self.parent_column def self.relative_positioning_parent_column
:project_id :project_id
end end
......
...@@ -33,7 +33,7 @@ class Label < ApplicationRecord ...@@ -33,7 +33,7 @@ class Label < ApplicationRecord
default_scope { order(title: :asc) } default_scope { order(title: :asc) }
scope :templates, -> { where(template: true) } scope :templates, -> { where(template: true, type: [Label.name, nil]) }
scope :with_title, ->(title) { where(title: title) } scope :with_title, ->(title) { where(title: title) }
scope :with_lists_and_board, -> { joins(lists: :board).merge(List.movable) } scope :with_lists_and_board, -> { joins(lists: :board).merge(List.movable) }
scope :on_project_boards, ->(project_id) { with_lists_and_board.where(boards: { project_id: project_id }) } scope :on_project_boards, ->(project_id) { with_lists_and_board.where(boards: { project_id: project_id }) }
......
...@@ -292,7 +292,7 @@ class Note < ApplicationRecord ...@@ -292,7 +292,7 @@ class Note < ApplicationRecord
end end
def special_role=(role) def special_role=(role)
raise "Role is undefined, #{role} not found in #{SpecialRole.values}" unless SpecialRole.values.include?(role) raise "Role is undefined, #{role} not found in #{SpecialRole.values}" unless SpecialRole.value?(role)
@special_role = role @special_role = role
end end
......
...@@ -1862,16 +1862,24 @@ class Project < ApplicationRecord ...@@ -1862,16 +1862,24 @@ class Project < ApplicationRecord
end end
def append_or_update_attribute(name, value) def append_or_update_attribute(name, value)
old_values = public_send(name.to_s) # rubocop:disable GitlabSecurity/PublicSend if Project.reflect_on_association(name).try(:macro) == :has_many
# if this is 1-to-N relation, update the parent object
value.each do |item|
item.update!(
Project.reflect_on_association(name).foreign_key => id)
end
# force to drop relation cache
public_send(name).reset # rubocop:disable GitlabSecurity/PublicSend
if Project.reflect_on_association(name).try(:macro) == :has_many && old_values.any? # succeeded
update_attribute(name, old_values + value) true
else else
# if this is another relation or attribute, update just object
update_attribute(name, value) update_attribute(name, value)
end end
rescue ActiveRecord::RecordInvalid => e
rescue ActiveRecord::RecordNotSaved => e raise e, "Failed to set #{name}: #{e.message}"
handle_update_attribute_error(e, value)
end end
# Tries to set repository as read_only, checking for existing Git transfers in progress beforehand # Tries to set repository as read_only, checking for existing Git transfers in progress beforehand
...@@ -2260,18 +2268,6 @@ class Project < ApplicationRecord ...@@ -2260,18 +2268,6 @@ class Project < ApplicationRecord
ContainerRepository.build_root_repository(self).has_tags? ContainerRepository.build_root_repository(self).has_tags?
end end
def handle_update_attribute_error(ex, value)
if ex.message.start_with?('Failed to replace')
if value.respond_to?(:each)
invalid = value.detect(&:invalid?)
raise ex, ([ex.message] + invalid.errors.full_messages).join(' ') if invalid
end
end
raise ex
end
def fetch_branch_allows_collaboration(user, branch_name = nil) def fetch_branch_allows_collaboration(user, branch_name = nil)
return false unless user return false unless user
......
# frozen_string_literal: true # frozen_string_literal: true
class ProjectAutoDevops < ApplicationRecord class ProjectAutoDevops < ApplicationRecord
include IgnorableColumn
ignore_column :domain
belongs_to :project, inverse_of: :auto_devops belongs_to :project, inverse_of: :auto_devops
enum deploy_strategy: { enum deploy_strategy: {
......
...@@ -26,7 +26,7 @@ class UserPreference < ApplicationRecord ...@@ -26,7 +26,7 @@ class UserPreference < ApplicationRecord
def set_notes_filter(filter_id, issuable) def set_notes_filter(filter_id, issuable)
# No need to update the column if the value is already set. # No need to update the column if the value is already set.
if filter_id && NOTES_FILTERS.values.include?(filter_id) if filter_id && NOTES_FILTERS.value?(filter_id)
field = notes_filter_field_for(issuable) field = notes_filter_field_for(issuable)
self[field] = filter_id self[field] = filter_id
......
...@@ -8,6 +8,13 @@ ...@@ -8,6 +8,13 @@
= f.label :allow_local_requests_from_hooks_and_services, class: 'form-check-label' do = f.label :allow_local_requests_from_hooks_and_services, class: 'form-check-label' do
Allow requests to the local network from hooks and services Allow requests to the local network from hooks and services
.form-group
= f.label :outbound_local_requests_whitelist_raw, class: 'label-bold' do
= _('Whitelist to allow requests to the local network from hooks and services')
= f.text_area :outbound_local_requests_whitelist_raw, placeholder: "example.com, 192.168.1.1", class: 'form-control', rows: 8
%span.form-text.text-muted
= _('Requests to these domain(s)/address(es) on the local network will be allowed when local requests from hooks and services are disabled. IP ranges such as 1:0:0:0:0:0:0:0/124 or 127.0.0.0/28 are supported. Domain wildcards are not supported currently. Use comma, semicolon, or newline to separate multiple entries. The whitelist can hold a maximum of 4000 entries. Domains should use IDNA encoding. Ex: domain.com, 192.168.1.1, 127.0.0.0/28.')
.form-group .form-group
.form-check .form-check
= f.check_box :dns_rebinding_protection_enabled, class: 'form-check-input' = f.check_box :dns_rebinding_protection_enabled, class: 'form-check-input'
......
...@@ -15,4 +15,10 @@ ...@@ -15,4 +15,10 @@
AuthorizedKeysCommand. Click on the help icon for more details. AuthorizedKeysCommand. Click on the help icon for more details.
= link_to icon('question-circle'), help_page_path('administration/operations/fast_ssh_key_lookup') = link_to icon('question-circle'), help_page_path('administration/operations/fast_ssh_key_lookup')
.form-group
= f.label :raw_blob_request_limit, _('Raw blob request rate limit per minute'), class: 'label-bold'
= f.number_field :raw_blob_request_limit, class: 'form-control'
.form-text.text-muted
= _('Highest number of requests per minute for each raw path, default to 300. To disable throttling set to 0.')
= f.submit 'Save changes', class: "btn btn-success" = f.submit 'Save changes', class: "btn btn-success"
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
.branch .branch
= merge_path_description(@merge_request, 'to') = merge_path_description(@merge_request, 'to')
.author .author
Author #{@merge_request.author_name} Author: #{@merge_request.author_name}
.assignee .assignee
= assignees_label(@merge_request) = assignees_label(@merge_request)
.approvers .approvers
......
- local_assigns.fetch(:view)
%span.bold
%span{ title: _('Invoke Time'), data: { defer_to: "#{view.defer_key}-gc_time" } }...
\/
%span{ title: _('Invoke Count'), data: { defer_to: "#{view.defer_key}-invokes" } }...
gc
- local_assigns.fetch(:view)
%span.bold
%span{ data: { defer_to: "#{view.defer_key}-duration" } }...
\/
%span{ data: { defer_to: "#{view.defer_key}-calls" } }...
redis
- local_assigns.fetch(:view)
%span.bold
%span{ data: { defer_to: "#{view.defer_key}-duration" } }...
\/
%span{ data: { defer_to: "#{view.defer_key}-calls" } }...
sidekiq
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
- page_title @tag.name, s_('TagsPage|Tags') - page_title @tag.name, s_('TagsPage|Tags')
%div{ class: container_class } %div{ class: container_class }
.top-area.multi-line .top-area.multi-line.flex-wrap
.nav-text .nav-text
.title .title
%span.item-title.ref-name %span.item-title.ref-name
......
- force_priority = local_assigns.fetch(:force_priority, false) - force_priority = local_assigns.fetch(:force_priority, false)
- subject_or_group_defined = defined?(@project) || defined?(@group) - subject_or_group_defined = defined?(@project) || defined?(@group)
- show_label_issues_link = subject_or_group_defined && show_label_issuables_link?(label, :issues, project: @project) - show_label_issues_link = subject_or_group_defined && show_label_issuables_link?(label, :issues)
- show_label_merge_requests_link = subject_or_group_defined && show_label_issuables_link?(label, :merge_requests, project: @project) - show_label_merge_requests_link = subject_or_group_defined && show_label_issuables_link?(label, :merge_requests)
.label-name .label-name
= render_label(label, tooltip: false) = render_label(label, tooltip: false)
......
---
title: Add Rate Request Limiter to RawController#show endpoint
merge_request: 30635
author:
type: added
---
title: Fix admin labels page when there are invalid records
merge_request: 30885
author:
type: fixed
---
title: Fixed distorted avatars when resource not reachable
merge_request: 30904
author: Marc Schwede
type: other
---
title: Fix tag page layout
merge_request:
author:
type: fixed
---
title: Use tablesample approximate counting by default.
merge_request: 31048
author:
type: performance
---
title: Add Outbound requests whitelist for local networks
merge_request: 30350
author: Istvan Szalai
type: added
---
title: Optimise import performance
merge_request: 31045
author:
type: performance
...@@ -400,6 +400,15 @@ production: &base ...@@ -400,6 +400,15 @@ production: &base
# path: shared/registry # path: shared/registry
# issuer: gitlab-issuer # issuer: gitlab-issuer
# Add notification settings if you plan to use Geo Replication for the registry
# notifications:
# - name: geo_event
# url: https://example.com/api/v4/container_registry_event/events
# timeout: 2s
# threshold: 5
# backoff: 1s
# headers:
# Authorization: secret_phrase
## Error Reporting and Logging with Sentry ## Error Reporting and Logging with Sentry
sentry: sentry:
......
...@@ -259,6 +259,7 @@ Settings.registry['key'] ||= nil ...@@ -259,6 +259,7 @@ Settings.registry['key'] ||= nil
Settings.registry['issuer'] ||= nil Settings.registry['issuer'] ||= nil
Settings.registry['host_port'] ||= [Settings.registry['host'], Settings.registry['port']].compact.join(':') Settings.registry['host_port'] ||= [Settings.registry['host'], Settings.registry['port']].compact.join(':')
Settings.registry['path'] = Settings.absolute(Settings.registry['path'] || File.join(Settings.shared['path'], 'registry')) Settings.registry['path'] = Settings.absolute(Settings.registry['path'] || File.join(Settings.shared['path'], 'registry'))
Settings.registry['notifications'] ||= []
# #
# Error Reporting and Logging with Sentry # Error Reporting and Logging with Sentry
......
# frozen_string_literal: true
class AddOutboundRequestsWhitelistToApplicationSettings < ActiveRecord::Migration[5.1]
DOWNTIME = false
def change
add_column :application_settings, :outbound_local_requests_whitelist, :string, array: true, limit: 255
end
end
...@@ -35,7 +35,7 @@ class AddIndexesForMergeRequestDiffsQuery < ActiveRecord::Migration[5.0] ...@@ -35,7 +35,7 @@ class AddIndexesForMergeRequestDiffsQuery < ActiveRecord::Migration[5.0]
end end
def down def down
INDEX_SPECS.reverse.each do |spec| INDEX_SPECS.reverse_each do |spec|
remove_concurrent_index(*spec) remove_concurrent_index(*spec)
end end
end end
......
# frozen_string_literal: true
class AddGeoContainerRepositoryUpdatedEventsTable < ActiveRecord::Migration[5.1]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
create_table :geo_container_repository_updated_events, force: :cascade do |t|
t.integer :container_repository_id, null: false
t.index :container_repository_id, name: :idx_geo_con_rep_updated_events_on_container_repository_id, using: :btree
end
add_column :geo_event_log, :container_repository_updated_event_id, :bigint
end
end
# frozen_string_literal: true
class AddIndexToGeoEventLog < ActiveRecord::Migration[5.1]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index :geo_event_log, :container_repository_updated_event_id
end
def down
remove_concurrent_index(:geo_event_log, :container_repository_updated_event_id)
end
end
# frozen_string_literal: true
class AddForeignKeysForContainerRepository < ActiveRecord::Migration[5.1]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_foreign_key(:geo_container_repository_updated_events, :container_repositories, column: :container_repository_id, on_delete: :cascade)
add_concurrent_foreign_key(:geo_event_log, :geo_container_repository_updated_events, column: :container_repository_updated_event_id, on_delete: :cascade)
end
def down
if foreign_key_exists?(:geo_container_repository_updated_events, :container_repositories)
remove_foreign_key(:geo_container_repository_updated_events, :container_repositories)
end
if foreign_key_exists?(:geo_event_log, :geo_container_repository_updated_events)
remove_foreign_key(:geo_event_log, :geo_container_repository_updated_events)
end
end
end
# frozen_string_literal: true
class AddRawBlobRequestLimitToApplicationSettings < ActiveRecord::Migration[5.2]
DOWNTIME = false
def change
add_column :application_settings, :raw_blob_request_limit, :integer, default: 300, null: false
end
end
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2019_07_15_114644) do ActiveRecord::Schema.define(version: 2019_07_15_142138) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm" enable_extension "pg_trgm"
...@@ -228,6 +228,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do ...@@ -228,6 +228,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.boolean "lock_memberships_to_ldap", default: false, null: false t.boolean "lock_memberships_to_ldap", default: false, null: false
t.boolean "time_tracking_limit_to_hours", default: false, null: false t.boolean "time_tracking_limit_to_hours", default: false, null: false
t.string "grafana_url", default: "/-/grafana", null: false t.string "grafana_url", default: "/-/grafana", null: false
t.string "outbound_local_requests_whitelist", limit: 255, array: true
t.integer "raw_blob_request_limit", default: 300, null: false
t.index ["custom_project_templates_group_id"], name: "index_application_settings_on_custom_project_templates_group_id" t.index ["custom_project_templates_group_id"], name: "index_application_settings_on_custom_project_templates_group_id"
t.index ["file_template_project_id"], name: "index_application_settings_on_file_template_project_id" t.index ["file_template_project_id"], name: "index_application_settings_on_file_template_project_id"
t.index ["usage_stats_set_by_user_id"], name: "index_application_settings_on_usage_stats_set_by_user_id" t.index ["usage_stats_set_by_user_id"], name: "index_application_settings_on_usage_stats_set_by_user_id"
...@@ -1275,6 +1277,11 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do ...@@ -1275,6 +1277,11 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "key", null: false t.string "key", null: false
end end
create_table "geo_container_repository_updated_events", force: :cascade do |t|
t.integer "container_repository_id", null: false
t.index ["container_repository_id"], name: "idx_geo_con_rep_updated_events_on_container_repository_id"
end
create_table "geo_event_log", force: :cascade do |t| create_table "geo_event_log", force: :cascade do |t|
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.bigint "repository_updated_event_id" t.bigint "repository_updated_event_id"
...@@ -1289,7 +1296,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do ...@@ -1289,7 +1296,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.bigint "job_artifact_deleted_event_id" t.bigint "job_artifact_deleted_event_id"
t.bigint "reset_checksum_event_id" t.bigint "reset_checksum_event_id"
t.bigint "cache_invalidation_event_id" t.bigint "cache_invalidation_event_id"
t.bigint "container_repository_updated_event_id"
t.index ["cache_invalidation_event_id"], name: "index_geo_event_log_on_cache_invalidation_event_id", where: "(cache_invalidation_event_id IS NOT NULL)" t.index ["cache_invalidation_event_id"], name: "index_geo_event_log_on_cache_invalidation_event_id", where: "(cache_invalidation_event_id IS NOT NULL)"
t.index ["container_repository_updated_event_id"], name: "index_geo_event_log_on_container_repository_updated_event_id"
t.index ["hashed_storage_attachments_event_id"], name: "index_geo_event_log_on_hashed_storage_attachments_event_id", where: "(hashed_storage_attachments_event_id IS NOT NULL)" t.index ["hashed_storage_attachments_event_id"], name: "index_geo_event_log_on_hashed_storage_attachments_event_id", where: "(hashed_storage_attachments_event_id IS NOT NULL)"
t.index ["hashed_storage_migrated_event_id"], name: "index_geo_event_log_on_hashed_storage_migrated_event_id", where: "(hashed_storage_migrated_event_id IS NOT NULL)" t.index ["hashed_storage_migrated_event_id"], name: "index_geo_event_log_on_hashed_storage_migrated_event_id", where: "(hashed_storage_migrated_event_id IS NOT NULL)"
t.index ["job_artifact_deleted_event_id"], name: "index_geo_event_log_on_job_artifact_deleted_event_id", where: "(job_artifact_deleted_event_id IS NOT NULL)" t.index ["job_artifact_deleted_event_id"], name: "index_geo_event_log_on_job_artifact_deleted_event_id", where: "(job_artifact_deleted_event_id IS NOT NULL)"
...@@ -3700,7 +3709,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do ...@@ -3700,7 +3709,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
add_foreign_key "fork_network_members", "projects", on_delete: :cascade add_foreign_key "fork_network_members", "projects", on_delete: :cascade
add_foreign_key "fork_networks", "projects", column: "root_project_id", name: "fk_e7b436b2b5", on_delete: :nullify add_foreign_key "fork_networks", "projects", column: "root_project_id", name: "fk_e7b436b2b5", on_delete: :nullify
add_foreign_key "forked_project_links", "projects", column: "forked_to_project_id", name: "fk_434510edb0", on_delete: :cascade add_foreign_key "forked_project_links", "projects", column: "forked_to_project_id", name: "fk_434510edb0", on_delete: :cascade
add_foreign_key "geo_container_repository_updated_events", "container_repositories", name: "fk_212c89c706", on_delete: :cascade
add_foreign_key "geo_event_log", "geo_cache_invalidation_events", column: "cache_invalidation_event_id", name: "fk_42c3b54bed", on_delete: :cascade add_foreign_key "geo_event_log", "geo_cache_invalidation_events", column: "cache_invalidation_event_id", name: "fk_42c3b54bed", on_delete: :cascade
add_foreign_key "geo_event_log", "geo_container_repository_updated_events", column: "container_repository_updated_event_id", name: "fk_6ada82d42a", on_delete: :cascade
add_foreign_key "geo_event_log", "geo_hashed_storage_migrated_events", column: "hashed_storage_migrated_event_id", name: "fk_27548c6db3", on_delete: :cascade add_foreign_key "geo_event_log", "geo_hashed_storage_migrated_events", column: "hashed_storage_migrated_event_id", name: "fk_27548c6db3", on_delete: :cascade
add_foreign_key "geo_event_log", "geo_job_artifact_deleted_events", column: "job_artifact_deleted_event_id", name: "fk_176d3fbb5d", on_delete: :cascade add_foreign_key "geo_event_log", "geo_job_artifact_deleted_events", column: "job_artifact_deleted_event_id", name: "fk_176d3fbb5d", on_delete: :cascade
add_foreign_key "geo_event_log", "geo_lfs_object_deleted_events", column: "lfs_object_deleted_event_id", name: "fk_d5af95fcd9", on_delete: :cascade add_foreign_key "geo_event_log", "geo_lfs_object_deleted_events", column: "lfs_object_deleted_event_id", name: "fk_d5af95fcd9", on_delete: :cascade
......
...@@ -242,7 +242,7 @@ node's Geo Nodes dashboard in your browser. ...@@ -242,7 +242,7 @@ node's Geo Nodes dashboard in your browser.
![Geo dashboard](img/geo_node_dashboard.png) ![Geo dashboard](img/geo_node_dashboard.png)
If your installation isn't working properly, check the If your installation isn't working properly, check the
[troubleshooting document]. [troubleshooting document](troubleshooting.md).
The two most obvious issues that can become apparent in the dashboard are: The two most obvious issues that can become apparent in the dashboard are:
......
...@@ -251,7 +251,7 @@ Omnibus is the following: ...@@ -251,7 +251,7 @@ Omnibus is the following:
1. Check the steps about defining `postgresql['sql_user_password']`, `gitlab_rails['db_password']`. 1. Check the steps about defining `postgresql['sql_user_password']`, `gitlab_rails['db_password']`.
1. Make sure `postgresql['max_replication_slots']` matches the number of **secondary** Geo nodes locations. 1. Make sure `postgresql['max_replication_slots']` matches the number of **secondary** Geo nodes locations.
1. Install GitLab on the **secondary** server. 1. Install GitLab on the **secondary** server.
1. Re-run the [database replication process][database-replication]. 1. Re-run the [database replication process](database.md#step-3-initiate-the-replication-process).
## Special update notes for 9.0.x ## Special update notes for 9.0.x
......
...@@ -209,7 +209,7 @@ ps auwx | grep unicorn | awk '{ print " -p " $2}' | xargs strace -tt -T -f -s 10 ...@@ -209,7 +209,7 @@ ps auwx | grep unicorn | awk '{ print " -p " $2}' | xargs strace -tt -T -f -s 10
The output in `/tmp/unicorn.txt` may help diagnose the root cause. The output in `/tmp/unicorn.txt` may help diagnose the root cause.
# More information ## More information
- [Debugging Stuck Ruby Processes](https://blog.newrelic.com/2013/04/29/debugging-stuck-ruby-processes-what-to-do-before-you-kill-9/) - [Debugging Stuck Ruby Processes](https://blog.newrelic.com/2013/04/29/debugging-stuck-ruby-processes-what-to-do-before-you-kill-9/)
- [Cheatsheet of using gdb and ruby processes](gdb-stuck-ruby.txt) - [Cheatsheet of using gdb and ruby processes](gdb-stuck-ruby.txt)
...@@ -29,7 +29,7 @@ The following API resources are available in the project context: ...@@ -29,7 +29,7 @@ The following API resources are available in the project context:
| [Commits](commits.md) | `/projects/:id/repository/commits`, `/projects/:id/statuses` | | [Commits](commits.md) | `/projects/:id/repository/commits`, `/projects/:id/statuses` |
| [Container Registry](container_registry.md) | `/projects/:id/registry/repositories` | | [Container Registry](container_registry.md) | `/projects/:id/registry/repositories` |
| [Custom attributes](custom_attributes.md) | `/projects/:id/custom_attributes` (also available for groups and users) | | [Custom attributes](custom_attributes.md) | `/projects/:id/custom_attributes` (also available for groups and users) |
| [Dependencies](dependencies.md) **[ULTIMATE]** | `/projects/:id/dependencies` | [Dependencies](dependencies.md) **(ULTIMATE)** | `/projects/:id/dependencies`
| [Deploy keys](deploy_keys.md) | `/projects/:id/deploy_keys` (also available standalone) | | [Deploy keys](deploy_keys.md) | `/projects/:id/deploy_keys` (also available standalone) |
| [Deployments](deployments.md) | `/projects/:id/deployments` | | [Deployments](deployments.md) | `/projects/:id/deployments` |
| [Discussions](discussions.md) (threaded comments) | `/projects/:id/issues/.../discussions`, `/projects/:id/snippets/.../discussions`, `/projects/:id/merge_requests/.../discussions`, `/projects/:id/commits/.../discussions` (also available for groups) | | [Discussions](discussions.md) (threaded comments) | `/projects/:id/issues/.../discussions`, `/projects/:id/snippets/.../discussions`, `/projects/:id/merge_requests/.../discussions`, `/projects/:id/commits/.../discussions` (also available for groups) |
......
...@@ -10,7 +10,7 @@ If epics feature is not available a `403` status code will be returned. ...@@ -10,7 +10,7 @@ If epics feature is not available a `403` status code will be returned.
The [epic issues API](epic_issues.md) allows you to interact with issues associated with an epic. The [epic issues API](epic_issues.md) allows you to interact with issues associated with an epic.
# Milestone dates integration ## Milestone dates integration
> [Introduced][ee-6448] in GitLab 11.3. > [Introduced][ee-6448] in GitLab 11.3.
......
...@@ -39,6 +39,7 @@ Example response: ...@@ -39,6 +39,7 @@ Example response:
"session_expire_delay" : 10080, "session_expire_delay" : 10080,
"home_page_url" : null, "home_page_url" : null,
"default_snippet_visibility" : "private", "default_snippet_visibility" : "private",
"outbound_local_requests_whitelist": [],
"domain_whitelist" : [], "domain_whitelist" : [],
"domain_blacklist_enabled" : false, "domain_blacklist_enabled" : false,
"domain_blacklist" : [], "domain_blacklist" : [],
...@@ -113,6 +114,7 @@ Example response: ...@@ -113,6 +114,7 @@ Example response:
"default_project_visibility": "internal", "default_project_visibility": "internal",
"default_snippet_visibility": "private", "default_snippet_visibility": "private",
"default_group_visibility": "private", "default_group_visibility": "private",
"outbound_local_requests_whitelist": [],
"domain_whitelist": [], "domain_whitelist": [],
"domain_blacklist_enabled" : false, "domain_blacklist_enabled" : false,
"domain_blacklist" : [], "domain_blacklist" : [],
...@@ -193,6 +195,7 @@ are listed in the descriptions of the relevant settings. ...@@ -193,6 +195,7 @@ are listed in the descriptions of the relevant settings.
| `domain_blacklist` | array of strings | required by: `domain_blacklist_enabled` | Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: `domain.com`, `*.domain.com`. | | `domain_blacklist` | array of strings | required by: `domain_blacklist_enabled` | Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: `domain.com`, `*.domain.com`. |
| `domain_blacklist_enabled` | boolean | no | (**If enabled, requires:** `domain_blacklist`) Allows blocking sign-ups from emails from specific domains. | | `domain_blacklist_enabled` | boolean | no | (**If enabled, requires:** `domain_blacklist`) Allows blocking sign-ups from emails from specific domains. |
| `domain_whitelist` | array of strings | no | Force people to use only corporate emails for sign-up. Default is `null`, meaning there is no restriction. | | `domain_whitelist` | array of strings | no | Force people to use only corporate emails for sign-up. Default is `null`, meaning there is no restriction. |
| `outbound_local_requests_whitelist` | array of strings | no | Define a list of trusted domains or ip addresses to which local requests are allowed when local requests for hooks and services are disabled.
| `dsa_key_restriction` | integer | no | The minimum allowed bit length of an uploaded DSA key. Default is `0` (no restriction). `-1` disables DSA keys. | | `dsa_key_restriction` | integer | no | The minimum allowed bit length of an uploaded DSA key. Default is `0` (no restriction). `-1` disables DSA keys. |
| `ecdsa_key_restriction` | integer | no | The minimum allowed curve size (in bits) of an uploaded ECDSA key. Default is `0` (no restriction). `-1` disables ECDSA keys. | | `ecdsa_key_restriction` | integer | no | The minimum allowed curve size (in bits) of an uploaded ECDSA key. Default is `0` (no restriction). `-1` disables ECDSA keys. |
| `ed25519_key_restriction` | integer | no | The minimum allowed curve size (in bits) of an uploaded ED25519 key. Default is `0` (no restriction). `-1` disables ED25519 keys. | | `ed25519_key_restriction` | integer | no | The minimum allowed curve size (in bits) of an uploaded ED25519 key. Default is `0` (no restriction). `-1` disables ED25519 keys. |
......
...@@ -345,7 +345,7 @@ GitLab provides API endpoints to: ...@@ -345,7 +345,7 @@ GitLab provides API endpoints to:
- [Triggering pipelines through the API](triggers/README.md). - [Triggering pipelines through the API](triggers/README.md).
- [Pipeline triggers API](../api/pipeline_triggers.md). - [Pipeline triggers API](../api/pipeline_triggers.md).
### Start multiple manual actions in a stage ### Start multiple manual actions in a stage
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/27188) in GitLab 11.11. > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/27188) in GitLab 11.11.
......
...@@ -69,7 +69,7 @@ curl http://localhost:3000/-/chaos/leakmem?memory_mb=1024&duration_s=10&token=se ...@@ -69,7 +69,7 @@ curl http://localhost:3000/-/chaos/leakmem?memory_mb=1024&duration_s=10&token=se
This endpoint attempts to fully utilise a single core, at 100%, for the given period. This endpoint attempts to fully utilise a single core, at 100%, for the given period.
Depending on your rack server setup, your request may timeout after a predermined period (normally 60 seconds). Depending on your rack server setup, your request may timeout after a predetermined period (normally 60 seconds).
If you're using Unicorn, this is done by killing the worker process. If you're using Unicorn, this is done by killing the worker process.
``` ```
...@@ -80,7 +80,7 @@ GET /-/chaos/cpu_spin?duration_s=50&async=true ...@@ -80,7 +80,7 @@ GET /-/chaos/cpu_spin?duration_s=50&async=true
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| ------------ | ------- | -------- | --------------------------------------------------------------------- | | ------------ | ------- | -------- | --------------------------------------------------------------------- |
| `duration_s` | integer | no | Duration, in seconds, that the core will be utilised. Defaults to 30s | | `duration_s` | integer | no | Duration, in seconds, that the core will be utilized. Defaults to 30s |
| `async` | boolean | no | Set to true to consume CPU in a Sidekiq background worker process | | `async` | boolean | no | Set to true to consume CPU in a Sidekiq background worker process |
```bash ```bash
...@@ -93,7 +93,7 @@ curl http://localhost:3000/-/chaos/cpu_spin?duration_s=60&token=secret ...@@ -93,7 +93,7 @@ curl http://localhost:3000/-/chaos/cpu_spin?duration_s=60&token=secret
This endpoint attempts to fully utilise a single core, and interleave it with DB request, for the given period. This endpoint attempts to fully utilise a single core, and interleave it with DB request, for the given period.
This endpoint can be used to model yielding execution to another threads when running concurrently. This endpoint can be used to model yielding execution to another threads when running concurrently.
Depending on your rack server setup, your request may timeout after a predermined period (normally 60 seconds). Depending on your rack server setup, your request may timeout after a predetermined period (normally 60 seconds).
If you're using Unicorn, this is done by killing the worker process. If you're using Unicorn, this is done by killing the worker process.
``` ```
...@@ -105,7 +105,7 @@ GET /-/chaos/db_spin?duration_s=50&async=true ...@@ -105,7 +105,7 @@ GET /-/chaos/db_spin?duration_s=50&async=true
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |
| ------------ | ------- | -------- | --------------------------------------------------------------------------- | | ------------ | ------- | -------- | --------------------------------------------------------------------------- |
| `interval_s` | float | no | Interval, in seconds, for every DB request. Defaults to 1s | | `interval_s` | float | no | Interval, in seconds, for every DB request. Defaults to 1s |
| `duration_s` | integer | no | Duration, in seconds, that the core will be utilised. Defaults to 30s | | `duration_s` | integer | no | Duration, in seconds, that the core will be utilized. Defaults to 30s |
| `async` | boolean | no | Set to true to perform the operation in a Sidekiq background worker process | | `async` | boolean | no | Set to true to perform the operation in a Sidekiq background worker process |
```bash ```bash
......
### Community members & roles # Community members & roles
GitLab community members and their privileges/responsibilities. GitLab community members and their privileges/responsibilities.
......
# Axios # Axios
We use [axios][axios] to communicate with the server in Vue applications and most new code. We use [axios][axios] to communicate with the server in Vue applications and most new code.
In order to guarantee all defaults are set you *should not use `axios` directly*, you should import `axios` from `axios_utils`. In order to guarantee all defaults are set you *should not use `axios` directly*, you should import `axios` from `axios_utils`.
## CSRF token ## CSRF token
All our request require a CSRF token. All our request require a CSRF token.
To guarantee this token is set, we are importing [axios][axios], setting the token, and exporting `axios` . To guarantee this token is set, we are importing [axios][axios], setting the token, and exporting `axios` .
This exported module should be used instead of directly using `axios` to ensure the token is set. This exported module should be used instead of directly using `axios` to ensure the token is set.
## Usage ## Usage
```javascript ```javascript
import axios from './lib/utils/axios_utils'; import axios from './lib/utils/axios_utils';
......
...@@ -212,6 +212,7 @@ selectors are intended for use only with JavaScript to allow for removal or ...@@ -212,6 +212,7 @@ selectors are intended for use only with JavaScript to allow for removal or
renaming without breaking styling. renaming without breaking styling.
### IDs ### IDs
Don't use ID selectors in CSS. Don't use ID selectors in CSS.
```scss ```scss
......
...@@ -173,6 +173,45 @@ func Test(t *testing.T) { ...@@ -173,6 +173,45 @@ func Test(t *testing.T) {
} }
``` ```
### Table-Driven Tests
Using [Table-Driven Tests](https://github.com/golang/go/wiki/TableDrivenTests)
is generally good practice when you have multiple entries of
inputs/outputs for the same function. Below are some guidelines one can
follow when writing table-driven test. These guidelines are mostly
extracted from Go standard library source code. Keep in mind it's OK not
to follow these guidelines when it makes sense.
#### Defining test cases
Each table entry is a complete test case with inputs and expected
results, and sometimes with additional information such as a test name
to make the test output easily readable.
- [Define a slice of anonymous struct](https://github.com/golang/go/blob/50bd1c4d4eb4fac8ddeb5f063c099daccfb71b26/src/encoding/csv/reader_test.go#L16)
inside of the test.
- [Define a slice of anonymous struct](https://github.com/golang/go/blob/55d31e16c12c38d36811bdee65ac1f7772148250/src/cmd/go/internal/module/module_test.go#L9-L66)
outside of the test.
- [Named structs](https://github.com/golang/go/blob/2e0cd2aef5924e48e1ceb74e3d52e76c56dd34cc/src/cmd/go/internal/modfetch/coderepo_test.go#L54-L69)
for code reuse.
- [Using `map[string]struct{}`](https://github.com/golang/go/blob/6d5caf38e37bf9aeba3291f1f0b0081f934b1187/src/cmd/trace/annotations_test.go#L180-L235).
#### Contents of the test case
- Ideally, each test case should have a field with a unique identifier
to use for naming subtests. In the Go standard library, this is commonly the
`name string` field.
- Use `want`/`expect`/`actual` when you are specifcing something in the
test case that will be used for assertion.
#### Variable names
- Each table-driven test map/slice of struct can be named `tests`.
- When looping through `tests` the anonymous struct can be referred
to as `tt` or `tc`.
- The description of the test can be referred to as
`name`/`testName`/`tn`.
### Benchmarks ### Benchmarks
Programs handling a lot of IO or complex operations should always include Programs handling a lot of IO or complex operations should always include
......
## Modules with instance variables could be considered harmful # Modules with instance variables could be considered harmful
### Background ## Background
Rails somehow encourages people using modules and instance variables Rails somehow encourages people using modules and instance variables
everywhere. For example, using instance variables in the controllers, everywhere. For example, using instance variables in the controllers,
...@@ -9,7 +9,7 @@ helpers, and views. They're also encouraging the use of ...@@ -9,7 +9,7 @@ helpers, and views. They're also encouraging the use of
saving everything in a giant, single object, and people could access saving everything in a giant, single object, and people could access
everything in that one giant object. everything in that one giant object.
### The problems ## The problems
Of course this is convenient to develop, because we just have everything Of course this is convenient to develop, because we just have everything
within reach. However this has a number of downsides when that chosen object within reach. However this has a number of downsides when that chosen object
...@@ -24,7 +24,7 @@ manipulated from 3 different modules. It's hard to track when those variables ...@@ -24,7 +24,7 @@ manipulated from 3 different modules. It's hard to track when those variables
start giving us troubles. We don't know which module would suddenly change start giving us troubles. We don't know which module would suddenly change
one of the variables. Everything could touch anything. one of the variables. Everything could touch anything.
### Similar concerns ## Similar concerns
People are saying multiple inheritance is bad. Mixing multiple modules with People are saying multiple inheritance is bad. Mixing multiple modules with
multiple instance variables scattering everywhere suffer from the same issue. multiple instance variables scattering everywhere suffer from the same issue.
...@@ -40,7 +40,7 @@ Note that `included` doesn't solve the whole issue. They define the ...@@ -40,7 +40,7 @@ Note that `included` doesn't solve the whole issue. They define the
dependencies, but they still allow each modules to talk implicitly via the dependencies, but they still allow each modules to talk implicitly via the
instance variables in the final giant object, and that's where the problem is. instance variables in the final giant object, and that's where the problem is.
### Solutions ## Solutions
We should split the giant object into multiple objects, and they communicate We should split the giant object into multiple objects, and they communicate
with each other with the API, i.e. public methods. In short, composition over with each other with the API, i.e. public methods. In short, composition over
...@@ -53,7 +53,7 @@ With clearly defined API, this would make things less coupled and much easier ...@@ -53,7 +53,7 @@ With clearly defined API, this would make things less coupled and much easier
to debug and track, and much more extensible for other objects to use, because to debug and track, and much more extensible for other objects to use, because
they communicate in a clear way, rather than implicit dependencies. they communicate in a clear way, rather than implicit dependencies.
### Acceptable use ## Acceptable use
However, it's not always bad to use instance variables in a module, However, it's not always bad to use instance variables in a module,
as long as it's contained in the same module; that is, no other modules or as long as it's contained in the same module; that is, no other modules or
...@@ -74,7 +74,7 @@ Unfortunately it's not easy to code more complex rules into the cop, so ...@@ -74,7 +74,7 @@ Unfortunately it's not easy to code more complex rules into the cop, so
we rely on people's best judgement. If we could find another good pattern we rely on people's best judgement. If we could find another good pattern
we could easily add to the cop, we should do it. we could easily add to the cop, we should do it.
### How to rewrite and avoid disabling this cop ## How to rewrite and avoid disabling this cop
Even if we could just disable the cop, we should avoid doing so. Some code Even if we could just disable the cop, we should avoid doing so. Some code
could be easily rewritten in simple form. Consider this acceptable method: could be easily rewritten in simple form. Consider this acceptable method:
...@@ -181,7 +181,7 @@ rather than whatever includes the module, and those modules which were also ...@@ -181,7 +181,7 @@ rather than whatever includes the module, and those modules which were also
included, making it much easier to track down any issues, included, making it much easier to track down any issues,
and reducing the chance of having name conflicts. and reducing the chance of having name conflicts.
### How to disable this cop ## How to disable this cop
Put the disabling comment right after your code in the same line: Put the disabling comment right after your code in the same line:
...@@ -210,14 +210,14 @@ end ...@@ -210,14 +210,14 @@ end
Note that you need to enable it at some point, otherwise everything below Note that you need to enable it at some point, otherwise everything below
won't be checked. won't be checked.
### Things we might need to ignore right now ## Things we might need to ignore right now
Because of the way Rails helpers and mailers work, we might not be able to Because of the way Rails helpers and mailers work, we might not be able to
avoid the use of instance variables there. For those cases, we could ignore avoid the use of instance variables there. For those cases, we could ignore
them at the moment. At least we're not going to share those modules with them at the moment. At least we're not going to share those modules with
other random objects, so they're still somewhat isolated. other random objects, so they're still somewhat isolated.
### Instance variables in views ## Instance variables in views
They're bad because we can't easily tell who's using the instance variables They're bad because we can't easily tell who's using the instance variables
(from controller's point of view) and where we set them up (from partials' (from controller's point of view) and where we set them up (from partials'
......
...@@ -15,6 +15,18 @@ Exceptions are made for some tools that we require in the ...@@ -15,6 +15,18 @@ Exceptions are made for some tools that we require in the
`gitlab:assets:compile` CI job such as `webpack-bundle-analyzer` to analyze our `gitlab:assets:compile` CI job such as `webpack-bundle-analyzer` to analyze our
production assets post-compile. production assets post-compile.
To add or upgrade a dependency, run:
```sh
yarn add <your dependency here>
```
This may introduce duplicate dependencies. To de-duplicate `yarn.lock`, run:
```sh
node_modules/.bin/yarn-deduplicate --list --strategy fewer yarn.lock && yarn install
```
--- ---
> TODO: Add Dependencies > TODO: Add Dependencies
# Accessiblity # Accessiblity
Using semantic HTML plays a key role when it comes to accessibility. Using semantic HTML plays a key role when it comes to accessibility.
## Accessible Rich Internet Applications - ARIA ## Accessible Rich Internet Applications - ARIA
WAI-ARIA, the Accessible Rich Internet Applications specification, defines a way to make Web content and Web applications more accessible to people with disabilities. WAI-ARIA, the Accessible Rich Internet Applications specification, defines a way to make Web content and Web applications more accessible to people with disabilities.
> Note: It is [recommended][using-aria] to use semantic elements as the primary method to achieve accessibility rather than adding aria attributes. Adding aria attributes should be seen as a secondary method for creating accessible elements. > Note: It is [recommended][using-aria] to use semantic elements as the primary method to achieve accessibility rather than adding aria attributes. Adding aria attributes should be seen as a secondary method for creating accessible elements.
### Role ### Role
The `role` attribute describes the role the element plays in the context of the document. The `role` attribute describes the role the element plays in the context of the document.
Check the list of WAI-ARIA roles [here][roles] Check the list of WAI-ARIA roles [here][roles]
## Icons ## Icons
When using icons or images that aren't absolutely needed to understand the context, we should use `aria-hidden="true"`. When using icons or images that aren't absolutely needed to understand the context, we should use `aria-hidden="true"`.
On the other hand, if an icon is crucial to understand the context we should do one of the following: On the other hand, if an icon is crucial to understand the context we should do one of the following:
...@@ -20,6 +24,7 @@ On the other hand, if an icon is crucial to understand the context we should do ...@@ -20,6 +24,7 @@ On the other hand, if an icon is crucial to understand the context we should do
1. Use `aria-labelledby` to point to an element that contains the explanation for that icon 1. Use `aria-labelledby` to point to an element that contains the explanation for that icon
## Form inputs ## Form inputs
In forms we should use the `for` attribute in the label statement: In forms we should use the `for` attribute in the label statement:
``` ```
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
## [Vue style guide](vue.md) ## [Vue style guide](vue.md)
# Tooling ## Tooling
## [Prettier](prettier.md) ## [Prettier](prettier.md)
......
...@@ -14,13 +14,13 @@ on maintainability, the ability to easily debug problems, or even performance. ...@@ -14,13 +14,13 @@ on maintainability, the ability to easily debug problems, or even performance.
An example would be to use `ProjectsFinder` in `IssuesFinder` to limit issues to An example would be to use `ProjectsFinder` in `IssuesFinder` to limit issues to
those belonging to a set of projects. While initially this may seem like a good those belonging to a set of projects. While initially this may seem like a good
idea, both classes provide a very high level interface with very little control. idea, both classes provide a very high level interface with very little control.
This means that `IssuesFinder` may not be able to produce a better optimised This means that `IssuesFinder` may not be able to produce a better optimized
database query, as a large portion of the query is controlled by the internals database query, as a large portion of the query is controlled by the internals
of `ProjectsFinder`. of `ProjectsFinder`.
To work around this problem, you would use the same code used by To work around this problem, you would use the same code used by
`ProjectsFinder`, instead of using `ProjectsFinder` itself directly. This allows `ProjectsFinder`, instead of using `ProjectsFinder` itself directly. This allows
you to compose your behaviour better, giving you more control over the behaviour you to compose your behavior better, giving you more control over the behavior
of the code. of the code.
To illustrate, consider the following code from `IssuableFinder#projects`: To illustrate, consider the following code from `IssuableFinder#projects`:
...@@ -52,7 +52,7 @@ functionality is added to this (high level) interface. Instead of _only_ ...@@ -52,7 +52,7 @@ functionality is added to this (high level) interface. Instead of _only_
affecting the cases where this is necessary, it may also start affecting affecting the cases where this is necessary, it may also start affecting
`IssuableFinder` in a negative way. For example, the query produced by `IssuableFinder` in a negative way. For example, the query produced by
`GroupProjectsFinder` may include unnecessary conditions. Since we're using a `GroupProjectsFinder` may include unnecessary conditions. Since we're using a
finder here, we can't easily opt-out of that behaviour. We could add options to finder here, we can't easily opt-out of that behavior. We could add options to
do so, but then we'd need as many options as we have features. Every option adds do so, but then we'd need as many options as we have features. Every option adds
two code paths, which means that for four features we have to cover 8 different two code paths, which means that for four features we have to cover 8 different
code paths. code paths.
...@@ -213,6 +213,5 @@ The API provided by Active Record itself, such as the `where` method, `save`, ...@@ -213,6 +213,5 @@ The API provided by Active Record itself, such as the `where` method, `save`,
Everything in `app/workers`. Everything in `app/workers`.
The scheduling of Sidekiq jobs using `SomeWorker.perform_async`, `perform_in`, Use `SomeWorker.perform_async` or `SomeWorker.perform_in` to schedule Sidekiq
etc. Directly invoking a worker using `SomeWorker.new.perform` should be avoided jobs. Never directly invoke a worker using `SomeWorker.new.perform`.
at all times in application code, though this is fine to use in tests.
...@@ -25,7 +25,7 @@ else ...@@ -25,7 +25,7 @@ else
end end
``` ```
# Read-only database ## Read-only database
The database can be used in read-only mode. In this case we have to The database can be used in read-only mode. In this case we have to
make sure all GET requests don't attempt any write operations to the make sure all GET requests don't attempt any write operations to the
......
--- ---
redirect_to: '../project_services/slack.md' redirect_to: '../user/project/integrations/slack.md'
--- ---
This document was moved to [project_services/slack.md](../project_services/slack.md). This document was moved to [project_services/slack.md](../user/project/integrations/slack.md).
# Auto DevOps # Auto DevOps
> [Introduced][ce-37115] in GitLab 10.0. Generally available on GitLab 11.0. > - [Introduced][ce-37115] in GitLab 10.0.
> - Generally available on GitLab 11.0.
Auto DevOps provides pre-defined CI/CD configuration which allows you to automatically detect, build, test, Auto DevOps provides pre-defined CI/CD configuration which allows you to automatically detect, build, test,
deploy, and monitor your applications. Leveraging CI/CD best practices and tools, Auto DevOps aims deploy, and monitor your applications. Leveraging CI/CD best practices and tools, Auto DevOps aims
...@@ -43,14 +44,14 @@ platform or a Platform as a Service (PaaS). It takes inspiration from the ...@@ -43,14 +44,14 @@ platform or a Platform as a Service (PaaS). It takes inspiration from the
innovative work done by [Heroku](https://www.heroku.com/) and goes beyond it innovative work done by [Heroku](https://www.heroku.com/) and goes beyond it
in multiple ways: in multiple ways:
1. Auto DevOps works with any Kubernetes cluster; you're not limited to running - Auto DevOps works with any Kubernetes cluster; you're not limited to running
on GitLab's infrastructure. (Note that many features also work without Kubernetes.) on GitLab's infrastructure. (Note that many features also work without Kubernetes).
1. There is no additional cost (no markup on the infrastructure costs), and you - There is no additional cost (no markup on the infrastructure costs), and you
can use a self-hosted Kubernetes cluster or Containers as a Service on any can use a self-hosted Kubernetes cluster or Containers as a Service on any
public cloud (for example, [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine/)). public cloud (for example, [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine/)).
1. Auto DevOps has more features including security testing, performance testing, - Auto DevOps has more features including security testing, performance testing,
and code quality testing. and code quality testing.
1. Auto DevOps offers an incremental graduation path. If you need advanced customizations, - Auto DevOps offers an incremental graduation path. If you need advanced customizations,
you can start modifying the templates without having to start over on a you can start modifying the templates without having to start over on a
completely different platform. Review the [customizing](#customizing) section for more information. completely different platform. Review the [customizing](#customizing) section for more information.
...@@ -90,7 +91,7 @@ For an overview on the creation of Auto DevOps, read the blog post [From 2/3 of ...@@ -90,7 +91,7 @@ For an overview on the creation of Auto DevOps, read the blog post [From 2/3 of
To make full use of Auto DevOps, you will need: To make full use of Auto DevOps, you will need:
1. **GitLab Runner** (needed for all stages) - Your Runner needs to be - **GitLab Runner** (needed for all stages) - Your Runner needs to be
configured to be able to run Docker. Generally this means using the configured to be able to run Docker. Generally this means using the
[Docker](https://docs.gitlab.com/runner/executors/docker.html) or [Kubernetes [Docker](https://docs.gitlab.com/runner/executors/docker.html) or [Kubernetes
executor](https://docs.gitlab.com/runner/executors/kubernetes.html), with executor](https://docs.gitlab.com/runner/executors/kubernetes.html), with
...@@ -102,18 +103,18 @@ To make full use of Auto DevOps, you will need: ...@@ -102,18 +103,18 @@ To make full use of Auto DevOps, you will need:
should be registered as [shared Runners](../../ci/runners/README.md#registering-a-shared-runner) should be registered as [shared Runners](../../ci/runners/README.md#registering-a-shared-runner)
for the entire GitLab instance, or [specific Runners](../../ci/runners/README.md#registering-a-specific-runner) for the entire GitLab instance, or [specific Runners](../../ci/runners/README.md#registering-a-specific-runner)
that are assigned to specific projects. that are assigned to specific projects.
1. **Base domain** (needed for Auto Review Apps and Auto Deploy) - You will need - **Base domain** (needed for Auto Review Apps and Auto Deploy) - You will need
a domain configured with wildcard DNS which is going to be used by all of your a domain configured with wildcard DNS which is going to be used by all of your
Auto DevOps applications. [Read the specifics](#auto-devops-base-domain). Auto DevOps applications. [Read the specifics](#auto-devops-base-domain).
1. **Kubernetes** (needed for Auto Review Apps, Auto Deploy, and Auto Monitoring) - - **Kubernetes** (needed for Auto Review Apps, Auto Deploy, and Auto Monitoring) -
To enable deployments, you will need Kubernetes 1.5+. You need a [Kubernetes cluster][kubernetes-clusters] To enable deployments, you will need Kubernetes 1.5+. You need a [Kubernetes cluster][kubernetes-clusters]
for the project, or a Kubernetes [default service template](../../user/project/integrations/services_templates.md) for the project, or a Kubernetes [default service template](../../user/project/integrations/services_templates.md)
for the entire GitLab installation. for the entire GitLab installation.
1. **A load balancer** - You can use NGINX ingress by deploying it to your - **A load balancer** - You can use NGINX ingress by deploying it to your
Kubernetes cluster using the Kubernetes cluster using the
[`nginx-ingress`](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress) [`nginx-ingress`](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress)
Helm chart. Helm chart.
1. **Prometheus** (needed for Auto Monitoring) - To enable Auto Monitoring, you - **Prometheus** (needed for Auto Monitoring) - To enable Auto Monitoring, you
will need Prometheus installed somewhere (inside or outside your cluster) and will need Prometheus installed somewhere (inside or outside your cluster) and
configured to scrape your Kubernetes cluster. To get response metrics configured to scrape your Kubernetes cluster. To get response metrics
(in addition to system metrics), you need to (in addition to system metrics), you need to
...@@ -147,7 +148,7 @@ NOTE: **Note** ...@@ -147,7 +148,7 @@ NOTE: **Note**
A wildcard DNS A record matching the base domain(s) is required, for example, A wildcard DNS A record matching the base domain(s) is required, for example,
given a base domain of `example.com`, you'd need a DNS entry like: given a base domain of `example.com`, you'd need a DNS entry like:
``` ```text
*.example.com 3600 A 1.2.3.4 *.example.com 3600 A 1.2.3.4
``` ```
...@@ -224,7 +225,7 @@ full use of Auto DevOps are available. If this is your fist time, we recommend y ...@@ -224,7 +225,7 @@ full use of Auto DevOps are available. If this is your fist time, we recommend y
GitLab.com users can enable/disable Auto DevOps at the project-level only. Self-managed users GitLab.com users can enable/disable Auto DevOps at the project-level only. Self-managed users
can enable/disable Auto DevOps at the project-level, group-level or instance-level. can enable/disable Auto DevOps at the project-level, group-level or instance-level.
### Enabling/disabling Auto DevOps at the instance-level (Administrators only) ### At the instance level (Administrators only)
Even when disabled at the instance level, group owners and project maintainers can still enable Even when disabled at the instance level, group owners and project maintainers can still enable
Auto DevOps at the group and project level, respectively. Auto DevOps at the group and project level, respectively.
...@@ -234,7 +235,7 @@ Auto DevOps at the group and project level, respectively. ...@@ -234,7 +235,7 @@ Auto DevOps at the group and project level, respectively.
1. If enabling, optionally set up the Auto DevOps [base domain](#auto-devops-base-domain) which will be used for Auto Deploy and Auto Review Apps. 1. If enabling, optionally set up the Auto DevOps [base domain](#auto-devops-base-domain) which will be used for Auto Deploy and Auto Review Apps.
1. Click **Save changes** for the changes to take effect. 1. Click **Save changes** for the changes to take effect.
### Enabling/disabling Auto DevOps at the group-level ### At the group level
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/52447) in GitLab 11.10. > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/52447) in GitLab 11.10.
...@@ -250,7 +251,7 @@ When enabling or disabling Auto DevOps at group-level, group configuration will ...@@ -250,7 +251,7 @@ When enabling or disabling Auto DevOps at group-level, group configuration will
the subgroups and projects inside that group, unless Auto DevOps is specifically enabled or disabled on the subgroups and projects inside that group, unless Auto DevOps is specifically enabled or disabled on
the subgroup or project. the subgroup or project.
### Enabling/disabling Auto DevOps at the project-level ### At the project level
If enabling, check that your project doesn't have a `.gitlab-ci.yml`, or if one exists, remove it. If enabling, check that your project doesn't have a `.gitlab-ci.yml`, or if one exists, remove it.
...@@ -263,7 +264,7 @@ If enabling, check that your project doesn't have a `.gitlab-ci.yml`, or if one ...@@ -263,7 +264,7 @@ If enabling, check that your project doesn't have a `.gitlab-ci.yml`, or if one
When the feature has been enabled, an Auto DevOps pipeline is triggered on the default branch. When the feature has been enabled, an Auto DevOps pipeline is triggered on the default branch.
### Feature flag to enable for a percentage of projects ### Enable for a percentage of projects
There is also a feature flag to enable Auto DevOps by default to your chosen percentage of projects. There is also a feature flag to enable Auto DevOps by default to your chosen percentage of projects.
...@@ -371,7 +372,7 @@ Any differences between the source and target branches are also ...@@ -371,7 +372,7 @@ Any differences between the source and target branches are also
Static Application Security Testing (SAST) uses the Static Application Security Testing (SAST) uses the
[SAST Docker image](https://gitlab.com/gitlab-org/security-products/sast) to run static [SAST Docker image](https://gitlab.com/gitlab-org/security-products/sast) to run static
analysis on the current code and checks for potential security issues. The analysis on the current code and checks for potential security issues. The
the Auto SAST stage will be skipped on licenses other than Ultimate and requires GitLab Runner 11.5 or above. Auto SAST stage will be skipped on licenses other than Ultimate and requires GitLab Runner 11.5 or above.
Once the report is created, it's uploaded as an artifact which you can later download and Once the report is created, it's uploaded as an artifact which you can later download and
check out. check out.
...@@ -488,7 +489,7 @@ Any security warnings are also shown in the merge request widget. Read how ...@@ -488,7 +489,7 @@ Any security warnings are also shown in the merge request widget. Read how
Auto Browser Performance Testing utilizes the [Sitespeed.io container](https://hub.docker.com/r/sitespeedio/sitespeed.io/) to measure the performance of a web page. A JSON report is created and uploaded as an artifact, which includes the overall performance score for each page. By default, the root page of Review and Production environments will be tested. If you would like to add additional URL's to test, simply add the paths to a file named `.gitlab-urls.txt` in the root directory, one per line. For example: Auto Browser Performance Testing utilizes the [Sitespeed.io container](https://hub.docker.com/r/sitespeedio/sitespeed.io/) to measure the performance of a web page. A JSON report is created and uploaded as an artifact, which includes the overall performance score for each page. By default, the root page of Review and Production environments will be tested. If you would like to add additional URL's to test, simply add the paths to a file named `.gitlab-urls.txt` in the root directory, one per line. For example:
``` ```text
/ /
/features /features
/direction /direction
...@@ -749,7 +750,7 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac ...@@ -749,7 +750,7 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac
| `DB_MIGRATE` | From GitLab 11.4, this variable can be used to specify the command to run to migrate the application's PostgreSQL database. It runs inside the application pod. | | `DB_MIGRATE` | From GitLab 11.4, this variable can be used to specify the command to run to migrate the application's PostgreSQL database. It runs inside the application pod. |
| `STAGING_ENABLED` | From GitLab 10.8, this variable can be used to define a [deploy policy for staging and production environments](#deploy-policy-for-staging-and-production-environments). | | `STAGING_ENABLED` | From GitLab 10.8, this variable can be used to define a [deploy policy for staging and production environments](#deploy-policy-for-staging-and-production-environments). |
| `CANARY_ENABLED` | From GitLab 11.0, this variable can be used to define a [deploy policy for canary environments](#deploy-policy-for-canary-environments-premium). | | `CANARY_ENABLED` | From GitLab 11.0, this variable can be used to define a [deploy policy for canary environments](#deploy-policy-for-canary-environments-premium). |
| `INCREMENTAL_ROLLOUT_MODE`| From GitLab 11.4, this variable, if present, can be used to enable an [incremental rollout](#incremental-rollout-to-production-premium) of your application for the production environment.<br/>Set to: <ul><li>`manual`, for manual deployment jobs.</li><li>`timed`, for automatic rollout deployments with a 5 minute delay each one.</li></ul> | | `INCREMENTAL_ROLLOUT_MODE`| From GitLab 11.4, this variable, if present, can be used to enable an [incremental rollout](#incremental-rollout-to-production-premium) of your application for the production environment. Set to `manual` for manual deployment jobs or `timed` for automatic rollout deployments with a 5 minute delay each one. |
| `TEST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `test` job. If the variable is present, the job will not be created. | | `TEST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `test` job. If the variable is present, the job will not be created. |
| `CODE_QUALITY_DISABLED` | From GitLab 11.0, this variable can be used to disable the `codequality` job. If the variable is present, the job will not be created. | | `CODE_QUALITY_DISABLED` | From GitLab 11.0, this variable can be used to disable the `codequality` job. If the variable is present, the job will not be created. |
| `LICENSE_MANAGEMENT_DISABLED` | From GitLab 11.0, this variable can be used to disable the `license_management` job. If the variable is present, the job will not be created. | | `LICENSE_MANAGEMENT_DISABLED` | From GitLab 11.0, this variable can be used to disable the `license_management` job. If the variable is present, the job will not be created. |
...@@ -1009,7 +1010,7 @@ multi-buildpack does not. ...@@ -1009,7 +1010,7 @@ multi-buildpack does not.
As of GitLab 10.0, the supported buildpacks are: As of GitLab 10.0, the supported buildpacks are:
``` ```text
- heroku-buildpack-multi v1.0.0 - heroku-buildpack-multi v1.0.0
- heroku-buildpack-ruby v168 - heroku-buildpack-ruby v168
- heroku-buildpack-nodejs v99 - heroku-buildpack-nodejs v99
......
# Abuse reports # Abuse reports
Report abuse from users to GitLab administrators. You can report abuse from other GitLab users to GitLab administrators.
A GitLab administrator [can then choose](admin_area/abuse_reports.md) to:
- Remove the user, which deletes them from the instance.
- Block the user, which denies them access to the instance.
- Or remove the report, which retains the users access to the instance.
You can report a user through their: You can report a user through their:
...@@ -12,7 +18,8 @@ You can report a user through their: ...@@ -12,7 +18,8 @@ You can report a user through their:
To report abuse from a user's profile page: To report abuse from a user's profile page:
1. Click on the exclamation point report abuse button at the top right of the user's profile. 1. Click on the exclamation point report abuse button at the top right of the
user's profile.
1. Complete an abuse report. 1. Complete an abuse report.
1. Click the **Send report** button. 1. Click the **Send report** button.
...@@ -26,15 +33,18 @@ To report abuse from a user's comment: ...@@ -26,15 +33,18 @@ To report abuse from a user's comment:
1. Click the **Send report** button. 1. Click the **Send report** button.
NOTE: **Note:** NOTE: **Note:**
A URL to the reported user's comment will be A URL to the reported user's comment will be pre-filled in the abuse report's
pre-filled in the abuse report's **Message** field. **Message** field.
## Reporting abuse through a user's issue or merge request ## Reporting abuse through a user's issue or merge request
The **Report abuse** button is displayed at the top right of the issue or merge request: The **Report abuse** button is displayed at the top right of the issue or merge request:
- When **Report abuse** is selected from the menu that appears when the **Close issue** or **Close merge request** button is clicked, for users that have permission to close the issue or merge request. - When **Report abuse** is selected from the menu that appears when the
- When viewing the issue or merge request, for users that don't have permission to close the issue or merge request. **Close issue** or **Close merge request** button is clicked, for users that
have permission to close the issue or merge request.
- When viewing the issue or merge request, for users that don't have permission
to close the issue or merge request.
With the **Report abuse** button displayed, to submit an abuse report: With the **Report abuse** button displayed, to submit an abuse report:
......
...@@ -2,30 +2,60 @@ ...@@ -2,30 +2,60 @@
View and resolve abuse reports from GitLab users. View and resolve abuse reports from GitLab users.
Admins can view abuse reports in the admin area and are able to GitLab administrators can view and [resolve](#resolving-abuse-reports) abuse
resolve the reports by removing the reported user, blocking the reported user, or removing the report. reports in the Admin Area.
## Reporting abuse ## Reporting abuse
To find out more about reporting abuse, see [abuse reports user documentation](../abuse_reports.md). To find out more about reporting abuse, see [abuse reports user
documentation](../abuse_reports.md).
## Resolving abuse reports ## Resolving abuse reports
To access abuse reports, go to **Admin area > Abuse Reports**. To access abuse reports, go to **Admin Area > Abuse Reports**.
There are 3 ways to resolve an abuse report, with a button for each method: There are 3 ways to resolve an abuse report, with a button for each method:
- Remove user & report: [Deletes the reported user](../profile/account/delete_account.md) from the instance and removes the abuse report from the list. - Remove user & report. This will:
- Block user: Blocks the reported user from the instance and does not remove the abuse report from the list. - [Delete the reported user](../profile/account/delete_account.md) from the
- Remove report: Removes the abuse report from the list and does not restrict the access for the reported user. instance.
- Remove the abuse report from the list.
- [Block user](#blocking-users).
- Remove report. This will:
- Remove the abuse report from the list.
- Remove access restrictions for the reported user.
The following is an example of the **Abuse Reports** page:
![abuse-reports-page-image](img/abuse_reports_page.png) ![abuse-reports-page-image](img/abuse_reports_page.png)
## Blocked users ### Blocking users
A blocked user cannot log in or access any repositories, but all of their data
remains.
Blocking a user:
- Leaves them in the abuse report list.
- Changes the **Block user** button to a disabled **Already blocked** button.
Blocking a user will not remove the abuse report from the list. The user will be notified with the
[following message](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/app/workers/email_receiver_worker.rb#L38):
Instead, the block button will be disabled and explain that the user is "Already blocked". ```text
You are still able to remove the user and/or report if necessary. Your account has been blocked. If you believe this is in error, contact a staff member.
```
After blocking, you can still either:
- Remove the user and report if necessary.
- Remove the report.
The following is an example of a blocked user listed on the **Abuse Reports**
page:
![abuse-report-blocked-user-image](img/abuse_report_blocked_user.png) ![abuse-report-blocked-user-image](img/abuse_report_blocked_user.png)
NOTE: **Note:**
Users can be [blocked](../../api/users.md#block-user) and
[unblocked](../../api/users.md#unblock-user) using the GitLab API.
...@@ -331,7 +331,7 @@ project's dependencies with their versions. This list can be generated only for ...@@ -331,7 +331,7 @@ project's dependencies with their versions. This list can be generated only for
[languages and package managers](#supported-languages-and-package-managers) [languages and package managers](#supported-languages-and-package-managers)
supported by Gemnasium. supported by Gemnasium.
To see the generated dependency list, navigate to your project's **Project > Dependency List**. To see the generated dependency list, navigate to your project's **Security & Compliance > Dependency List**.
## Versioning and release process ## Versioning and release process
......
...@@ -49,7 +49,7 @@ The group Security Dashboard gives an overview of the vulnerabilities of all the ...@@ -49,7 +49,7 @@ The group Security Dashboard gives an overview of the vulnerabilities of all the
projects in a group and its subgroups. projects in a group and its subgroups.
First, navigate to the Security Dashboard found under your group's First, navigate to the Security Dashboard found under your group's
**Overview > Security Dashboard**. **Security** tab.
Once you're on the dashboard, at the top you should see a series of filters for: Once you're on the dashboard, at the top you should see a series of filters for:
...@@ -58,7 +58,7 @@ Once you're on the dashboard, at the top you should see a series of filters for: ...@@ -58,7 +58,7 @@ Once you're on the dashboard, at the top you should see a series of filters for:
- Report type - Report type
- Project - Project
![dashboard with action buttons and metrics](img/dashboard.png) ![dashboard with action buttons and metrics](img/group_security_dashboard.png)
Selecting one or more filters will filter the results in this page. Selecting one or more filters will filter the results in this page.
The first section is an overview of all the vulnerabilities, grouped by severity. The first section is an overview of all the vulnerabilities, grouped by severity.
......
...@@ -45,7 +45,7 @@ The following identity providers are supported: ...@@ -45,7 +45,7 @@ The following identity providers are supported:
Feature.enable(:group_scim, group) Feature.enable(:group_scim, group)
``` ```
### GitLab configuration ## GitLab configuration
Once [Single sign-on](index.md) has been configured, we can: Once [Single sign-on](index.md) has been configured, we can:
...@@ -55,41 +55,48 @@ Once [Single sign-on](index.md) has been configured, we can: ...@@ -55,41 +55,48 @@ Once [Single sign-on](index.md) has been configured, we can:
![SCIM token configuration](img/scim_token.png) ![SCIM token configuration](img/scim_token.png)
## SCIM IdP configuration ## Identity Provider configuration
### Configuration on Azure ### Azure
In the [Single sign-on](index.md) configuration for the group, make sure First, double check the [Single sign-on](index.md) configuration for your group and ensure that **Name identifier value** (NameID) points to `user.objectid` or another unique identifier. This will match the `extern_uid` used on GitLab.
that the **Name identifier value** (NameID) points to a unique identifier, such
as the `user.objectid`. This will match the `extern_uid` used on GitLab.
The GitLab app in Azure needs to be configured following ![Name identifier value mapping](img/scim_name_identifier_mapping.png)
[Azure's SCIM setup](https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/use-scim-to-provision-users-and-groups#getting-started).
Note the following: #### Set up admin credentials
Next, configure your GitLab application in Azure by following the
[Provisioning users and groups to applications that support SCIM](https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/use-scim-to-provision-users-and-groups#provisioning-users-and-groups-to-applications-that-support-scim)
section in Azure's SCIM setup documentation.
During this configuration, note the following:
- The `Tenant URL` and `secret token` are the ones retrieved in the - The `Tenant URL` and `secret token` are the ones retrieved in the
[previous step](#gitlab-configuration). [previous step](#gitlab-configuration).
- Should there be any problems with the availability of GitLab or similar - Should there be any problems with the availability of GitLab or similar
errors, the notification email set will get those. errors, the notification email set will get those.
- It is recommended to set a notification email and check the **Send an email notification when a failure occurs** checkbox.
- For mappings, we will only leave `Synchronize Azure Active Directory Users to AppName` enabled. - For mappings, we will only leave `Synchronize Azure Active Directory Users to AppName` enabled.
You can then test the connection clicking on `Test Connection`. You can then test the connection by clicking on **Test Connection**. If the connection is successful, be sure to save your configuration before moving on.
### Synchronize Azure Active Directory users #### Configure attribute mapping
1. Click on `Synchronize Azure Active Directory Users to AppName`, to configure 1. Click on `Synchronize Azure Active Directory Users to AppName`, to configure the attribute mapping.
the attribute mapping. 1. Click **Delete** next to the `mail` mapping.
1. Select the unique identifier (in the example `objectId`) as the `id` and `externalId`, 1. Map `userPrincipalName` to `emails[type eq "work"].value` and change it's **Matching precedence** to `2`.
and enable the `Create`, `Update`, and `Delete` actions. 1. Map `mailNickname` to `userName`.
1. Map the `userPricipalName` to `emails[type eq "work"].value` and `mailNickname` to 1. Create a new mapping by clicking **Add New Mapping** then set **Source attribute** to `objectId`, **Target attribute** to `id`, **Match objects using this attribute** to `Yes`, and **Matching precedence** to `1`.
`userName`. 1. Create a new mapping by clicking **Add New Mapping** then set **Source attribute** to `objectId`, and **Target attribute** to `externalId`.
1. Click the `userPrincipalName` mapping and change **Match objects using this attribute** to `No`.
Example configuration: Save your changes and you should have the following configuration:
![Azure's attribute mapping configuration](img/scim_attribute_mapping.png) ![Azure's attribute mapping configuration](img/scim_attribute_mapping.png)
1. Click on **Show advanced options > Edit attribute list for AppName**. NOTE: **Note:** If you used a unique identifier **other than** `objectId`, be sure to map it instead to both `id` and `externalId`.
1. Below the mapping list click on **Show advanced options > Edit attribute list for AppName**.
1. Leave the `id` as the primary and only required field. 1. Leave the `id` as the primary and only required field.
NOTE: **Note:** NOTE: **Note:**
...@@ -99,12 +106,14 @@ You can then test the connection clicking on `Test Connection`. ...@@ -99,12 +106,14 @@ You can then test the connection clicking on `Test Connection`.
![Azure's attribute advanced configuration](img/scim_advanced.png) ![Azure's attribute advanced configuration](img/scim_advanced.png)
1. Save all the screens and, in the **Provisioning** step, set 1. Save all the screens and, in the **Provisioning** step, set
the `Provisioning Status` to `ON`. the `Provisioning Status` to `On`.
![Provisioning status toggle switch](img/scim_provisioning_status.png)
NOTE: **Note:** NOTE: **Note:**
You can control what is actually synced by selecting the `Scope`. For example, You can control what is actually synced by selecting the `Scope`. For example,
`Sync only assigned users and groups` will only sync the users assigned to `Sync only assigned users and groups` will only sync the users assigned to
the application (`Users and groups`), otherwise it will sync the whole Active Directory. the application (`Users and groups`), otherwise, it will sync the whole Active Directory.
Once enabled, the synchronization details and any errors will appear on the Once enabled, the synchronization details and any errors will appear on the
bottom of the **Provisioning** screen, together with a link to the audit logs. bottom of the **Provisioning** screen, together with a link to the audit logs.
......
...@@ -397,6 +397,7 @@ unordered or ordered lists: ...@@ -397,6 +397,7 @@ unordered or ordered lists:
- [ ] Sub-task 1 - [ ] Sub-task 1
- [x] Sub-task 2 - [x] Sub-task 2
- [ ] Sub-task 3 - [ ] Sub-task 3
1. [x] Completed task 1. [x] Completed task
1. [ ] Incomplete task 1. [ ] Incomplete task
1. [ ] Sub-task 1 1. [ ] Sub-task 1
...@@ -408,6 +409,7 @@ unordered or ordered lists: ...@@ -408,6 +409,7 @@ unordered or ordered lists:
- [ ] Sub-task 1 - [ ] Sub-task 1
- [x] Sub-task 2 - [x] Sub-task 2
- [ ] Sub-task 3 - [ ] Sub-task 3
1. [x] Completed task 1. [x] Completed task
1. [ ] Incomplete task 1. [ ] Incomplete task
1. [ ] Sub-task 1 1. [ ] Sub-task 1
...@@ -976,7 +978,7 @@ after the `</summary>` tag and before the `</details>` tag, as shown in the exam ...@@ -976,7 +978,7 @@ after the `</summary>` tag and before the `</details>` tag, as shown in the exam
These details _will_ remain **hidden** until expanded. These details _will_ remain **hidden** until expanded.
PASTE LOGS HERE PASTE LOGS HERE
</details> </details>
``` ```
...@@ -988,7 +990,7 @@ These details _will_ remain **hidden** until expanded. ...@@ -988,7 +990,7 @@ These details _will_ remain **hidden** until expanded.
These details <em>will</em> remain <b>hidden</b> until expanded. These details <em>will</em> remain <b>hidden</b> until expanded.
PASTE LOGS HERE PASTE LOGS HERE
</details> </details>
...@@ -1135,13 +1137,13 @@ GFM will autolink almost any URL you put into your text: ...@@ -1135,13 +1137,13 @@ GFM will autolink almost any URL you put into your text:
### Lists ### Lists
Ordered and unordered lists can be easily created. Add the number you want the list Ordered and unordered lists can be easily created. Add the number you want the list
to start with, like `1. ` (with a space) at the start of each line for ordered lists. to start with, like `1.`, followed by a space, at the start of each line for ordered lists.
After the first number, it does not matter what number you use, ordered lists will be After the first number, it does not matter what number you use, ordered lists will be
numbered automatically by vertical order, so repeating `1. ` for all items in the numbered automatically by vertical order, so repeating `1.` for all items in the
same list is common. If you start with a number other than `1. `, it will use that as the first same list is common. If you start with a number other than `1.`, it will use that as the first
number, and count up from there. number, and count up from there.
Add a `* `, `- ` or `+ ` (with a space) at the start of each line for unordered lists, but Add a `*`, `-` or `+`, followed by a space, at the start of each line for unordered lists, but
you should not use a mix of them. you should not use a mix of them.
Examples: Examples:
...@@ -1156,7 +1158,9 @@ Examples: ...@@ -1156,7 +1158,9 @@ Examples:
4. And another item. 4. And another item.
* Unordered lists can use asterisks * Unordered lists can use asterisks
- Or minuses - Or minuses
+ Or pluses + Or pluses
``` ```
...@@ -1170,9 +1174,11 @@ Examples: ...@@ -1170,9 +1174,11 @@ Examples:
1. Next ordered sub-list item 1. Next ordered sub-list item
1. And another item. 1. And another item.
* Unordered lists can use asterisks - Unordered lists can use asterisks
- Or minuses - Or minuses
+ Or pluses
- Or pluses
--- ---
......
...@@ -47,6 +47,7 @@ The following table depicts the various user permission levels in a project. ...@@ -47,6 +47,7 @@ The following table depicts the various user permission levels in a project.
| View approved/blacklisted licenses **(ULTIMATE)** | ✓ | ✓ | ✓ | ✓ | ✓ | | View approved/blacklisted licenses **(ULTIMATE)** | ✓ | ✓ | ✓ | ✓ | ✓ |
| View license management reports **(ULTIMATE)** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ | | View license management reports **(ULTIMATE)** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| View Security reports **(ULTIMATE)** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ | | View Security reports **(ULTIMATE)** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| View [Design Management](project/issues/design_management.md) pages **(PREMIUM)** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| View project code | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ | | View project code | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| Pull project code | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ | | Pull project code | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| View GitLab Pages protected by [access control](project/pages/introduction.md#gitlab-pages-access-control-core-only) | ✓ | ✓ | ✓ | ✓ | ✓ | | View GitLab Pages protected by [access control](project/pages/introduction.md#gitlab-pages-access-control-core-only) | ✓ | ✓ | ✓ | ✓ | ✓ |
...@@ -74,6 +75,7 @@ The following table depicts the various user permission levels in a project. ...@@ -74,6 +75,7 @@ The following table depicts the various user permission levels in a project.
| View Error Tracking list | | ✓ | ✓ | ✓ | ✓ | | View Error Tracking list | | ✓ | ✓ | ✓ | ✓ |
| Pull from [Maven repository](project/packages/maven_repository.md) or [NPM registry](project/packages/npm_registry.md) **(PREMIUM)** | | ✓ | ✓ | ✓ | ✓ | | Pull from [Maven repository](project/packages/maven_repository.md) or [NPM registry](project/packages/npm_registry.md) **(PREMIUM)** | | ✓ | ✓ | ✓ | ✓ |
| Publish to [Maven repository](project/packages/maven_repository.md) or [NPM registry](project/packages/npm_registry.md) **(PREMIUM)** | | | ✓ | ✓ | ✓ || | Publish to [Maven repository](project/packages/maven_repository.md) or [NPM registry](project/packages/npm_registry.md) **(PREMIUM)** | | | ✓ | ✓ | ✓ ||
| Upload [Design Management](project/issues/design_management.md) files **(PREMIUM)** | | | ✓ | ✓ | ✓ |
| Create new branches | | | ✓ | ✓ | ✓ | | Create new branches | | | ✓ | ✓ | ✓ |
| Push to non-protected branches | | | ✓ | ✓ | ✓ | | Push to non-protected branches | | | ✓ | ✓ | ✓ |
| Force push to non-protected branches | | | ✓ | ✓ | ✓ | | Force push to non-protected branches | | | ✓ | ✓ | ✓ |
......
# Deleting a User Account # Deleting a User account
Users can be deleted from a GitLab instance, either by:
- The user themselves.
- An administrator.
NOTE: **Note:** NOTE: **Note:**
Deleting a user will delete all projects in that user namespace. Deleting a user will delete all projects in that user namespace.
- As a user, you can delete your own account by navigating to **Settings** > **Account** and selecting **Delete account** ## As a user
- As an admin, you can delete a user account by navigating to the **Admin Area**, selecting the **Users** tab, selecting a user, and clicking on **Delete user**
As a user, you can delete your own account by:
1. Clicking on your avatar.
1. Navigating to **Settings > Account**.
1. Selecting **Delete account**.
## As an administrator
As an administrator, you can delete a user account by:
1. Navigating to **Admin Area > Overview > Users**.
1. Selecting a user.
1. Under the **Account** tab, clicking:
- **Delete user** to delete only the user but maintaining their
[associated records](#associated-records).
- **Delete user and contributions** to delete the user and
their associated records.
### Blocking a user
In addition to blocking a user
[via an abuse report](../../admin_area/abuse_reports.md#blocking-users),
a user can be blocked directly from the Admin area. To do this:
1. Navigate to **Admin Area > Overview > Users**.
1. Selecting a user.
1. Under the **Account** tab, click **Block user**.
## Associated Records ## Associated Records
> Introduced for issues in [GitLab 9.0][ce-7393], and for merge requests, award > - Introduced for issues in
emoji, notes, and abuse reports in [GitLab 9.1][ce-10467]. > [GitLab 9.0](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7393).
Hard deletion from abuse reports and spam logs was introduced in > - Introduced for merge requests, award emoji, notes, and abuse reports in
[GitLab 9.1][ce-10273], and from the API in [GitLab 9.3][ce-11853]. > [GitLab 9.1](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10467).
> - Hard deletion from abuse reports and spam logs was introduced in
> [GitLab 9.1](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10273),
> and from the API in
> [GitLab 9.3](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11853).
When a user account is deleted, not all associated records are deleted with it. When a user account is deleted, not all associated records are deleted with it.
Here's a list of things that will **not** be deleted: Here's a list of things that will **not** be deleted:
- Issues that the user created - Issues that the user created.
- Merge requests that the user created - Merge requests that the user created.
- Notes that the user created - Notes that the user created.
- Abuse reports that the user reported - Abuse reports that the user reported.
- Award emoji that the user created - Award emoji that the user created.
Instead of being deleted, these records will be moved to a system-wide Instead of being deleted, these records will be moved to a system-wide
user with the username "Ghost User", whose sole purpose is to act as a container for such records. Any commits made by a deleted user will still display the username of the original user. user with the username "Ghost User", whose sole purpose is to act as a container
for such records. Any commits made by a deleted user will still display the
When a user is deleted from an [abuse report](../../admin_area/abuse_reports.md) or spam log, these associated username of the original user.
records are not ghosted and will be removed, along with any groups the user
is a sole owner of. Administrators can also request this behaviour when When a user is deleted from an [abuse report](../../admin_area/abuse_reports.md)
deleting users from the [API](../../../api/users.md#user-deletion) or the or spam log, these associated records are not ghosted and will be removed, along
admin area. with any groups the user is a sole owner of.
[ce-7393]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7393 Administrators can also request this behavior when deleting users from the
[ce-10273]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10273 [API](../../../api/users.md#user-deletion) or the Admin Area.
[ce-10467]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10467
[ce-11853]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11853
...@@ -253,7 +253,7 @@ With RBAC disabled and services deployed, ...@@ -253,7 +253,7 @@ With RBAC disabled and services deployed,
[Auto DevOps](../../../../topics/autodevops/index.md) can now be leveraged [Auto DevOps](../../../../topics/autodevops/index.md) can now be leveraged
to build, test, and deploy the app. to build, test, and deploy the app.
[Enable Auto DevOps](../../../../topics/autodevops/index.md#enablingdisabling-auto-devops-at-the-project-level) [Enable Auto DevOps](../../../../topics/autodevops/index.md#at-the-project-level)
if not already enabled. If a wildcard DNS entry was created resolving to the if not already enabled. If a wildcard DNS entry was created resolving to the
Load Balancer, enter it in the `domain` field under the Auto DevOps settings. Load Balancer, enter it in the `domain` field under the Auto DevOps settings.
Otherwise, the deployed app will not be externally available outside of the cluster. Otherwise, the deployed app will not be externally available outside of the cluster.
......
# Monitoring HAProxy # Monitoring HAProxy
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12621) in GitLab 9.4 > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12621) in GitLab 9.4
GitLab has support for automatically detecting and monitoring HAProxy. This is provided by leveraging the [HAProxy Exporter](https://github.com/prometheus/haproxy_exporter), which translates HAProxy statistics into a Prometheus readable form. GitLab has support for automatically detecting and monitoring HAProxy. This is provided by leveraging the [HAProxy Exporter](https://github.com/prometheus/haproxy_exporter), which translates HAProxy statistics into a Prometheus readable form.
......
# Design Management **(PREMIUM)**
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/660) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.2.
CAUTION: **Warning:**
This an __alpha__ feature and is subject to change at any time without
prior notice.
## Overview
Design Management allows you to upload design assets (wireframes, mockups, etc.)
to GitLab issues and keep them stored in one single place, accessed by the Design
Management's page within an issue, giving product designers, product managers, and engineers a
way to collaborate on designs over one single source of truth.
You can easily share mock-ups of designs with your team, or visual regressions can be easily
viewed and addressed.
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
For an overview, see the video [Design Management (GitLab 12.2)](https://www.youtube.com/watch?v=CCMtCqdK_aM).
## Requirements
Design Management requires
[Large File Storage (LFS)](../../../workflow/lfs/manage_large_binaries_with_git_lfs.md)
to be enabled:
- For GitLab.com, LFS is already enabled.
- For self-managed instances, a GitLab administrator must have
[enabled LFS globally](../../../workflow/lfs/lfs_administration.md).
- For both GitLab.com and self-managed instances: LFS must be enabled for the project itself.
If enabled globally, LFS will be enabled by default to all projects. To enable LFS on the
project level, navigate to your project's **Settings > General**, expand **Visibility, project features, permissions**
and enable **Git Large File Storage**.
## Limitations
- Files uploaded must have a file extension of either `png`, `jpg`, `jpeg`, `gif`, `bmp`, `tiff` or `ico`. The [`svg` extension is not yet supported](https://gitlab.com/gitlab-org/gitlab-ee/issues/12771).
- [Designs cannot yet be deleted](https://gitlab.com/gitlab-org/gitlab-ee/issues/11089).
- Design Management is [not yet supported in the project export](https://gitlab.com/gitlab-org/gitlab-ee/issues/11090).
## The Design Management page
Navigate to the **Design Management** page from any issue by clicking the **Designs** tab:
![Designs tab](img/design_management_v12_2.png)
## Adding designs
To upload design images, click the **Upload Designs** button and select images to upload.
Designs with the same filename as an existing uploaded design will create a new version
of the design, and will replace the previous version.
## Viewing designs
Images on the Design Management page can be enlarged by clicking on them.
...@@ -120,6 +120,12 @@ associated label or assignee will change to match that of the new column. The en ...@@ -120,6 +120,12 @@ associated label or assignee will change to match that of the new column. The en
board can also be filtered to only include issues from a certain milestone or an overarching board can also be filtered to only include issues from a certain milestone or an overarching
label. label.
### Design Management **(PREMIUM)**
With [Design Management](design_management.md), you can upload design
assets to issues and view them all together to easily share and
collaborate with your team.
### Epics **(ULTIMATE)** ### Epics **(ULTIMATE)**
[Epics](../../group/epics/index.md) let you manage your portfolio of projects more [Epics](../../group/epics/index.md) let you manage your portfolio of projects more
......
...@@ -9,8 +9,18 @@ in [GitLab Starter](https://about.gitlab.com/pricing/) 9.3. ...@@ -9,8 +9,18 @@ in [GitLab Starter](https://about.gitlab.com/pricing/) 9.3.
With the help of [GitLab CI/CD](../../../ci/README.md), you can analyze your With the help of [GitLab CI/CD](../../../ci/README.md), you can analyze your
source code quality using GitLab Code Quality. source code quality using GitLab Code Quality.
Code Quality uses [Code Climate Engines](https://codeclimate.com), which are
free and open source. Code Quality doesn't require a Code Climate subscription. Code Quality:
- Uses [Code Climate Engines](https://codeclimate.com), which are
free and open source. Code Quality doesn't require a Code Climate
subscription.
- Runs in [pipelines](../../../ci/pipelines.md) using an Docker image built in
[GitLab Code
Quality](https://gitlab.com/gitlab-org/security-products/codequality) project.
- Can make use of a [template](#template-and-examples).
- Is available with [Auto
DevOps](../../../topics/autodevops/index.md#auto-code-quality-starter).
Going a step further, GitLab can show the Code Quality report right Going a step further, GitLab can show the Code Quality report right
in the merge request widget area: in the merge request widget area:
...@@ -21,22 +31,48 @@ in the merge request widget area: ...@@ -21,22 +31,48 @@ in the merge request widget area:
For instance, consider the following workflow: For instance, consider the following workflow:
1. Your backend team member starts a new implementation for making a certain feature in your app faster 1. Your backend team member starts a new implementation for making a certain
1. With Code Quality reports, they analyze how their implementation is impacting the code quality feature in your app faster.
1. The metrics show that their code degrade the quality in 10 points 1. With Code Quality reports, they analyze how their implementation is impacting
1. You ask a co-worker to help them with this modification the code quality.
1. They both work on the changes until Code Quality report displays no degradations, only improvements 1. The metrics show that their code degrade the quality in 10 points.
1. You approve the merge request and authorize its deployment to staging 1. You ask a co-worker to help them with this modification.
1. Once verified, their changes are deployed to production 1. They both work on the changes until Code Quality report displays no
degradations, only improvements.
1. You approve the merge request and authorize its deployment to staging.
1. Once verified, their changes are deployed to production.
## Template and examples
For most GitLab instances, the supplied template is the preferred method of
implementing Code Quality. See
[Analyze your project's Code Quality](../../../ci/examples/code_quality.md) for:
- Information on the builtin GitLab Code Quality template.
- Examples of manual GitLab configuration for earlier GitLab versions.
## How it works ## Configuring jobs using variables
First of all, you need to define a job in your `.gitlab-ci.yml` file that generates the The Code Quality job supports environment variables that users can set to
[Code Quality report artifact](../../../ci/yaml/README.md#artifactsreportscodequality-starter). configure job execution at runtime.
The Code Quality report artifact is a subset of the For a list of available environment variables, see
[Code Climate spec](https://github.com/codeclimate/spec/blob/master/SPEC.md#data-types). [Environment variables](https://gitlab.com/gitlab-org/security-products/codequality/blob/master/README.md#environment-variables).
It must be a JSON file containing an array of objects with the following properties:
## Implementing a custom tool
It's possible to have a custom tool provide Code Quality reports in GitLab. To
do this:
1. Define a job in your `.gitlab-ci.yml` file that generates the
[Code Quality report
artifact](../../../ci/yaml/README.md#artifactsreportscodequality-starter).
1. Configure your tool to generate the Code Quality report artifact as a JSON
file that implements subset of the [Code Climate
spec](https://github.com/codeclimate/spec/blob/master/SPEC.md#data-types).
The Code Quality report artifact JSON file must contain an array of objects
with the following properties:
| Name | Description | | Name | Description |
| ---------------------- | -------------------------------------------------------------------------------------- | | ---------------------- | -------------------------------------------------------------------------------------- |
...@@ -63,13 +99,16 @@ Example: ...@@ -63,13 +99,16 @@ Example:
``` ```
NOTE: **Note:** NOTE: **Note:**
Although the Code Climate spec supports more properties, those are ignored by GitLab. Although the Code Climate spec supports more properties, those are ignored by
GitLab.
## Code Quality reports
For more information on what the Code Quality job should look like, check the Once the Code Quality job has completed, GitLab:
example on [analyzing a project's code quality](../../../ci/examples/code_quality.md).
GitLab then checks this report, compares the metrics between the source and target - Checks the generated report.
branches, and shows the information right on the merge request. - Compares the metrics between the source and target branches.
- Shows the information right on the merge request.
If multiple jobs in a pipeline generate a code quality artifact, only the artifact from If multiple jobs in a pipeline generate a code quality artifact, only the artifact from
the last created job (the job with the largest job ID) is used. To avoid confusion, the last created job (the job with the largest job ID) is used. To avoid confusion,
......
...@@ -22,6 +22,9 @@ export default { ...@@ -22,6 +22,9 @@ export default {
className() { className() {
return `vulnerability-count-${this.severity}`; return `vulnerability-count-${this.severity}`;
}, },
qaSelector() {
return `vulnerability_count_${this.severity}`;
},
severityTitle() { severityTitle() {
return SEVERITY_LEVELS[this.severity] || this.severity; return SEVERITY_LEVELS[this.severity] || this.severity;
}, },
...@@ -32,7 +35,7 @@ export default { ...@@ -32,7 +35,7 @@ export default {
<template> <template>
<div class="vulnerability-count" :class="className"> <div class="vulnerability-count" :class="className">
<div class="vulnerability-count-header">{{ severityTitle }}</div> <div class="vulnerability-count-header">{{ severityTitle }}</div>
<div class="vulnerability-count-body"> <div class="vulnerability-count-body" :data-qa-selector="qaSelector">
<span v-if="isLoading">&mdash;</span> <span v-else>{{ count }}</span> <span v-if="isLoading">&mdash;</span> <span v-else>{{ count }}</span>
</div> </div>
</div> </div>
......
...@@ -101,14 +101,14 @@ class Projects::FeatureFlagsController < Projects::ApplicationController ...@@ -101,14 +101,14 @@ class Projects::FeatureFlagsController < Projects::ApplicationController
params.require(:operations_feature_flag) params.require(:operations_feature_flag)
.permit(:name, :description, :active, .permit(:name, :description, :active,
scopes_attributes: [:environment_scope, :active, scopes_attributes: [:environment_scope, :active,
strategies: [:name, parameters: [:groupId, :percentage]]]) strategies: [:name, parameters: [:groupId, :percentage, :userIds]]])
end end
def update_params def update_params
params.require(:operations_feature_flag) params.require(:operations_feature_flag)
.permit(:name, :description, :active, .permit(:name, :description, :active,
scopes_attributes: [:id, :environment_scope, :active, :_destroy, scopes_attributes: [:id, :environment_scope, :active, :_destroy,
strategies: [:name, parameters: [:groupId, :percentage]]]) strategies: [:name, parameters: [:groupId, :percentage, :userIds]]])
end end
def feature_flag_json(feature_flag) def feature_flag_json(feature_flag)
......
...@@ -144,8 +144,11 @@ module EE ...@@ -144,8 +144,11 @@ module EE
::Group ::Group
end end
# Column name used by RelativePositioning for scoping. This is not related to `parent_class` above. def relative_positioning_query_base(epic)
def parent_column in_parents(epic.parent_ids)
end
def relative_positioning_parent_column
:parent_id :parent_id
end end
......
...@@ -417,7 +417,7 @@ module EE ...@@ -417,7 +417,7 @@ module EE
return if repository_read_only? return if repository_read_only?
return if repository_storage == new_repository_storage_key return if repository_storage == new_repository_storage_key
raise ArgumentError unless ::Gitlab.config.repositories.storages.keys.include?(new_repository_storage_key) raise ArgumentError unless ::Gitlab.config.repositories.storages.key?(new_repository_storage_key)
run_after_commit { ProjectUpdateRepositoryStorageWorker.perform_async(id, new_repository_storage_key) } run_after_commit { ProjectUpdateRepositoryStorageWorker.perform_async(id, new_repository_storage_key) }
self.repository_read_only = true self.repository_read_only = true
......
...@@ -13,11 +13,11 @@ class EpicIssue < ApplicationRecord ...@@ -13,11 +13,11 @@ class EpicIssue < ApplicationRecord
scope :in_epic, ->(epic_id) { where(epic_id: epic_id) } scope :in_epic, ->(epic_id) { where(epic_id: epic_id) }
class << self def self.relative_positioning_query_base(epic_issue)
alias_method :in_parents, :in_epic in_epic(epic_issue.parent_ids)
end
def parent_column def self.relative_positioning_parent_column
:epic_id :epic_id
end end
end
end end
# frozen_string_literal: true
module Geo
class ContainerRepositoryUpdatedEvent < ApplicationRecord
include Geo::Model
include Geo::Eventable
belongs_to :container_repository
validates :container_repository, presence: true
end
end
...@@ -16,7 +16,8 @@ module Geo ...@@ -16,7 +16,8 @@ module Geo
Geo::HashedStorageAttachmentsEvent Geo::HashedStorageAttachmentsEvent
Geo::LfsObjectDeletedEvent Geo::LfsObjectDeletedEvent
Geo::JobArtifactDeletedEvent Geo::JobArtifactDeletedEvent
Geo::UploadDeletedEvent].freeze Geo::UploadDeletedEvent
Geo::ContainerRepositoryUpdatedEvent].freeze
belongs_to :cache_invalidation_event, belongs_to :cache_invalidation_event,
class_name: 'Geo::CacheInvalidationEvent', class_name: 'Geo::CacheInvalidationEvent',
...@@ -66,6 +67,10 @@ module Geo ...@@ -66,6 +67,10 @@ module Geo
class_name: 'Geo::ResetChecksumEvent', class_name: 'Geo::ResetChecksumEvent',
foreign_key: :reset_checksum_event_id foreign_key: :reset_checksum_event_id
belongs_to :container_repository_updated_event,
class_name: 'Geo::ContainerRepositoryUpdatedEvent',
foreign_key: :container_repository_updated_event_id
def self.latest_event def self.latest_event
order(id: :desc).first order(id: :desc).first
end end
...@@ -97,7 +102,8 @@ module Geo ...@@ -97,7 +102,8 @@ module Geo
job_artifact_deleted_event || job_artifact_deleted_event ||
upload_deleted_event || upload_deleted_event ||
reset_checksum_event || reset_checksum_event ||
cache_invalidation_event cache_invalidation_event ||
container_repository_updated_event
end end
def project_id def project_id
......
# frozen_string_literal: true
module Geo
class ContainerRepositoryUpdatedEventStore < EventStore
self.event_type = :container_repository_updated_event
attr_reader :repository
def initialize(repository)
@repository = repository
end
private
def build_event
Geo::ContainerRepositoryUpdatedEvent.new(
container_repository: repository
)
end
# This is called by ProjectLogHelpers to build json log with context info
#
# @see ::Gitlab::Geo::ProjectLogHelpers
def base_log_data(message)
{
class: self.class.name,
container_repository_id: repository.try(:id),
message: message
}.compact
end
end
end
...@@ -3,6 +3,14 @@ ...@@ -3,6 +3,14 @@
class FeatureFlagStrategiesValidator < ActiveModel::EachValidator class FeatureFlagStrategiesValidator < ActiveModel::EachValidator
STRATEGY_DEFAULT = 'default'.freeze STRATEGY_DEFAULT = 'default'.freeze
STRATEGY_GRADUALROLLOUTUSERID = 'gradualRolloutUserId'.freeze STRATEGY_GRADUALROLLOUTUSERID = 'gradualRolloutUserId'.freeze
STRATEGY_USERWITHID = 'userWithId'.freeze
# Order key names alphabetically
STRATEGIES = {
STRATEGY_DEFAULT => [].freeze,
STRATEGY_GRADUALROLLOUTUSERID => %w[groupId percentage].freeze,
STRATEGY_USERWITHID => ['userIds'].freeze
}.freeze
USERID_MAX_LENGTH = 256
def validate_each(record, attribute, value) def validate_each(record, attribute, value)
return unless value return unless value
...@@ -12,20 +20,40 @@ class FeatureFlagStrategiesValidator < ActiveModel::EachValidator ...@@ -12,20 +20,40 @@ class FeatureFlagStrategiesValidator < ActiveModel::EachValidator
strategy_validations(record, attribute, strategy) strategy_validations(record, attribute, strategy)
end end
else else
record.errors.add(attribute, 'must be an array of strategy hashes') error(record, attribute, 'must be an array of strategy hashes')
end end
end end
private private
def strategy_validations(record, attribute, strategy) def strategy_validations(record, attribute, strategy)
validate_name(record, attribute, strategy) &&
validate_parameters_type(record, attribute, strategy) &&
validate_parameters_keys(record, attribute, strategy) &&
validate_parameters_values(record, attribute, strategy)
end
def validate_name(record, attribute, strategy)
STRATEGIES.key?(strategy['name']) || error(record, attribute, 'strategy name is invalid')
end
def validate_parameters_type(record, attribute, strategy)
strategy['parameters'].is_a?(Hash) || error(record, attribute, 'parameters are invalid')
end
def validate_parameters_keys(record, attribute, strategy)
name, parameters = strategy.values_at('name', 'parameters')
actual_keys = parameters.keys.sort
expected_keys = STRATEGIES[name]
expected_keys == actual_keys || error(record, attribute, 'parameters are invalid')
end
def validate_parameters_values(record, attribute, strategy)
case strategy['name'] case strategy['name']
when STRATEGY_DEFAULT
default_parameters_validation(record, attribute, strategy)
when STRATEGY_GRADUALROLLOUTUSERID when STRATEGY_GRADUALROLLOUTUSERID
gradual_rollout_user_id_parameters_validation(record, attribute, strategy) gradual_rollout_user_id_parameters_validation(record, attribute, strategy)
else when STRATEGY_USERWITHID
record.errors.add(attribute, 'strategy name is invalid') user_with_id_parameters_validation(record, attribute, strategy)
end end
end end
...@@ -34,17 +62,34 @@ class FeatureFlagStrategiesValidator < ActiveModel::EachValidator ...@@ -34,17 +62,34 @@ class FeatureFlagStrategiesValidator < ActiveModel::EachValidator
group_id = strategy.dig('parameters', 'groupId') group_id = strategy.dig('parameters', 'groupId')
unless percentage.is_a?(String) && percentage.match(/\A[1-9]?[0-9]\z|\A100\z/) unless percentage.is_a?(String) && percentage.match(/\A[1-9]?[0-9]\z|\A100\z/)
record.errors.add(attribute, 'percentage must be a string between 0 and 100 inclusive') error(record, attribute, 'percentage must be a string between 0 and 100 inclusive')
end end
unless group_id.is_a?(String) && group_id.match(/\A[a-z]{1,32}\z/) unless group_id.is_a?(String) && group_id.match(/\A[a-z]{1,32}\z/)
record.errors.add(attribute, 'groupId parameter is invalid') error(record, attribute, 'groupId parameter is invalid')
end
end
def user_with_id_parameters_validation(record, attribute, strategy)
user_ids = strategy.dig('parameters', 'userIds')
unless user_ids.is_a?(String) && !user_ids.match(/[\n\r\t]|,,/) && valid_ids?(user_ids.split(","))
error(record, attribute, "userIds must be a string of unique comma separated values each #{USERID_MAX_LENGTH} characters or less")
end end
end end
def default_parameters_validation(record, attribute, strategy) def valid_ids?(user_ids)
unless strategy['parameters'] == {} user_ids.uniq.length == user_ids.length &&
record.errors.add(attribute, 'parameters must be empty for default strategy') user_ids.all? { |id| valid_id?(id) }
end end
def valid_id?(user_id)
user_id.present? &&
user_id.strip == user_id &&
user_id.length <= USERID_MAX_LENGTH
end
def error(record, attribute, msg)
record.errors.add(attribute, msg)
false
end end
end end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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