Commit a822c6de authored by Robert Speicher's avatar Robert Speicher

Merge branch 'master' into rs-sign_in-ee

parents eb2bc1c0 294d1804
......@@ -348,6 +348,7 @@ db:migrate:reset-mysql:
- git fetch origin v8.14.10-ee
- git checkout -f FETCH_HEAD
- bundle install $BUNDLE_INSTALL_FLAGS
- cp config/gitlab.yml.example config/gitlab.yml
- bundle exec rake db:drop db:create db:schema:load db:seed_fu
- git checkout $CI_COMMIT_SHA
- bundle install $BUNDLE_INSTALL_FLAGS
......
9.3.0-pre
9.4.0-pre
......@@ -43,7 +43,8 @@ class GeoNodeStatus {
if (status.health === 'Healthy') {
this.$health.html('');
} else {
this.$health.html(`<code class="geo-health">${status.health}</code>`);
const strippedData = $('<div>').html(`${status.health}`).text();
this.$health.html(`<code class="geo-health">${strippedData}</code>`);
}
this.$status.show();
......
......@@ -105,9 +105,9 @@
this.measurements = measurements.small;
}
this.data = query.result[0].values;
this.unitOfDisplay = query.unit || 'N/A';
this.unitOfDisplay = query.unit || '';
this.yAxisLabel = this.columnData.y_label || 'Values';
this.legendTitle = query.legend || 'Average';
this.legendTitle = query.label || 'Average';
this.graphWidth = this.$refs.baseSvg.clientWidth -
this.margin.left - this.margin.right;
this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom;
......
......@@ -62,7 +62,7 @@ import findAndFollowLink from './shortcuts_dashboard_navigation';
if (Cookies.get(performanceBarCookieName) === 'true') {
Cookies.remove(performanceBarCookieName, { path: '/' });
} else {
Cookies.set(performanceBarCookieName, true, { path: '/' });
Cookies.set(performanceBarCookieName, 'true', { path: '/' });
}
gl.utils.refreshCurrentPage();
};
......
......@@ -74,11 +74,17 @@ $red-700: #a62d19;
$red-800: #8b2615;
$red-900: #711e11;
$purple-600: #6e49cb;
$purple-650: #5c35ae;
$purple-700: #4a2192;
$purple-800: #2c0a5c;
$purple-900: #380d75;
$indigo-50: #f7f7ff;
$indigo-100: #ebebfa;
$indigo-200: #d1d1f0;
$indigo-300: #a6a6de;
$indigo-400: #7c7ccc;
$indigo-500: #6666c4;
$indigo-600: #5b5bbd;
$indigo-700: #4b4ba3;
$indigo-800: #393982;
$indigo-900: #292961;
$indigo-950: #1a1a40;
$black: #000;
$black-transparent: rgba(0, 0, 0, 0.3);
......
......@@ -4,7 +4,7 @@
header.navbar-gitlab-new {
color: $white-light;
background-color: $purple-900;
background: linear-gradient(to right, $indigo-900, $indigo-800);
border-bottom: 0;
.header-content {
......@@ -24,11 +24,9 @@ header.navbar-gitlab-new {
> a {
display: flex;
align-items: center;
padding-top: 3px;
padding-right: $gl-padding;
padding-left: $gl-padding;
margin-left: -$gl-padding;
border-bottom: 3px solid transparent;
@media (min-width: $screen-sm-min) {
padding-right: $gl-padding;
......@@ -45,9 +43,8 @@ header.navbar-gitlab-new {
&:hover,
&:focus {
color: currentColor;
color: $tanuki-yellow;
text-decoration: none;
border-bottom-color: $white-light;
}
}
}
......@@ -71,7 +68,7 @@ header.navbar-gitlab-new {
.navbar-collapse {
padding-left: 0;
color: $white-light;
color: $indigo-200;
box-shadow: 0;
@media (max-width: $screen-xs-max) {
......@@ -101,7 +98,7 @@ header.navbar-gitlab-new {
font-size: 14px;
text-align: center;
color: currentColor;
border-left: 1px solid lighten($purple-700, 10%);
border-left: 1px solid lighten($indigo-700, 10%);
&:hover,
&:focus,
......@@ -120,6 +117,7 @@ header.navbar-gitlab-new {
li {
.badge {
box-shadow: none;
font-weight: 600;
}
}
}
......@@ -133,12 +131,11 @@ header.navbar-gitlab-new {
> a {
background: none;
opacity: .9;
will-change: opacity;
will-change: color;
&.header-user-dropdown-toggle {
.header-user-avatar {
border-color: $white-light;
border-color: $indigo-200;
}
}
......@@ -165,29 +162,34 @@ header.navbar-gitlab-new {
.navbar-sub-nav {
display: flex;
margin-bottom: 0;
color: $white-light;
color: $indigo-200;
> li {
&.active > a,
a:hover,
a:focus {
border-bottom-color: $white-light;
> a:hover,
> a:focus {
box-shadow: inset 0 -3px 0 rgba($indigo-200, .4);
text-decoration: none;
outline: 0;
opacity: 1;
color: $white-light;
}
&.active > a {
box-shadow: inset 0 -3px 0 $indigo-500;
color: $white-light;
font-weight: 700;
}
> a {
display: block;
padding: 16px 10px 13px;
padding: 16px 10px;
font-size: 13px;
color: currentColor;
border-bottom: 3px solid transparent;
opacity: .9;
will-change: opacity;
box-shadow: inset 0 0 0 transparent;
will-change: box-shadow;
transition: box-shadow 0.15s;
@media (min-width: $screen-sm-min) {
padding: 15px $gl-padding 12px;
padding: 15px $gl-padding;
font-size: 14px;
}
}
......@@ -207,55 +209,60 @@ header.navbar-gitlab-new {
.search {
form {
border-color: $purple-800;
border: 0;
background-color: rgba($indigo-200, .2);
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s, background-color ease-in-out 0.15s;
&:hover {
border-color: rgba($white-light, .6);
background-color: rgba($indigo-200, .3);
box-shadow: none;
}
}
&.search-active form {
border-color: $white-light;
}
form,
.search-input {
background-color: $purple-700;
background-color: rgba($indigo-200, .3);
box-shadow: none;
}
.search-input {
color: $white-light;
background: none;
}
.search-input::placeholder {
color: rgba($white-light, .6);
color: rgba($indigo-200, .8);
}
.location-badge {
font-size: 12px;
color: rgba($white-light, .6);
background-color: $purple-800;
color: $indigo-100;
background-color: rgba($indigo-200, .1);
transition: color 0.15s;
will-change: color;
margin: -4px 4px -4px -4px;
line-height: 25px;
padding: 4px 8px;
border-radius: 2px 0 0 2px;
border-right: 1px solid $indigo-800;
height: 34px;
}
.search-input-wrap {
.search-icon,
.clear-icon {
color: rgba($white-light, .6);
color: rgba($indigo-200, .8);
}
}
&.search-active {
.location-badge {
color: $white-light;
background-color: $purple-800;
background-color: rgba($indigo-200, .2);
}
.search-input-wrap {
.search-icon {
color: rgba($white-light, .6);
color: rgba($indigo-200, .8);
}
.clear-icon {
......
......@@ -2,6 +2,15 @@
@import 'framework/tw_bootstrap_variables';
@import "bootstrap/variables";
$active-background: rgba(0,0,0,.04);
$active-border: $indigo-500;
$active-color: $indigo-700;
$active-hover-background: $active-background;
$active-hover-color: $gl-text-color;
$inactive-badge-background: rgba(0,0,0,.08);
$hover-background: $indigo-700;
$hover-color: $white-light;
$inactive-color: $gl-text-color-secondary;
$new-sidebar-width: 220px;
.page-with-new-sidebar {
......@@ -17,24 +26,39 @@ $new-sidebar-width: 220px;
}
.context-header {
background-color: $gray-normal;
border-bottom: 1px solid $border-color;
font-weight: 600;
display: flex;
align-items: center;
padding: 10px 14px;
padding: 10px 16px 10px 10px;
color: $gl-text-color;
.avatar-container {
flex: 0 0 40px;
}
&:hover {
background-color: $border-color;
background-color: $hover-background;
color: $hover-color;
border-color: $hover-background;
.avatar-container {
border-color: transparent;
}
.settings-avatar {
background-color: $indigo-500;
i {
color: $hover-color;
}
}
}
}
.settings-avatar {
background-color: $white-light;
transition: background-color 100ms linear;
i {
font-size: 20px;
......@@ -42,6 +66,7 @@ $new-sidebar-width: 220px;
color: $gl-text-color-secondary;
text-align: center;
align-self: center;
transition: color 100ms linear;
}
}
......@@ -54,11 +79,15 @@ $new-sidebar-width: 220px;
bottom: 0;
left: 0;
overflow: auto;
background-color: $gray-light;
border-right: 1px solid $border-color;
background-color: $gray-normal;
box-shadow: inset -2px 0 0 $border-color;
a {
text-decoration: none;
}
ul {
padding: 0;
padding-left: 0;
list-style: none;
}
......@@ -67,13 +96,18 @@ $new-sidebar-width: 220px;
a {
display: block;
padding: 12px 14px;
padding: 12px 16px;
color: $inactive-color;
}
}
a {
color: $gl-text-color;
text-decoration: none;
li.active {
box-shadow: inset 4px 0 0 $active-border;
> a {
color: $active-color;
font-weight: 700;
}
}
@media (max-width: $screen-xs-max) {
......@@ -83,22 +117,28 @@ $new-sidebar-width: 220px;
.sidebar-sub-level-items {
display: none;
padding-bottom: 8px;
> li {
a {
padding: 12px 24px;
color: $gl-text-color-light;
font-size: 12px;
padding: 8px 16px 8px 24px;
&:hover {
color: $gl-text-color;
background-color: $border-color;
&:hover,
&:focus {
background: $active-hover-background;
color: $active-hover-color;
}
}
&.active {
> a {
color: $purple-650;
font-weight: 600;
a {
&,
&:hover,
&:focus {
background: $active-background;
color: $active-color;
}
}
}
}
......@@ -108,35 +148,31 @@ $new-sidebar-width: 220px;
> li {
.badge {
float: right;
background-color: $border-color;
color: $gl-text-color;
background-color: $inactive-badge-background;
color: $inactive-color;
}
&.active {
> a {
background-color: $purple-600;
color: $white-light;
font-weight: 600;
}
background: $active-background;
.badge {
background-color: $purple-700;
color: $white-light;
color: $active-color;
font-weight: 600;
}
.sidebar-sub-level-items {
background-color: $gray-normal;
border-left: 6px solid $purple-600;
display: block;
}
}
&:not(.active) > a:hover {
background-color: $border-color;
> a:hover {
background-color: $hover-background;
color: $hover-color;
.badge {
transition: background-color 100ms linear;
background-color: $gray-normal;
transition: background-color 100ms linear, color 100ms linear;
background-color: $indigo-500;
color: $hover-color;
}
}
}
......@@ -155,3 +191,13 @@ $new-sidebar-width: 220px;
// scss-lint:enable DuplicateProperty
}
}
// Change color of all horizontal tabs to match the new indigo color
.nav-links li.active a {
border-bottom-color: $active-border;
.badge {
font-weight: 600;
}
}
......@@ -60,8 +60,6 @@
@media (min-width: $screen-sm-min) {
display: -webkit-flex;
display: flex;
width: 400px;
max-width: 50%;
}
}
......@@ -71,7 +69,6 @@
@media (min-width: $screen-sm-min) {
display: -webkit-flex;
display: flex;
width: 100%;
margin-top: 3px;
}
}
......@@ -87,18 +84,10 @@
.member-form-control {
@media (max-width: $screen-xs-max) {
padding: 5px 0;
padding-bottom: 5px;
margin-left: 0;
margin-right: 0;
}
@media (min-width: $screen-sm-min) {
width: 50%;
}
.dropdown-menu-toggle {
width: 100%;
}
}
.member-access-text {
......@@ -266,3 +255,102 @@
}
}
}
.content-list.members-list li {
display: flex;
justify-content: space-between;
.list-item-name {
float: none;
display: flex;
flex: 1;
}
.user-info {
padding-right: 10px;
}
.member {
font-weight: bold;
overflow-wrap: break-word;
word-break: break-all;
}
.member-group-link {
display: inline-block;
}
.form-control {
width: inherit;
}
.btn {
align-self: flex-start;
}
.form-horizontal ~ .btn {
margin-right: 0;
}
@media (max-width: $screen-xs-max) {
display: block;
.controls > .btn {
margin-left: 0;
margin-right: 0;
display: block;
}
.form-control {
width: 100%;
}
.member-access-text {
line-height: 0;
margin-left: 50px;
}
.member-controls {
margin-top: 5px;
}
.form-horizontal {
margin-top: 10px;
}
}
}
.panel-mobile {
.content-list.members-list li {
display: block;
.member-controls {
float: none;
display: block;
}
.dropdown-menu-toggle,
.dropdown-menu,
.form-control,
.list-item-name {
width: 100%;
}
.dropdown-menu {
margin-top: 0;
}
.form-horizontal {
display: block;
}
.member-form-control {
margin: 5px 0;
}
.btn {
width: 100%;
margin-left: 0;
}
}
}
......@@ -731,11 +731,11 @@
.merge-request-tabs-holder {
top: $header-height;
z-index: 100;
z-index: 200;
background-color: $white-light;
border-bottom: 1px solid $border-color;
@media(min-width: $screen-sm-min) {
@media (min-width: $screen-sm-min) {
position: sticky;
position: -webkit-sticky;
}
......@@ -770,6 +770,12 @@
max-width: $limited-layout-width;
margin-left: auto;
margin-right: auto;
.inner-page-scroll-tabs {
background-color: $white-light;
margin-left: -$gl-padding;
padding-left: $gl-padding;
}
}
}
......
......@@ -127,6 +127,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:metrics_port,
:metrics_sample_interval,
:metrics_timeout,
:performance_bar_allowed_group_id,
:performance_bar_enabled,
:recaptcha_enabled,
:recaptcha_private_key,
:recaptcha_site_key,
......
......@@ -9,7 +9,7 @@ class ApplicationController < ActionController::Base
include SentryHelper
include WorkhorseHelper
include EnforcesTwoFactorAuthentication
include Peek::Rblineprof::CustomControllerHelpers
include WithPerformanceBar
before_action :authenticate_user_from_private_token!
before_action :authenticate_user_from_rss_token!
......@@ -68,21 +68,6 @@ class ApplicationController < ActionController::Base
end
end
def peek_enabled?
return false unless Gitlab::PerformanceBar.enabled?
return false unless current_user
if RequestStore.active?
if RequestStore.store.key?(:peek_enabled)
RequestStore.store[:peek_enabled]
else
RequestStore.store[:peek_enabled] = cookies[:perf_bar_enabled].present?
end
else
cookies[:perf_bar_enabled].present?
end
end
protected
# This filter handles both private tokens and personal access tokens
......
......@@ -47,7 +47,7 @@ module IssuableCollections
end
def merge_requests_collection
merge_requests_finder.execute.preload(:source_project, :target_project, :author, :assignee, :labels, :milestone, :merge_request_diff, :head_pipeline, target_project: :namespace)
merge_requests_finder.execute.preload(:source_project, :target_project, :author, :assignee, :labels, :milestone, :head_pipeline, target_project: :namespace, merge_request_diff: :merge_request_diff_commits)
end
def issues_finder
......
module WithPerformanceBar
extend ActiveSupport::Concern
included do
include Peek::Rblineprof::CustomControllerHelpers
end
def peek_enabled?
return false unless Gitlab::PerformanceBar.enabled?(current_user)
if RequestStore.active?
RequestStore.fetch(:peek_enabled) { cookies[:perf_bar_enabled].present? }
else
cookies[:perf_bar_enabled].present?
end
end
end
class Dashboard::LabelsController < Dashboard::ApplicationController
def index
labels = LabelsFinder.new(current_user).execute
respond_to do |format|
format.json { render json: LabelSerializer.new.represent_appearance(labels) }
end
end
def labels
finder_params = { project_ids: projects.select(:id) }
labels = LabelsFinder.new(current_user, finder_params).execute
GlobalLabel.build_collection(labels)
end
end
......@@ -3,6 +3,10 @@ module Projects
class SlacksController < Projects::ApplicationController
before_action :handle_oauth_error, only: :slack_auth
before_action :authorize_admin_project!
before_action :slack_integration, only: [:edit, :update]
before_action :service, only: [:destroy, :edit, :update]
layout 'project_settings'
def slack_auth
result = Projects::SlackApplicationInstallService.new(project, current_user, params).execute
......@@ -15,10 +19,22 @@ module Projects
end
def destroy
service = project.gitlab_slack_application_service
service.slack_integration.destroy
slack_integration.destroy
redirect_to_service_page
end
def edit
end
def update
if slack_integration.update(slack_integration_params)
flash[:notice] = 'The project alias was updated successfully'
redirect_to_service_page
else
render :edit
end
end
private
......@@ -36,6 +52,18 @@ module Projects
redirect_to_service_page
end
end
def slack_integration
@slack_integration ||= project.gitlab_slack_application_service.slack_integration
end
def service
@service = project.gitlab_slack_application_service
end
def slack_integration_params
params.require(:slack_integration).permit(:alias)
end
end
end
end
......@@ -337,6 +337,7 @@ class ProjectsController < Projects::ApplicationController
def project_params_ee
%i[
approvals_before_merge
approvals
approver_group_ids
approver_ids
issues_template
......@@ -345,6 +346,7 @@ class ProjectsController < Projects::ApplicationController
mirror
mirror_trigger_builds
mirror_user_id
disable_overriding_approvers_per_merge_request
repository_size_limit
reset_approvals_on_push
service_desk_enabled
......
module CreatedAtFilter
def by_created_at(items)
items = items.created_before(params[:created_before]) if params[:created_before].present?
items = items.created_after(params[:created_after]) if params[:created_after].present?
items
end
end
......@@ -19,6 +19,8 @@
# iids: integer[]
#
class IssuableFinder
include CreatedAtFilter
NONE = '0'.freeze
IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page].freeze
......@@ -36,6 +38,7 @@ class IssuableFinder
def execute
items = init_collection
items = by_scope(items)
items = by_created_at(items)
items = by_state(items)
items = by_group(items)
items = by_search(items)
......@@ -47,7 +50,6 @@ class IssuableFinder
items = by_iids(items)
items = by_milestone(items)
items = by_label(items)
items = by_created_at(items)
# Filtering by project HAS TO be the last because we use the project IDs yielded by the issuable query thus far
items = by_project(items)
......@@ -438,18 +440,6 @@ class IssuableFinder
params[:non_archived].present? ? items.non_archived : items
end
def by_created_at(items)
if params[:created_after].present?
items = items.where(items.klass.arel_table[:created_at].gteq(params[:created_after]))
end
if params[:created_before].present?
items = items.where(items.klass.arel_table[:created_at].lteq(params[:created_before]))
end
items
end
def current_user_related?
params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me'
end
......
......@@ -15,6 +15,8 @@
# skip_ldap: boolean
#
class UsersFinder
include CreatedAtFilter
attr_accessor :current_user, :params
def initialize(current_user, params = {})
......@@ -30,6 +32,7 @@ class UsersFinder
users = by_active(users)
users = by_external_identity(users)
users = by_external(users)
users = by_created_at(users)
users = by_non_ldap(users)
users
......
......@@ -84,7 +84,8 @@ module ButtonHelper
html: true,
placement: placement,
container: 'body',
title: _('Add an SSH key to your profile to pull or push via SSH.')
title: _('Add an SSH key to your profile to pull or push via SSH.'),
primary_url: (geo_primary_ssh_url_to_repo(project) if Gitlab::Geo.secondary?)
}
end
......
......@@ -8,7 +8,7 @@ module EE
end
def geo_primary_ssh_url_to_repo(project)
"#{::Gitlab::Geo.primary_node.clone_url_prefix}#{project.path_with_namespace}"
"#{::Gitlab::Geo.primary_node.clone_url_prefix}#{project.path_with_namespace}.git"
end
def geo_primary_http_url_to_repo(project)
......
......@@ -247,6 +247,7 @@ class ApplicationSetting < ActiveRecord::Base
koding_url: nil,
max_artifacts_size: Settings.artifacts['max_size'],
max_attachment_size: Settings.gitlab['max_attachment_size'],
performance_bar_allowed_group_id: nil,
plantuml_enabled: false,
plantuml_url: nil,
recaptcha_enabled: false,
......@@ -383,6 +384,48 @@ class ApplicationSetting < ActiveRecord::Base
super(levels.map { |level| Gitlab::VisibilityLevel.level_value(level) })
end
def performance_bar_allowed_group_id=(group_full_path)
group_full_path = nil if group_full_path.blank?
if group_full_path.nil?
if group_full_path != performance_bar_allowed_group_id
super(group_full_path)
Gitlab::PerformanceBar.expire_allowed_user_ids_cache
end
return
end
group = Group.find_by_full_path(group_full_path)
if group
if group.id != performance_bar_allowed_group_id
super(group.id)
Gitlab::PerformanceBar.expire_allowed_user_ids_cache
end
else
super(nil)
Gitlab::PerformanceBar.expire_allowed_user_ids_cache
end
end
def performance_bar_allowed_group
Group.find_by_id(performance_bar_allowed_group_id)
end
# Return true if the Performance Bar is enabled for a given group
def performance_bar_enabled
performance_bar_allowed_group_id.present?
end
# - If `enable` is true, we early return since the actual attribute that holds
# the enabling/disabling is `performance_bar_allowed_group_id`
# - If `enable` is false, we set `performance_bar_allowed_group_id` to `nil`
def performance_bar_enabled=(enable)
return if enable
self.performance_bar_allowed_group_id = nil
end
# Choose one of the available repository storage options. Currently all have
# equal weighting.
def pick_repository_storage
......
......@@ -10,5 +10,11 @@ module BlobViewer
def visible_to?(current_user)
can?(current_user, :read_wiki, project)
end
def render_error
return if project.has_external_wiki? || (project.wiki_enabled? && project.wiki.has_home_page?)
:no_wiki
end
end
end
......@@ -222,7 +222,7 @@ module Ci
.reorder(iid: :desc)
merge_requests.find do |merge_request|
merge_request.commits_sha.include?(pipeline.sha)
merge_request.commit_shas.include?(pipeline.sha)
end
end
end
......
module CreatedAtFilterable
extend ActiveSupport::Concern
included do
scope :created_before, ->(date) { where(scoped_table[:created_at].lteq(date)) }
scope :created_after, ->(date) { where(scoped_table[:created_at].gteq(date)) }
def self.scoped_table
arel_table.alias(table_name)
end
end
end
module EachBatch
extend ActiveSupport::Concern
module ClassMethods
# Iterates over the rows in a relation in batches, similar to Rails'
# `in_batches` but in a more efficient way.
#
# Unlike `in_batches` provided by Rails this method does not support a
# custom start/end range, nor does it provide support for the `load:`
# keyword argument.
#
# This method will yield an ActiveRecord::Relation to the supplied block, or
# return an Enumerator if no block is given.
#
# Example:
#
# User.each_batch do |relation|
# relation.update_all(updated_at: Time.now)
# end
#
# The supplied block is also passed an optional batch index:
#
# User.each_batch do |relation, index|
# puts index # => 1, 2, 3, ...
# end
#
# You can also specify an alternative column to use for ordering the rows:
#
# User.each_batch(column: :created_at) do |relation|
# ...
# end
#
# This will produce SQL queries along the lines of:
#
# User Load (0.7ms) SELECT "users"."id" FROM "users" WHERE ("users"."id" >= 41654) ORDER BY "users"."id" ASC LIMIT 1 OFFSET 1000
# (0.7ms) SELECT COUNT(*) FROM "users" WHERE ("users"."id" >= 41654) AND ("users"."id" < 42687)
#
# of - The number of rows to retrieve per batch.
# column - The column to use for ordering the batches.
def each_batch(of: 1000, column: primary_key)
unless column
raise ArgumentError,
'the column: argument must be set to a column name to use for ordering rows'
end
start = except(:select)
.select(column)
.reorder(column => :asc)
.take
return unless start
start_id = start[column]
arel_table = self.arel_table
1.step do |index|
stop = except(:select)
.select(column)
.where(arel_table[column].gteq(start_id))
.reorder(column => :asc)
.offset(of)
.limit(1)
.take
relation = where(arel_table[column].gteq(start_id))
if stop
stop_id = stop[column]
start_id = stop_id
relation = relation.where(arel_table[column].lt(stop_id))
end
# Any ORDER BYs are useless for this relation and can lead to less
# efficient UPDATE queries, hence we get rid of it.
yield relation.except(:order), index
break unless stop
end
end
end
end
......@@ -165,6 +165,10 @@ module EE
repository.fetch_upstream(self.import_url)
end
def can_override_approvers?
!disable_overriding_approvers_per_merge_request?
end
def shared_runners_available?
super && !namespace.shared_runners_minutes_used?
end
......
......@@ -2,7 +2,7 @@ class GlobalLabel
attr_accessor :title, :labels
alias_attribute :name, :title
delegate :color, :description, to: :@first_label
delegate :color, :text_color, :description, to: :@first_label
def for_display
@first_label
......
......@@ -13,6 +13,7 @@ class Issue < ActiveRecord::Base
include FasterCacheKeys
include RelativePositioning
include IgnorableColumn
include CreatedAtFilterable
ignore_column :position
......@@ -60,8 +61,6 @@ class Issue < ActiveRecord::Base
scope :order_weight_desc, -> { reorder('weight IS NOT NULL, weight DESC') }
scope :order_weight_asc, -> { reorder('weight ASC') }
scope :created_after, -> (datetime) { where("created_at >= ?", datetime) }
scope :preload_associations, -> { preload(:labels, project: :namespace) }
after_save :expire_etag_cache
......
......@@ -6,6 +6,7 @@ class MergeRequest < ActiveRecord::Base
include Sortable
include Elastic::MergeRequestsSearch
include IgnorableColumn
include CreatedAtFilterable
ignore_column :position
......@@ -34,7 +35,7 @@ class MergeRequest < ActiveRecord::Base
after_create :ensure_merge_request_diff, unless: :importing?
after_update :reload_diff_if_branch_changed
delegate :commits, :real_size, :commits_sha, :commits_count,
delegate :commits, :real_size, :commit_shas, :commits_count,
to: :merge_request_diff, prefix: nil
delegate :codeclimate_artifact, to: :head_pipeline, prefix: :head, allow_nil: true
......@@ -542,7 +543,7 @@ class MergeRequest < ActiveRecord::Base
def related_notes
# Fetch comments only from last 100 commits
commits_for_notes_limit = 100
commit_ids = commits.last(commits_for_notes_limit).map(&:id)
commit_ids = commit_shas.take(commits_for_notes_limit)
Note.where(
"(project_id = :target_project_id AND noteable_type = 'MergeRequest' AND noteable_id = :mr_id) OR" +
......@@ -865,15 +866,15 @@ class MergeRequest < ActiveRecord::Base
return Ci::Pipeline.none unless source_project
@all_pipelines ||= source_project.pipelines
.where(sha: all_commits_sha, ref: source_branch)
.where(sha: all_commit_shas, ref: source_branch)
.order(id: :desc)
end
# Note that this could also return SHA from now dangling commits
#
def all_commits_sha
def all_commit_shas
if persisted?
merge_request_diffs.flat_map(&:commits_sha).uniq
merge_request_diffs.preload(:merge_request_diff_commits).flat_map(&:commit_shas).uniq
elsif compare_commits
compare_commits.to_a.reverse.map(&:id)
else
......
......@@ -11,6 +11,7 @@ class MergeRequestDiff < ActiveRecord::Base
belongs_to :merge_request
has_many :merge_request_diff_files, -> { order(:merge_request_diff_id, :relative_order) }
has_many :merge_request_diff_commits, -> { order(:merge_request_diff_id, :relative_order) }
serialize :st_commits # rubocop:disable Cop/ActiveRecordSerialize
serialize :st_diffs # rubocop:disable Cop/ActiveRecordSerialize
......@@ -47,14 +48,13 @@ class MergeRequestDiff < ActiveRecord::Base
# Collect information about commits and diff from repository
# and save it to the database as serialized data
def save_git_content
ensure_commits_sha
ensure_commit_shas
save_commits
reload_commits
save_diffs
keep_around_commits
end
def ensure_commits_sha
def ensure_commit_shas
merge_request.fetch_ref
self.start_commit_sha ||= merge_request.target_branch_sha
self.head_commit_sha ||= merge_request.source_branch_sha
......@@ -66,7 +66,7 @@ class MergeRequestDiff < ActiveRecord::Base
# created before version 8.4 that does not store head_commit_sha in separate db field.
def head_commit_sha
if persisted? && super.nil?
last_commit.try(:sha)
last_commit_sha
else
super
end
......@@ -97,16 +97,11 @@ class MergeRequestDiff < ActiveRecord::Base
end
def commits
@commits ||= load_commits(st_commits)
@commits ||= load_commits
end
def reload_commits
@commits = nil
commits
end
def last_commit
commits.first
def last_commit_sha
commit_shas.first
end
def first_commit
......@@ -131,8 +126,12 @@ class MergeRequestDiff < ActiveRecord::Base
project.commit(head_commit_sha)
end
def commits_sha
def commit_shas
if st_commits.present?
st_commits.map { |commit| commit[:id] }
else
merge_request_diff_commits.map(&:sha)
end
end
def diff_refs=(new_diff_refs)
......@@ -207,7 +206,11 @@ class MergeRequestDiff < ActiveRecord::Base
end
def commits_count
st_commits.count
if st_commits.present?
st_commits.size
else
merge_request_diff_commits.size
end
end
def utf8_st_diffs
......@@ -231,29 +234,6 @@ class MergeRequestDiff < ActiveRecord::Base
raw.any? { |element| VALID_CLASSES.include?(element.class) }
end
def dump_commits(commits)
commits.map(&:to_hash)
end
def load_commits(array)
array.map { |hash| Commit.new(Gitlab::Git::Commit.new(hash), merge_request.source_project) }
end
# Load all commits related to current merge request diff from repo
# and save it as array of hashes in st_commits db field
def save_commits
new_attributes = {}
commits = compare.commits
if commits.present?
commits = Commit.decorate(commits, merge_request.source_project).reverse
new_attributes[:st_commits] = dump_commits(commits)
end
update_columns_serialized(new_attributes)
end
def create_merge_request_diff_files(diffs)
rows = diffs.map.with_index do |diff, index|
diff.to_hash.merge(
......@@ -294,12 +274,18 @@ class MergeRequestDiff < ActiveRecord::Base
end
end
# Load diffs between branches related to current merge request diff from repo
# and save it as array of hashes in st_diffs db field
def load_commits
commits = st_commits.presence || merge_request_diff_commits
commits.map do |commit|
Commit.new(Gitlab::Git::Commit.new(commit.to_hash), merge_request.source_project)
end
end
def save_diffs
new_attributes = {}
if commits.size.zero?
if compare.commits.size.zero?
new_attributes[:state] = :empty
else
diff_collection = compare.diffs(Commit.max_diff_options)
......@@ -319,7 +305,13 @@ class MergeRequestDiff < ActiveRecord::Base
new_attributes[:state] = :overflow if diff_collection.overflow?
end
update_columns_serialized(new_attributes)
update(new_attributes)
end
def save_commits
MergeRequestDiffCommit.create_bulk(self.id, compare.commits.reverse)
merge_request_diff_commits.reload
end
def repository
......@@ -332,29 +324,6 @@ class MergeRequestDiff < ActiveRecord::Base
project.merge_base_commit(head_commit_sha, start_commit_sha).try(:sha)
end
#
# #save or #update_attributes providing changes on serialized attributes do a lot of
# serialization and deserialization calls resulting in bad performance.
# Using #update_columns solves the problem with just one YAML.dump per serialized attribute that we provide.
# As a tradeoff we need to reload the current instance to properly manage time objects on those serialized
# attributes. So to keep the same behaviour as the attribute assignment we reload the instance.
# The difference is in the usage of
# #write_attribute= (#update_attributes) and #raw_write_attribute= (#update_columns)
#
# Ex:
#
# new_attributes[:st_commits].first.slice(:committed_date)
# => {:committed_date=>2014-02-27 11:01:38 +0200}
# YAML.load(YAML.dump(new_attributes[:st_commits].first.slice(:committed_date)))
# => {:committed_date=>2014-02-27 10:01:38 +0100}
#
def update_columns_serialized(new_attributes)
return unless new_attributes.any?
update_columns(new_attributes.merge(updated_at: current_time_from_proper_timezone))
reload
end
def keep_around_commits
[repository, merge_request.source_project.repository].each do |repo|
repo.keep_around(start_commit_sha)
......
class MergeRequestDiffCommit < ActiveRecord::Base
include ShaAttribute
belongs_to :merge_request_diff
sha_attribute :sha
alias_attribute :id, :sha
def self.create_bulk(merge_request_diff_id, commits)
sha_attribute = Gitlab::Database::ShaAttribute.new
rows = commits.map.with_index do |commit, index|
# See #parent_ids.
commit_hash = commit.to_hash.except(:parent_ids)
sha = commit_hash.delete(:id)
commit_hash.merge(
merge_request_diff_id: merge_request_diff_id,
relative_order: index,
sha: sha_attribute.type_cast_for_database(sha)
)
end
Gitlab::Database.bulk_insert(self.table_name, rows)
end
def to_hash
Gitlab::Git::Commit::SERIALIZE_KEYS.each_with_object({}) do |key, hash|
hash[key] = public_send(key)
end
end
# We don't save these, because they would need a table or a serialised
# field. They aren't used anywhere, so just pretend the commit has no parents.
def parent_ids
[]
end
end
......@@ -70,6 +70,10 @@ class ProjectWiki
!!repository.exists?
end
def has_home_page?
!!find_page('home')
end
# Returns an Array of Gitlab WikiPage instances or an
# empty Array if this Wiki has no pages.
def pages
......
......@@ -4,7 +4,8 @@ class SlackIntegration < ActiveRecord::Base
validates :team_id, presence: true
validates :team_name, presence: true
validates :alias, presence: true,
uniqueness: { scope: :team_id, message: 'This alias has already been taken' }
uniqueness: { scope: :team_id, message: 'This alias has already been taken' },
length: 2..80
validates :user_id, presence: true
validates :service, presence: true
......
......@@ -12,6 +12,7 @@ class User < ActiveRecord::Base
include TokenAuthenticatable
include IgnorableColumn
include FeatureGate
include CreatedAtFilterable
prepend EE::GeoAwareAvatar
prepend EE::User
......
class LabelEntity < Grape::Entity
expose :id
expose :id, if: ->(label, _) { !label.is_a?(GlobalLabel) }
expose :title
expose :color
expose :description
......
......@@ -28,7 +28,8 @@ module Geo
if payload.is_a?(Hash)
payload['message']
else
payload
# The return value can be a giant blob of HTML; ignore it
''
end
Array([message, details].compact.join("\n"))
......
......@@ -9,6 +9,12 @@ module MergeRequests
params[:target_project_id] ||= source_project.id
unless @project.can_override_approvers?
params.delete(:approvals_before_merge)
params.delete(:approver_ids)
params.delete(:approver_group_ids)
end
merge_request = MergeRequest.new
merge_request.source_project = source_project
merge_request.source_branch = params[:source_branch]
......
......@@ -69,7 +69,7 @@ module MergeRequests
if merge_request.source_branch == @branch_name || force_push?
merge_request.reload_diff(current_user)
else
mr_commit_ids = merge_request.commits_sha
mr_commit_ids = merge_request.commit_shas
push_commit_ids = @commits.map(&:id)
matches = mr_commit_ids & push_commit_ids
merge_request.reload_diff(current_user) if matches.any?
......@@ -145,7 +145,7 @@ module MergeRequests
return unless @commits.present?
merge_requests_for_source_branch.each do |merge_request|
mr_commit_ids = Set.new(merge_request.commits_sha)
mr_commit_ids = Set.new(merge_request.commit_shas)
new_commits, existing_commits = @commits.partition do |commit|
mr_commit_ids.include?(commit.id)
......@@ -161,7 +161,7 @@ module MergeRequests
return unless @commits.present?
merge_requests_for_source_branch.each do |merge_request|
commit_shas = merge_request.commits_sha
commit_shas = merge_request.commit_shas
wip_commit = @commits.detect do |commit|
commit.work_in_progress? && commit_shas.include?(commit.sha)
......
......@@ -17,6 +17,12 @@ module MergeRequests
merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
end
unless project.can_override_approvers?
params.delete(:approvals_before_merge)
params.delete(:approver_ids)
params.delete(:approver_group_ids)
end
old_approvers = merge_request.overall_approvers.to_a
handle_wip_event(merge_request)
......
......@@ -360,6 +360,22 @@
%strong.cred WARNING:
Environment variable `prometheus_multiproc_dir` does not exist or is not pointing to a valid directory.
%fieldset
%legend Profiling - Performance Bar
%p
Enable the Performance Bar for a given group.
= link_to icon('question-circle'), help_page_path('administration/monitoring/performance/performance_bar')
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :performance_bar_enabled do
= f.check_box :performance_bar_enabled
Enable the Performance Bar
.form-group
= f.label :performance_bar_allowed_group_id, 'Allowed group', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :performance_bar_allowed_group_id, class: 'form-control', placeholder: 'my-org/my-group', value: @application_setting.performance_bar_allowed_group&.full_path
%fieldset
%legend Background Jobs
%p
......
......@@ -122,8 +122,7 @@
= select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2"
%hr
= button_tag 'Add users to group', class: "btn btn-create"
= render 'shared/members/requests', membership_source: @group, requesters: @requesters
= render 'shared/members/requests', membership_source: @group, requesters: @requesters, force_mobile_view: true
.panel.panel-default
.panel-heading
......@@ -132,7 +131,7 @@
%span.badge= @group.members.size
.pull-right
= link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@group, :members]), class: "btn btn-xs"
%ul.well-list.group-users-list.content-list
%ul.well-list.group-users-list.content-list.members-list
= render partial: 'shared/members/member', collection: @members, as: :member, locals: { show_controls: false }
.panel-footer
= paginate @members, param_name: 'members_page', theme: 'gitlab'
......@@ -162,12 +162,12 @@
.pull-right
= link_to admin_group_path(@group), class: 'btn btn-xs' do
= icon('pencil-square-o', text: 'Manage access')
%ul.well-list.content-list
%ul.well-list.content-list.members-list
= render partial: 'shared/members/member', collection: @group_members, as: :member, locals: { show_controls: false }
.panel-footer
= paginate @group_members, param_name: 'group_members_page', theme: 'gitlab'
= render 'shared/members/requests', membership_source: @project, requesters: @requesters
= render 'shared/members/requests', membership_source: @project, requesters: @requesters, force_mobile_view: true
.panel.panel-default
.panel-heading
......@@ -176,7 +176,7 @@
%span.badge= @project.users.size
.pull-right
= link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@project, :members]), class: "btn btn-xs"
%ul.well-list.project_members.content-list
%ul.well-list.project_members.content-list.members-list
= render partial: 'shared/members/member', collection: @project_members, as: :member, locals: { show_controls: false }
.panel-footer
= paginate @project_members, param_name: 'project_members_page', theme: 'gitlab'
......@@ -29,6 +29,6 @@
Members with access to
%strong= @group.name
%span.badge= @members.total_count
%ul.content-list
%ul.content-list.members-list
= render partial: 'shared/members/member', collection: @members, as: :member
= paginate @members, theme: 'gitlab'
= icon('info-circle fw')
= succeed '.' do
To learn more about this project, read
= link_to "the wiki", project_wikis_path(viewer.project)
= link_to "the wiki", get_project_wiki_path(viewer.project)
- return unless project.feature_available?(:merge_request_approvers)
- can_override_approvers = project.can_override_approvers?
.form-group.reset-approvals-on-push
.checkbox
= label_tag :require_approvals do
= check_box_tag :require_approvals, nil, project.approvals_before_merge.nonzero?, class: 'js-require-approvals-toggle'
%strong Activate merge request approvals
%strong Merge request approvals
= link_to icon('question-circle'), help_page_path("user/project/merge_requests/merge_request_approvals"), target: '_blank'
.descr Merge request approvals allow you to set the number of necessary approvals and predefine a list of approvers that you will need to approve every merge request in a project.
.nested-settings{ class: project.approvals_before_merge.nonzero? ? '' : 'hidden' }
.form-group
......@@ -19,7 +19,7 @@
%button.btn.btn-success.js-add-approvers{ type: 'button', title: 'Add approvers(s)' }
Add
.help-block
Add an approver or group suggestion for each merge request
Add users or groups who are allowed to approve every merge request
.panel.panel-default.prepend-top-10.js-current-approvers
.panel-heading
......@@ -61,11 +61,18 @@
Approvals required
= form.number_field :approvals_before_merge, class: "form-control", min: 0
.help-block
Set number of approvers required before open merge requests can be merged
.form-group
.checkbox
= form.label :disable_overriding_approvers_per_merge_request do
= form.check_box(:disable_overriding_approvers_per_merge_request, { checked: can_override_approvers }, false, true)
%strong Can override approvers and approvals required per merge request
= link_to icon('question-circle'), help_page_path("user/project/merge_requests/merge_request_approvals", anchor: 'can-override-approvers-and-approvals-required-per-merge-request'), target: '_blank'
.form-group.reset-approvals-on-push
.checkbox
= form.label :reset_approvals_on_push do
= form.check_box :reset_approvals_on_push
%strong Reset approvals on push
.descr Approvals are reset when new data is pushed to the merge request
%strong Remove all approvals in a merge request when new commits are pushed to its source branch
.panel.panel-default
.panel-heading
Group members with access to
%strong= @group.name
%span.badge= members.size
- if can?(current_user, :admin_group_member, @group)
.controls
= link_to 'Manage group members',
group_group_members_path(@group),
class: 'btn'
%ul.content-list
= render partial: 'shared/members/member',
collection: members.limit(20),
as: :member,
locals: { show_controls: false }
- if members.size > 20
%li
and #{members.count - 20} more. For full list visit #{link_to 'group members page', group_group_members_path(@group)}
.row.prepend-top-default
.col-lg-4.settings-sidebar
%h4.prepend-top-0
.col-lg-12
%h4
Project members
- if can?(current_user, :admin_project_member, @project)
%p
......@@ -13,7 +13,6 @@
%i Masters
or
%i Owners
.col-lg-8
.light
- if can?(current_user, :admin_project_member, @project) && !membership_locked?
%ul.nav-links.project-member-tabs{ role: 'tablist' }
......
- @project_group_links.each do |group_links|
- shared_group = group_links.group
- shared_group_members = shared_group.members
- shared_group_users_count = shared_group_members.size
.panel.panel-default
.panel-heading
Shared with
%strong= shared_group.name
group, members with
%strong= group_links.human_access
role (#{shared_group_users_count})
- if can?(current_user, :admin_group, shared_group)
.panel-head-actions
= link_to group_group_members_path(shared_group), class: 'btn btn-sm' do
%i.fa.fa-pencil-square-o
Edit group members
%ul.content-list
= render partial: 'shared/members/member',
collection: shared_group_members.order(access_level: :desc).limit(20),
as: :member,
locals: { show_controls: false, show_roles: false }
- if shared_group_users_count > 20
%li
and #{shared_group_users_count - 20} more. For full list visit #{link_to 'group members page', group_group_members_path(shared_group)}
......@@ -11,5 +11,5 @@
%button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
= icon("search")
= render 'shared/members/sort_dropdown'
%ul.content-list
%ul.content-list.members-list
= render partial: 'shared/members/member', collection: members, as: :member
......@@ -5,8 +5,8 @@
%colgroup
%col
%col
%col.hidden-xs
%col{ width: "120" }
%col
%col
%thead
%tr
%th Team name
......@@ -18,9 +18,11 @@
= slack_integration.team_name
%td
= slack_integration.alias
%td.light
%td
= time_ago_in_words slack_integration.created_at
ago
%td.light
%td
.controls
- project = @service.project
= link_to 'Remove', namespace_project_settings_slack_path(project.namespace, project), method: :delete, class: 'btn btn-danger', data: { confirm: 'Are you sure?' }
= link_to 'Edit', edit_project_settings_slack_path(project), class: 'btn btn-sm'
= link_to 'Remove', project_settings_slack_path(project), method: :delete, class: 'btn btn-danger btn-sm', data: { confirm: 'Are you sure?' }
- page_title 'Edit Slack integration'
= render "projects/settings/head"
.row.prepend-top-default.append-bottom-default
.col-lg-3
%h4.prepend-top-0
Edit project alias
%p You can use this alias in your Slack commands
.col-lg-9
= form_errors(@slack_integration)
= form_for(@slack_integration, url: project_settings_slack_path(@project), method: :put, html: { class: 'gl-show-field-errors form-horizontal js-integration-settings-form'}) do |form|
.form-group
= form.label :alias, 'Enter your alias', class: 'control-label'
.col-sm-10
= form.text_field :alias, class: 'form-control', placeholder: @slack_integration.alias, required: true
.footer-block.row-content-block
%button.btn.btn-save{ type: 'submit' }
= icon('spinner spin', class: 'hidden js-btn-spinner')
%span.js-btn-label
Save changes
&nbsp;
= link_to 'Cancel', edit_project_service_path(@project, @service), class: 'btn btn-cancel'
......@@ -21,7 +21,8 @@
remote:
= clipboard_button(target: 'pre#geo-info-2')
%pre#geo-info-2.dark
git remote set-url --push origin #{geo_primary_default_url_to_repo(project)}
git remote set-url --push origin &lt;clone url for primary repository&gt;
%p
%strong= 'Done.'
......
......@@ -4,22 +4,14 @@
- return unless issuable.is_a?(MergeRequest)
- return unless issuable.requires_approve?
.form-group
= form.label :approvals_before_merge, class: 'control-label' do
Approvals required
.col-sm-10
= form.number_field :approvals_before_merge, class: 'form-control', value: issuable.approvals_required
.help-block
Number of users who need to approve this merge request before it can be accepted.
If this isn't greater than the project default (#{pluralize(issuable.target_project.approvals_before_merge, 'user')}),
then it will be ignored and the project default will be used.
- can_override_approvers = issuable.target_project.can_override_approvers?
.form-group
= form.label :approver_ids, class: 'control-label' do
Approvers
.col-sm-10
- author = issuable.author || current_user
- skip_users = issuable.all_approvers_including_groups + [author]
= users_select_tag("merge_request[approver_ids]", multiple: true, class: 'input-large', email_user: true, skip_users: skip_users, project: issuable.target_project)
- if can_override_approvers
= users_select_tag("merge_request[approver_ids]", multiple: true, class: 'input-large', email_user: true, skip_users: issuable.all_approvers_including_groups, project: issuable.target_project)
.help-block
This merge request must be approved by these users.
You can override the project settings by setting your own list of approvers.
......@@ -42,6 +34,7 @@
- issuable.overall_approvers.each do |approver|
%li{ id: dom_id(approver.user), class: item_classes + ['approver'] }
= link_to approver.user.name, approver.user
- if can_override_approvers
.pull-right
- if unsaved_approvers
= link_to "#", data: { confirm: "Are you sure you want to remove approver #{approver.user.name}"}, class: "btn-xs btn btn-remove", title: 'Remove approver' do
......@@ -55,6 +48,7 @@
%li{ id: dom_id(approver_group.group), class: item_classes + ['approver-group'] }
Group:
= link_to approver_group.group.name, approver_group.group
- if can_override_approvers
.pull-right
- if unsaved_approvers
= link_to "#", data: { confirm: "Are you sure you want to remove group #{approver_group.group.name}"}, class: "btn-xs btn btn-remove", title: 'Remove group' do
......@@ -64,6 +58,14 @@
= link_to project_merge_request_approver_group_path(@project, issuable, approver_group), data: { confirm: "Are you sure you want to remove group #{approver_group.group.name}"}, method: :delete, class: "btn-xs btn btn-remove", title: 'Remove group' do
= icon("sign-out")
Remove
.col-sm-12
.form-group
= form.label :approvals_before_merge, class: 'label-light' do
Approvals required
= form.number_field :approvals_before_merge, class: 'form-control', value: issuable.approvals_required, readonly: !can_override_approvers
- if can_override_approvers
.help-block.suggested-approvers
- if @suggested_approvers.any?
Suggested approvers:
......
- show_roles = local_assigns.fetch(:show_roles, true)
- show_controls = local_assigns.fetch(:show_controls, true)
- force_mobile_view = local_assigns.fetch(:force_mobile_view, false)
- user = local_assigns.fetch(:user, member.user)
- source = member.source
- can_admin_member = can?(current_user, action_member_permission(:update, member), member)
......@@ -11,8 +12,8 @@
%span.list-item-name
- if user
= image_tag avatar_icon(user, 40), class: "avatar s40", alt: ''
%strong
= link_to user.name, user_path(user)
.user-info
= link_to user.name, user_path(user), class: 'member'
%span.cgray= user.to_reference
- if user == current_user
......@@ -26,7 +27,7 @@
&middot;
= link_to source.full_name, source, class: "member-group-link"
.hidden-xs.cgray
.cgray
- if member.request?
Requested
= time_ago_with_tooltip(member.requested_at)
......@@ -39,7 +40,8 @@
- else
= image_tag avatar_icon(member.invite_email, 40), class: "avatar s40", alt: ''
%strong= member.invite_email
.user-info
.member= member.invite_email
.cgray
Invited
- if member.created_by
......@@ -51,6 +53,12 @@
.controls.member-controls
= render 'shared/members/ee/ldap_tag', can_override: can_override_member, visible: false
- if show_controls && member.source == current_resource
- if member.invite? && can?(current_user, action_member_permission(:admin, member), member.source)
= link_to icon('paper-plane'), polymorphic_path([:resend_invite, member]),
method: :post,
class: 'btn btn-default prepend-left-10 hidden-xs',
title: 'Resend invite'
- if user != current_user && (can_admin_member || can_override_member)
= form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f|
= f.hidden_field :access_level
......@@ -80,13 +88,17 @@
- if member.invite? && can?(current_user, action_member_permission(:admin, member), member.source)
= link_to 'Resend invite', polymorphic_path([:resend_invite, member]),
method: :post,
class: 'btn btn-default prepend-left-10'
class: 'btn btn-default prepend-left-10 visible-xs-block'
- elsif member.request? && can_admin_member
= link_to icon('check inverse'), polymorphic_path([:approve_access_request, member]),
= link_to polymorphic_path([:approve_access_request, member]),
method: :post,
class: 'btn btn-success prepend-left-10',
title: 'Grant access'
title: 'Grant access' do
%span{ class: ('visible-xs-block' unless force_mobile_view) }
Grant access
- unless force_mobile_view
= icon('check inverse', class: 'hidden-xs')
- if can?(current_user, action_member_permission(:destroy, member), member)
- if current_user == user
......@@ -101,8 +113,9 @@
data: { confirm: remove_member_message(member) },
class: 'btn btn-remove prepend-left-10',
title: remove_member_title(member) do
%span.visible-xs-block
%span{ class: ('visible-xs-block' unless force_mobile_view) }
Delete
- unless force_mobile_view
= icon('trash', class: 'hidden-xs')
= render 'shared/members/ee/override_member_buttons', group: @group, member: member, user: user, action: :edit, can_override: can_override_member
- else
......
- force_mobile_view = local_assigns.fetch(:force_mobile_view, false)
- if requesters.any?
.panel.panel-default.prepend-top-default
.panel.panel-default.prepend-top-default{ class: ('panel-mobile' if force_mobile_view ) }
.panel-heading
Users requesting access to
%strong= membership_source.name
%span.badge= requesters.size
%ul.content-list
= render partial: 'shared/members/member', collection: requesters, as: :member
%ul.content-list.members-list
= render partial: 'shared/members/member', collection: requesters, as: :member, locals: { force_mobile_view: force_mobile_view }
---
title: 'Geo: Fix clone instructions in a secondary node for SSH protocol'
merge_request: 2319
author:
---
title: add toggle for overriding approvers per MR
merge_request:
author:
---
title: Improve members view on mobile
merge_request: 12619
author:
---
title: Improve the performance of the project list API
merge_request: 12679
author:
---
title: Allow to enable the performance bar per user or Feature group
merge_request: 12362
author:
---
title: Fix dashboard labels dropdown
merge_request: 12708
author:
---
title: Fixed the chart legend not being set correctly
merge_request: 12628
author:
---
title: Remove two columned layout from project member settings
merge_request:
author:
---
title: Don't show auxiliary blob viewer for README when there is no wiki
merge_request:
author:
---
title: Add creation time filters to user search API for admins
merge_request: 12682
author:
---
title: Upgrade GitLab Workhorse to v2.3.0
merge_request: 12676
author:
......@@ -29,7 +29,8 @@ module Gitlab
#{config.root}/app/models/project_services
#{config.root}/app/workers/concerns
#{config.root}/app/services/concerns
#{config.root}/app/uploaders/concerns))
#{config.root}/app/uploaders/concerns
#{config.root}/app/finders/concerns))
config.generators.templates.push("#{config.root}/generator_templates")
......@@ -175,8 +176,9 @@ module Gitlab
config.after_initialize do
Rails.application.reload_routes!
named_routes_set = Gitlab::Application.routes.named_routes
project_url_helpers = Module.new do
Gitlab::Application.routes.named_routes.helper_names.each do |name|
named_routes_set.helper_names.each do |name|
next unless name.include?('namespace_project')
define_method(name.sub('namespace_project', 'project')) do |project, *args|
......@@ -185,6 +187,9 @@ module Gitlab
end
end
named_routes_set.url_helpers_module.include project_url_helpers
named_routes_set.url_helpers_module.extend project_url_helpers
Gitlab::Routing.url_helpers.include project_url_helpers
Gitlab::Routing.url_helpers.extend project_url_helpers
......
......@@ -762,7 +762,10 @@ test:
client_id: 'YOUR_AUTH0_CLIENT_ID',
client_secret: 'YOUR_AUTH0_CLIENT_SECRET',
namespace: 'YOUR_AUTH0_DOMAIN' } }
- { name: 'authentiq',
app_id: 'YOUR_CLIENT_ID',
app_secret: 'YOUR_CLIENT_SECRET',
args: { scope: 'aq:name email~rs address aq:push' } }
ldap:
enabled: false
servers:
......
resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do
collection do
resources :artifacts, only: [], controller: 'build_artifacts' do
collection do
get :latest_succeeded,
path: '*ref_name_and_path',
format: false
end
end
end
member do
get :raw
end
resource :artifacts, only: [], controller: 'build_artifacts' do
get :download
get :browse, path: 'browse(/*path)', format: false
get :file, path: 'file/*path', format: false
get :raw, path: 'raw/*path', format: false
end
end
require 'constraints/project_url_constrainer'
require 'gitlab/routes/legacy_builds'
resources :projects, only: [:index, :new, :create]
......@@ -288,7 +287,7 @@ constraints(ProjectUrlConstrainer.new) do
end
end
Gitlab::Routes::LegacyBuilds.new(self).draw
draw :legacy_builds
resources :hooks, only: [:index, :create, :edit, :update, :destroy], constraints: { id: /\d+/ } do
member do
......@@ -439,7 +438,7 @@ constraints(ProjectUrlConstrainer.new) do
resource :ci_cd, only: [:show], controller: 'ci_cd'
resource :integrations, only: [:show]
resource :slack, only: [:destroy] do
resource :slack, only: [:destroy, :edit, :update] do
get :slack_auth
end
......
class AddDisableOverridingApproversPerMergeRequestToProject < ActiveRecord::Migration
DOWNTIME = false
def change
add_column :projects, :disable_overriding_approvers_per_merge_request, :boolean
end
end
class CreateMergeRequestDiffCommits < ActiveRecord::Migration
DOWNTIME = false
def change
create_table :merge_request_diff_commits, id: false do |t|
t.datetime_with_timezone :authored_date
t.datetime_with_timezone :committed_date
t.belongs_to :merge_request_diff, null: false, foreign_key: { on_delete: :cascade }
t.integer :relative_order, null: false
t.binary :sha, null: false, limit: 20
t.text :author_name
t.text :author_email
t.text :committer_name
t.text :committer_email
t.text :message
t.index [:merge_request_diff_id, :relative_order], name: 'index_merge_request_diff_commits_on_mr_diff_id_and_order', unique: true
end
end
end
class AddPerformanceBarAllowedGroupIdToApplicationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :application_settings, :performance_bar_allowed_group_id, :integer
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170706121518) do
ActiveRecord::Schema.define(version: 20170706151212) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -148,6 +148,7 @@ ActiveRecord::Schema.define(version: 20170706121518) do
t.string "slack_app_id"
t.string "slack_app_secret"
t.string "slack_app_verification_token"
t.integer "performance_bar_allowed_group_id"
end
create_table "approvals", force: :cascade do |t|
......@@ -884,6 +885,21 @@ ActiveRecord::Schema.define(version: 20170706121518) do
add_index "members", ["source_id", "source_type"], name: "index_members_on_source_id_and_source_type", using: :btree
add_index "members", ["user_id"], name: "index_members_on_user_id", using: :btree
create_table "merge_request_diff_commits", id: false, force: :cascade do |t|
t.datetime "authored_date"
t.datetime "committed_date"
t.integer "merge_request_diff_id", null: false
t.integer "relative_order", null: false
t.binary "sha", null: false
t.text "author_name"
t.text "author_email"
t.text "committer_name"
t.text "committer_email"
t.text "message"
end
add_index "merge_request_diff_commits", ["merge_request_diff_id", "relative_order"], name: "index_merge_request_diff_commits_on_mr_diff_id_and_order", unique: true, using: :btree
create_table "merge_request_diff_files", id: false, force: :cascade do |t|
t.integer "merge_request_diff_id", null: false
t.integer "relative_order", null: false
......@@ -1342,6 +1358,7 @@ ActiveRecord::Schema.define(version: 20170706121518) do
t.integer "cached_markdown_version"
t.datetime "last_repository_updated_at"
t.string "ci_config_path"
t.boolean "disable_overriding_approvers_per_merge_request"
end
add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree
......@@ -1895,6 +1912,7 @@ ActiveRecord::Schema.define(version: 20170706121518) do
add_foreign_key "labels", "projects", name: "fk_7de4989a69", on_delete: :cascade
add_foreign_key "lists", "boards", name: "fk_0d3f677137", on_delete: :cascade
add_foreign_key "lists", "labels", name: "fk_7a5553d60f", on_delete: :cascade
add_foreign_key "merge_request_diff_commits", "merge_request_diffs", on_delete: :cascade
add_foreign_key "merge_request_diff_files", "merge_request_diffs", on_delete: :cascade
add_foreign_key "merge_request_diffs", "merge_requests", name: "fk_8483f3258f", on_delete: :cascade
add_foreign_key "merge_request_metrics", "ci_pipelines", column: "pipeline_id", on_delete: :cascade
......
......@@ -193,6 +193,7 @@ have access to GitLab administration tools and settings.
- [Operations](administration/operations.md): Keeping GitLab up and running.
- [Polling](administration/polling.md): Configure how often the GitLab UI polls for updates.
- [Request Profiling](administration/monitoring/performance/request_profiling.md): Get a detailed profile on slow requests.
- [Performance Bar](administration/monitoring/performance/performance_bar.md): Get performance information for the current page.
### Customization
......
# Performance Bar
A Performance Bar can be displayed, to dig into the performance of a page. When
activated, it looks as follows:
![Performance Bar](img/performance_bar.png)
It allows you to:
- see the current host serving the page
- see the timing of the page (backend, frontend)
- the number of DB queries, the time it took, and the detail of these queries
![SQL profiling using the Performance Bar](img/performance_bar_sql_queries.png)
- the number of calls to Redis, and the time it took
- the number of background jobs created by Sidekiq, and the time it took
- the number of Ruby GC calls, and the time it took
- profile the code used to generate the page, line by line
![Line profiling using the Performance Bar](img/performance_bar_line_profiling.png)
## Enable the Performance Bar via the Admin panel
GitLab Performance Bar is disabled by default. To enable it for a given group,
navigate to the Admin area in **Settings > Profiling - Performance Bar**
(`/admin/application_settings`).
The only required setting you need to set is the full path of the group that
will be allowed to display the Performance Bar.
Make sure _Enable the Performance Bar_ is checked and hit
**Save** to save the changes.
---
![GitLab Performance Bar Admin Settings](img/performance_bar_configuration_settings.png)
---
......@@ -17,6 +17,7 @@ following locations:
- [Deploy Keys](deploy_keys.md)
- [Environments](environments.md)
- [Events](events.md)
- [Feature flags](features.md)
- [Gitignores templates](templates/gitignores.md)
- [GitLab CI Config templates](templates/gitlab_ci_ymls.md)
- [Groups](groups.md)
......
# Features API
# Features flags API
All methods require administrator authorization.
......@@ -61,7 +61,8 @@ POST /features/:name
| `feature_group` | string | no | A Feature group name |
| `user` | string | no | A GitLab username |
Note that `feature_group` and `user` are mutually exclusive.
Note that you can enable or disable a feature for both a `feature_group` and a
`user` with a single API call.
```bash
curl --data "value=30" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/features/new_library
......
......@@ -148,6 +148,12 @@ GET /users?extern_uid=1234567&provider=github
You can search for users who are external with: `/users?external=true`
You can search users by creation date time range with:
```
GET /users?created_before=2001-01-02T00:00:00.060Z&created_after=1999-01-02T00:00:00.060
```
## Single user
Get a single user.
......
......@@ -56,6 +56,7 @@
- [Single Table Inheritance](single_table_inheritance.md)
- [Background Migrations](background_migrations.md)
- [Storing SHA1 Hashes As Binary](sha1_as_binary.md)
- [Iterating Tables In Batches](iterating_tables_in_batches.md)
## i18n
......
......@@ -3,5 +3,19 @@
Starting from GitLab 9.3 we support feature flags via
[Flipper](https://github.com/jnunemaker/flipper/). You should use the `Feature`
class (defined in `lib/feature.rb`) in your code to get, set and list feature
flags. During runtime you can set the values for the gates via the
[admin API](../api/features.md).
flags.
During runtime you can set the values for the gates via the
[features API](../api/features.md) (accessible to admins only).
## Feature groups
Starting from GitLab 9.4 we support feature groups via
[Flipper groups](https://github.com/jnunemaker/flipper/blob/v0.10.2/docs/Gates.md#2-group).
Feature groups must be defined statically in `lib/feature.rb` (in the
`.register_feature_groups` method), but their implementation can obviously be
dynamic (querying the DB etc.).
Once defined in `lib/feature.rb`, you will be able to activate a
feature for a given feature group via the [`feature_group` param of the features API](../api/features.md#set-or-create-a-feature)
# Iterating Tables In Batches
Rails provides a method called `in_batches` that can be used to iterate over
rows in batches. For example:
```ruby
User.in_batches(of: 10) do |relation|
relation.update_all(updated_at: Time.now)
end
```
Unfortunately this method is implemented in a way that is not very efficient,
both query and memory usage wise.
To work around this you can include the `EachBatch` module into your models,
then use the `each_batch` class method. For example:
```ruby
class User < ActiveRecord::Base
include EachBatch
end
User.each_batch(of: 10) do |relation|
relation.update_all(updated_at: Time.now)
end
```
This will end up producing queries such as:
```
User Load (0.7ms) SELECT "users"."id" FROM "users" WHERE ("users"."id" >= 41654) ORDER BY "users"."id" ASC LIMIT 1 OFFSET 1000
(0.7ms) SELECT COUNT(*) FROM "users" WHERE ("users"."id" >= 41654) AND ("users"."id" < 42687)
```
The API of this method is similar to `in_batches`, though it doesn't support
all of the arguments that `in_batches` supports. You should always use
`each_batch` _unless_ you have a specific need for `in_batches`.
......@@ -2,7 +2,7 @@
## Repository size limit
> [Introduced][ee-740] in GitLab Enterprise Edition 8.12.
> [Introduced][ee-740] in [GitLab Enterprise Edition 8.12][ee-8.12].
Repositories within your GitLab instance can grow quickly, especially if you are
using LFS. Their size can grow exponentially and eat up your storage device quite
......@@ -12,6 +12,18 @@ In order to avoid this from happening, you can set a hard limit for your
repositories' size. This limit can be set globally, per group, or per project,
with per project limits taking the highest priority.
There are numerous cases where you'll need to set up a limit for repository size.
For instance, consider the following workflow:
1. Your team develops apps which demand large files to be stored in
the application repository
1. Although you have enabled [Git LFS](../../../workflow/lfs/manage_large_binaries_with_git_lfs.html#git-lfs)
to your project, your storage has grown significantly
1. Before you blow your storage limit up, you set up a limit of 10 GB
per repository
### How it works
Only a GitLab administrator can set those limits. Setting the limit to `0` means
there are no restrictions.
......@@ -28,5 +40,9 @@ allowed repository size.
For more manually purging the files, read the docs on
[reducing the repository size using Git][repo-size].
> **Note:**
> For GitLab.com, the repository size limit is 10 GB.
[ee-740]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/740
[repo-size]: ../../project/repository/reducing_the_repo_size_using_git.md
[ee-8.12]: https://about.gitlab.com/2016/09/22/gitlab-8-12-released/#limit-project-size-ee
......@@ -42,6 +42,14 @@ This sets the amount of approvals required before being able to merge a merge re
The number of approvers can be higher than the required approvals.
### Can override approvers and approvals required per merge request
> Introduced in GitLab Enterprise Edition 9.4.
When this setting is enabled, the approvers for a project can be overridden for
a merge request. When editing a merge request you can add or remove approvers,
and increase the number of required approvers.
### Reset approvals on push
With this setting turned on, approvals are reset when a new push
......
......@@ -14,14 +14,12 @@ module API
end
end
def gate_target(params)
if params[:feature_group]
Feature.group(params[:feature_group])
elsif params[:user]
User.find_by_username(params[:user])
else
gate_value(params)
end
def gate_targets(params)
targets = []
targets << Feature.group(params[:feature_group]) if params[:feature_group]
targets << User.find_by_username(params[:user]) if params[:user]
targets
end
end
......@@ -42,18 +40,25 @@ module API
requires :value, type: String, desc: '`true` or `false` to enable/disable, an integer for percentage of time'
optional :feature_group, type: String, desc: 'A Feature group name'
optional :user, type: String, desc: 'A GitLab username'
mutually_exclusive :feature_group, :user
end
post ':name' do
feature = Feature.get(params[:name])
target = gate_target(params)
targets = gate_targets(params)
value = gate_value(params)
case value
when true
feature.enable(target)
if targets.present?
targets.each { |target| feature.enable(target) }
else
feature.enable
end
when false
feature.disable(target)
if targets.present?
targets.each { |target| feature.disable(target) }
else
feature.disable
end
else
feature.enable_percentage_of_time(value)
end
......
......@@ -51,6 +51,8 @@ module API
optional :active, type: Boolean, default: false, desc: 'Filters only active users'
optional :external, type: Boolean, default: false, desc: 'Filters only external users'
optional :blocked, type: Boolean, default: false, desc: 'Filters only blocked users'
optional :created_after, type: DateTime, desc: 'Return users created after the specified time'
optional :created_before, type: DateTime, desc: 'Return users created before the specified time'
all_or_none_of :extern_uid, :provider
# EE
......@@ -61,6 +63,10 @@ module API
get do
authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?)
unless current_user&.admin?
params.except!(:created_after, :created_before)
end
users = UsersFinder.new(current_user, params).execute
authorized = can?(current_user, :read_users_list)
......
......@@ -30,6 +30,8 @@ module Banzai
attributes = attributes.reject { |_, v| v.nil? }
attributes[:reference_type] ||= self.class.reference_type
attributes[:container] ||= 'body'
attributes[:placement] ||= 'bottom'
attributes.delete(:original) if context[:no_original_data]
attributes.map do |key, value|
%Q(data-#{key.to_s.dasherize}="#{escape_once(value)}")
......
......@@ -13,6 +13,10 @@ module Gitlab
MergeRequestDiff.arel_table
end
def mr_diff_commits_table
MergeRequestDiffCommit.arel_table
end
def mr_closing_issues_table
MergeRequestsClosingIssues.arel_table
end
......
......@@ -2,40 +2,59 @@ module Gitlab
module CycleAnalytics
class PlanEventFetcher < BaseEventFetcher
def initialize(*args)
@projections = [mr_diff_table[:st_commits].as('commits'),
@projections = [mr_diff_table[:id],
mr_diff_table[:st_commits],
issue_metrics_table[:first_mentioned_in_commit_at]]
super(*args)
end
def events_query
base_query.join(mr_diff_table).on(mr_diff_table[:merge_request_id].eq(mr_table[:id]))
base_query
.join(mr_diff_table)
.on(mr_diff_table[:merge_request_id].eq(mr_table[:id]))
super
end
private
def merge_request_diff_commits
@merge_request_diff_commits ||=
MergeRequestDiffCommit
.where(merge_request_diff_id: event_result.map { |event| event['id'] })
.group_by(&:merge_request_diff_id)
end
def serialize(event)
st_commit = first_time_reference_commit(event.delete('commits'), event)
commit = first_time_reference_commit(event)
return unless commit
serialize_commit(event, commit, query)
end
return unless st_commit
def first_time_reference_commit(event)
return nil unless event && merge_request_diff_commits
serialize_commit(event, st_commit, query)
commits =
if event['st_commits'].present?
YAML.load(event['st_commits'])
else
merge_request_diff_commits[event['id'].to_i]
end
def first_time_reference_commit(commits, event)
return nil if commits.blank?
YAML.load(commits).find do |commit|
commits.find do |commit|
next unless commit[:committed_date] && event['first_mentioned_in_commit_at']
commit[:committed_date].to_i == DateTime.parse(event['first_mentioned_in_commit_at'].to_s).to_i
end
end
def serialize_commit(event, st_commit, query)
commit = Commit.new(Gitlab::Git::Commit.new(st_commit), @project)
def serialize_commit(event, commit, query)
commit = Commit.new(Gitlab::Git::Commit.new(commit.to_hash), @project)
AnalyticsCommitSerializer.new(project: @project, total_time: event['total_time']).represent(commit)
end
......
......@@ -27,6 +27,7 @@ project_tree:
- :author
- :events
- merge_request_diff:
- :merge_request_diff_commits
- :merge_request_diff_files
- :events
- :timelogs
......
......@@ -34,7 +34,7 @@ module Gitlab
write_csv do |csv|
ActiveRecord::Base.transaction do
User.with_two_factor.in_batches do |relation|
User.with_two_factor.in_batches do |relation| # rubocop: disable Cop/InBatches
rows = relation.pluck(:id, :encrypted_otp_secret, :encrypted_otp_secret_iv, :encrypted_otp_secret_salt)
rows.each do |row|
user = %i[id ciphertext iv salt].zip(row).to_h
......
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