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: ...@@ -348,6 +348,7 @@ db:migrate:reset-mysql:
- git fetch origin v8.14.10-ee - git fetch origin v8.14.10-ee
- git checkout -f FETCH_HEAD - git checkout -f FETCH_HEAD
- bundle install $BUNDLE_INSTALL_FLAGS - 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 - bundle exec rake db:drop db:create db:schema:load db:seed_fu
- git checkout $CI_COMMIT_SHA - git checkout $CI_COMMIT_SHA
- bundle install $BUNDLE_INSTALL_FLAGS - bundle install $BUNDLE_INSTALL_FLAGS
......
9.3.0-pre 9.4.0-pre
...@@ -43,7 +43,8 @@ class GeoNodeStatus { ...@@ -43,7 +43,8 @@ class GeoNodeStatus {
if (status.health === 'Healthy') { if (status.health === 'Healthy') {
this.$health.html(''); this.$health.html('');
} else { } 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(); this.$status.show();
......
...@@ -105,9 +105,9 @@ ...@@ -105,9 +105,9 @@
this.measurements = measurements.small; this.measurements = measurements.small;
} }
this.data = query.result[0].values; this.data = query.result[0].values;
this.unitOfDisplay = query.unit || 'N/A'; this.unitOfDisplay = query.unit || '';
this.yAxisLabel = this.columnData.y_label || 'Values'; this.yAxisLabel = this.columnData.y_label || 'Values';
this.legendTitle = query.legend || 'Average'; this.legendTitle = query.label || 'Average';
this.graphWidth = this.$refs.baseSvg.clientWidth - this.graphWidth = this.$refs.baseSvg.clientWidth -
this.margin.left - this.margin.right; this.margin.left - this.margin.right;
this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom; this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom;
...@@ -219,16 +219,16 @@ ...@@ -219,16 +219,16 @@
}; };
</script> </script>
<template> <template>
<div <div
:class="classType"> :class="classType">
<h5 <h5
class="text-center graph-title"> class="text-center graph-title">
{{columnData.title}} {{columnData.title}}
</h5> </h5>
<div <div
class="prometheus-svg-container" class="prometheus-svg-container"
:style="paddingBottomRootSvg"> :style="paddingBottomRootSvg">
<svg <svg
:viewBox="outterViewBox" :viewBox="outterViewBox"
ref="baseSvg"> ref="baseSvg">
<g <g
...@@ -239,7 +239,7 @@ ...@@ -239,7 +239,7 @@
class="y-axis" class="y-axis"
transform="translate(70, 20)"> transform="translate(70, 20)">
</g> </g>
<monitoring-legends <monitoring-legends
:graph-width="graphWidth" :graph-width="graphWidth"
:graph-height="graphHeight" :graph-height="graphHeight"
:margin="margin" :margin="margin"
...@@ -249,7 +249,7 @@ ...@@ -249,7 +249,7 @@
:y-axis-label="yAxisLabel" :y-axis-label="yAxisLabel"
:metric-usage="metricUsage" :metric-usage="metricUsage"
/> />
<svg <svg
class="graph-data" class="graph-data"
:viewBox="innerViewBox" :viewBox="innerViewBox"
ref="graphData"> ref="graphData">
...@@ -267,7 +267,7 @@ ...@@ -267,7 +267,7 @@
stroke-width="2" stroke-width="2"
transform="translate(-5, 20)"> transform="translate(-5, 20)">
</path> </path>
<rect <rect
class="prometheus-graph-overlay" class="prometheus-graph-overlay"
:width="(graphWidth - 70)" :width="(graphWidth - 70)"
:height="(graphHeight - 100)" :height="(graphHeight - 100)"
...@@ -281,7 +281,7 @@ ...@@ -281,7 +281,7 @@
:graph-height="graphHeight" :graph-height="graphHeight"
:graph-height-offset="graphHeightOffset" :graph-height-offset="graphHeightOffset"
/> />
<monitoring-flag <monitoring-flag
v-if="showFlag" v-if="showFlag"
:current-x-coordinate="currentXCoordinate" :current-x-coordinate="currentXCoordinate"
:current-y-coordinate="currentYCoordinate" :current-y-coordinate="currentYCoordinate"
......
...@@ -62,7 +62,7 @@ import findAndFollowLink from './shortcuts_dashboard_navigation'; ...@@ -62,7 +62,7 @@ import findAndFollowLink from './shortcuts_dashboard_navigation';
if (Cookies.get(performanceBarCookieName) === 'true') { if (Cookies.get(performanceBarCookieName) === 'true') {
Cookies.remove(performanceBarCookieName, { path: '/' }); Cookies.remove(performanceBarCookieName, { path: '/' });
} else { } else {
Cookies.set(performanceBarCookieName, true, { path: '/' }); Cookies.set(performanceBarCookieName, 'true', { path: '/' });
} }
gl.utils.refreshCurrentPage(); gl.utils.refreshCurrentPage();
}; };
......
...@@ -74,11 +74,17 @@ $red-700: #a62d19; ...@@ -74,11 +74,17 @@ $red-700: #a62d19;
$red-800: #8b2615; $red-800: #8b2615;
$red-900: #711e11; $red-900: #711e11;
$purple-600: #6e49cb; $indigo-50: #f7f7ff;
$purple-650: #5c35ae; $indigo-100: #ebebfa;
$purple-700: #4a2192; $indigo-200: #d1d1f0;
$purple-800: #2c0a5c; $indigo-300: #a6a6de;
$purple-900: #380d75; $indigo-400: #7c7ccc;
$indigo-500: #6666c4;
$indigo-600: #5b5bbd;
$indigo-700: #4b4ba3;
$indigo-800: #393982;
$indigo-900: #292961;
$indigo-950: #1a1a40;
$black: #000; $black: #000;
$black-transparent: rgba(0, 0, 0, 0.3); $black-transparent: rgba(0, 0, 0, 0.3);
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
header.navbar-gitlab-new { header.navbar-gitlab-new {
color: $white-light; color: $white-light;
background-color: $purple-900; background: linear-gradient(to right, $indigo-900, $indigo-800);
border-bottom: 0; border-bottom: 0;
.header-content { .header-content {
...@@ -24,11 +24,9 @@ header.navbar-gitlab-new { ...@@ -24,11 +24,9 @@ header.navbar-gitlab-new {
> a { > a {
display: flex; display: flex;
align-items: center; align-items: center;
padding-top: 3px;
padding-right: $gl-padding; padding-right: $gl-padding;
padding-left: $gl-padding; padding-left: $gl-padding;
margin-left: -$gl-padding; margin-left: -$gl-padding;
border-bottom: 3px solid transparent;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
padding-right: $gl-padding; padding-right: $gl-padding;
...@@ -45,9 +43,8 @@ header.navbar-gitlab-new { ...@@ -45,9 +43,8 @@ header.navbar-gitlab-new {
&:hover, &:hover,
&:focus { &:focus {
color: currentColor; color: $tanuki-yellow;
text-decoration: none; text-decoration: none;
border-bottom-color: $white-light;
} }
} }
} }
...@@ -71,7 +68,7 @@ header.navbar-gitlab-new { ...@@ -71,7 +68,7 @@ header.navbar-gitlab-new {
.navbar-collapse { .navbar-collapse {
padding-left: 0; padding-left: 0;
color: $white-light; color: $indigo-200;
box-shadow: 0; box-shadow: 0;
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
...@@ -101,7 +98,7 @@ header.navbar-gitlab-new { ...@@ -101,7 +98,7 @@ header.navbar-gitlab-new {
font-size: 14px; font-size: 14px;
text-align: center; text-align: center;
color: currentColor; color: currentColor;
border-left: 1px solid lighten($purple-700, 10%); border-left: 1px solid lighten($indigo-700, 10%);
&:hover, &:hover,
&:focus, &:focus,
...@@ -120,6 +117,7 @@ header.navbar-gitlab-new { ...@@ -120,6 +117,7 @@ header.navbar-gitlab-new {
li { li {
.badge { .badge {
box-shadow: none; box-shadow: none;
font-weight: 600;
} }
} }
} }
...@@ -133,12 +131,11 @@ header.navbar-gitlab-new { ...@@ -133,12 +131,11 @@ header.navbar-gitlab-new {
> a { > a {
background: none; background: none;
opacity: .9; will-change: color;
will-change: opacity;
&.header-user-dropdown-toggle { &.header-user-dropdown-toggle {
.header-user-avatar { .header-user-avatar {
border-color: $white-light; border-color: $indigo-200;
} }
} }
...@@ -165,29 +162,34 @@ header.navbar-gitlab-new { ...@@ -165,29 +162,34 @@ header.navbar-gitlab-new {
.navbar-sub-nav { .navbar-sub-nav {
display: flex; display: flex;
margin-bottom: 0; margin-bottom: 0;
color: $white-light; color: $indigo-200;
> li { > li {
&.active > a, > a:hover,
a:hover, > a:focus {
a:focus { box-shadow: inset 0 -3px 0 rgba($indigo-200, .4);
border-bottom-color: $white-light;
text-decoration: none; text-decoration: none;
outline: 0; outline: 0;
opacity: 1; color: $white-light;
}
&.active > a {
box-shadow: inset 0 -3px 0 $indigo-500;
color: $white-light;
font-weight: 700;
} }
> a { > a {
display: block; display: block;
padding: 16px 10px 13px; padding: 16px 10px;
font-size: 13px; font-size: 13px;
color: currentColor; color: currentColor;
border-bottom: 3px solid transparent; box-shadow: inset 0 0 0 transparent;
opacity: .9; will-change: box-shadow;
will-change: opacity; transition: box-shadow 0.15s;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
padding: 15px $gl-padding 12px; padding: 15px $gl-padding;
font-size: 14px; font-size: 14px;
} }
} }
...@@ -207,55 +209,60 @@ header.navbar-gitlab-new { ...@@ -207,55 +209,60 @@ header.navbar-gitlab-new {
.search { .search {
form { 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 { &:hover {
border-color: rgba($white-light, .6); background-color: rgba($indigo-200, .3);
box-shadow: none; box-shadow: none;
} }
} }
&.search-active form { &.search-active form {
border-color: $white-light; background-color: rgba($indigo-200, .3);
} box-shadow: none;
form,
.search-input {
background-color: $purple-700;
} }
.search-input { .search-input {
color: $white-light; color: $white-light;
background: none;
} }
.search-input::placeholder { .search-input::placeholder {
color: rgba($white-light, .6); color: rgba($indigo-200, .8);
} }
.location-badge { .location-badge {
font-size: 12px; font-size: 12px;
color: rgba($white-light, .6); color: $indigo-100;
background-color: $purple-800; background-color: rgba($indigo-200, .1);
transition: color 0.15s; transition: color 0.15s;
will-change: color; 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-input-wrap {
.search-icon, .search-icon,
.clear-icon { .clear-icon {
color: rgba($white-light, .6); color: rgba($indigo-200, .8);
} }
} }
&.search-active { &.search-active {
.location-badge { .location-badge {
color: $white-light; color: $white-light;
background-color: $purple-800; background-color: rgba($indigo-200, .2);
} }
.search-input-wrap { .search-input-wrap {
.search-icon { .search-icon {
color: rgba($white-light, .6); color: rgba($indigo-200, .8);
} }
.clear-icon { .clear-icon {
......
...@@ -2,6 +2,15 @@ ...@@ -2,6 +2,15 @@
@import 'framework/tw_bootstrap_variables'; @import 'framework/tw_bootstrap_variables';
@import "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; $new-sidebar-width: 220px;
.page-with-new-sidebar { .page-with-new-sidebar {
...@@ -17,24 +26,39 @@ $new-sidebar-width: 220px; ...@@ -17,24 +26,39 @@ $new-sidebar-width: 220px;
} }
.context-header { .context-header {
background-color: $gray-normal;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
font-weight: 600; font-weight: 600;
display: flex; display: flex;
align-items: center; align-items: center;
padding: 10px 14px; padding: 10px 16px 10px 10px;
color: $gl-text-color;
.avatar-container { .avatar-container {
flex: 0 0 40px; flex: 0 0 40px;
} }
&:hover { &: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 { .settings-avatar {
background-color: $white-light; background-color: $white-light;
transition: background-color 100ms linear;
i { i {
font-size: 20px; font-size: 20px;
...@@ -42,6 +66,7 @@ $new-sidebar-width: 220px; ...@@ -42,6 +66,7 @@ $new-sidebar-width: 220px;
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
text-align: center; text-align: center;
align-self: center; align-self: center;
transition: color 100ms linear;
} }
} }
...@@ -54,11 +79,15 @@ $new-sidebar-width: 220px; ...@@ -54,11 +79,15 @@ $new-sidebar-width: 220px;
bottom: 0; bottom: 0;
left: 0; left: 0;
overflow: auto; overflow: auto;
background-color: $gray-light; background-color: $gray-normal;
border-right: 1px solid $border-color; box-shadow: inset -2px 0 0 $border-color;
a {
text-decoration: none;
}
ul { ul {
padding: 0; padding-left: 0;
list-style: none; list-style: none;
} }
...@@ -67,13 +96,18 @@ $new-sidebar-width: 220px; ...@@ -67,13 +96,18 @@ $new-sidebar-width: 220px;
a { a {
display: block; display: block;
padding: 12px 14px; padding: 12px 16px;
color: $inactive-color;
} }
} }
a { li.active {
color: $gl-text-color; box-shadow: inset 4px 0 0 $active-border;
text-decoration: none;
> a {
color: $active-color;
font-weight: 700;
}
} }
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
...@@ -83,22 +117,28 @@ $new-sidebar-width: 220px; ...@@ -83,22 +117,28 @@ $new-sidebar-width: 220px;
.sidebar-sub-level-items { .sidebar-sub-level-items {
display: none; display: none;
padding-bottom: 8px;
> li { > li {
a { a {
padding: 12px 24px; font-size: 12px;
color: $gl-text-color-light; padding: 8px 16px 8px 24px;
&:hover { &:hover,
color: $gl-text-color; &:focus {
background-color: $border-color; background: $active-hover-background;
color: $active-hover-color;
} }
} }
&.active { &.active {
> a { a {
color: $purple-650; &,
font-weight: 600; &:hover,
&:focus {
background: $active-background;
color: $active-color;
}
} }
} }
} }
...@@ -108,35 +148,31 @@ $new-sidebar-width: 220px; ...@@ -108,35 +148,31 @@ $new-sidebar-width: 220px;
> li { > li {
.badge { .badge {
float: right; float: right;
background-color: $border-color; background-color: $inactive-badge-background;
color: $gl-text-color; color: $inactive-color;
} }
&.active { &.active {
> a { background: $active-background;
background-color: $purple-600;
color: $white-light;
font-weight: 600;
}
.badge { .badge {
background-color: $purple-700; color: $active-color;
color: $white-light; font-weight: 600;
} }
.sidebar-sub-level-items { .sidebar-sub-level-items {
background-color: $gray-normal;
border-left: 6px solid $purple-600;
display: block; display: block;
} }
} }
&:not(.active) > a:hover { > a:hover {
background-color: $border-color; background-color: $hover-background;
color: $hover-color;
.badge { .badge {
transition: background-color 100ms linear; transition: background-color 100ms linear, color 100ms linear;
background-color: $gray-normal; background-color: $indigo-500;
color: $hover-color;
} }
} }
} }
...@@ -155,3 +191,13 @@ $new-sidebar-width: 220px; ...@@ -155,3 +191,13 @@ $new-sidebar-width: 220px;
// scss-lint:enable DuplicateProperty // 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 @@ ...@@ -60,8 +60,6 @@
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
display: -webkit-flex; display: -webkit-flex;
display: flex; display: flex;
width: 400px;
max-width: 50%;
} }
} }
...@@ -71,7 +69,6 @@ ...@@ -71,7 +69,6 @@
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
display: -webkit-flex; display: -webkit-flex;
display: flex; display: flex;
width: 100%;
margin-top: 3px; margin-top: 3px;
} }
} }
...@@ -87,18 +84,10 @@ ...@@ -87,18 +84,10 @@
.member-form-control { .member-form-control {
@media (max-width: $screen-xs-max) { @media (max-width: $screen-xs-max) {
padding: 5px 0; padding-bottom: 5px;
margin-left: 0; margin-left: 0;
margin-right: 0; margin-right: 0;
} }
@media (min-width: $screen-sm-min) {
width: 50%;
}
.dropdown-menu-toggle {
width: 100%;
}
} }
.member-access-text { .member-access-text {
...@@ -266,3 +255,102 @@ ...@@ -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 @@ ...@@ -731,11 +731,11 @@
.merge-request-tabs-holder { .merge-request-tabs-holder {
top: $header-height; top: $header-height;
z-index: 100; z-index: 200;
background-color: $white-light; background-color: $white-light;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
@media(min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
position: sticky; position: sticky;
position: -webkit-sticky; position: -webkit-sticky;
} }
...@@ -770,6 +770,12 @@ ...@@ -770,6 +770,12 @@
max-width: $limited-layout-width; max-width: $limited-layout-width;
margin-left: auto; margin-left: auto;
margin-right: 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 ...@@ -127,6 +127,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:metrics_port, :metrics_port,
:metrics_sample_interval, :metrics_sample_interval,
:metrics_timeout, :metrics_timeout,
:performance_bar_allowed_group_id,
:performance_bar_enabled,
:recaptcha_enabled, :recaptcha_enabled,
:recaptcha_private_key, :recaptcha_private_key,
:recaptcha_site_key, :recaptcha_site_key,
......
...@@ -9,7 +9,7 @@ class ApplicationController < ActionController::Base ...@@ -9,7 +9,7 @@ class ApplicationController < ActionController::Base
include SentryHelper include SentryHelper
include WorkhorseHelper include WorkhorseHelper
include EnforcesTwoFactorAuthentication include EnforcesTwoFactorAuthentication
include Peek::Rblineprof::CustomControllerHelpers include WithPerformanceBar
before_action :authenticate_user_from_private_token! before_action :authenticate_user_from_private_token!
before_action :authenticate_user_from_rss_token! before_action :authenticate_user_from_rss_token!
...@@ -68,21 +68,6 @@ class ApplicationController < ActionController::Base ...@@ -68,21 +68,6 @@ class ApplicationController < ActionController::Base
end end
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 protected
# This filter handles both private tokens and personal access tokens # This filter handles both private tokens and personal access tokens
......
...@@ -47,7 +47,7 @@ module IssuableCollections ...@@ -47,7 +47,7 @@ module IssuableCollections
end end
def merge_requests_collection 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 end
def issues_finder 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 class Dashboard::LabelsController < Dashboard::ApplicationController
def index def index
labels = LabelsFinder.new(current_user).execute
respond_to do |format| respond_to do |format|
format.json { render json: LabelSerializer.new.represent_appearance(labels) } format.json { render json: LabelSerializer.new.represent_appearance(labels) }
end end
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 end
...@@ -3,6 +3,10 @@ module Projects ...@@ -3,6 +3,10 @@ module Projects
class SlacksController < Projects::ApplicationController class SlacksController < Projects::ApplicationController
before_action :handle_oauth_error, only: :slack_auth before_action :handle_oauth_error, only: :slack_auth
before_action :authorize_admin_project! 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 def slack_auth
result = Projects::SlackApplicationInstallService.new(project, current_user, params).execute result = Projects::SlackApplicationInstallService.new(project, current_user, params).execute
...@@ -15,12 +19,24 @@ module Projects ...@@ -15,12 +19,24 @@ module Projects
end end
def destroy def destroy
service = project.gitlab_slack_application_service slack_integration.destroy
service.slack_integration.destroy
redirect_to_service_page redirect_to_service_page
end 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 private
def redirect_to_service_page def redirect_to_service_page
...@@ -36,6 +52,18 @@ module Projects ...@@ -36,6 +52,18 @@ module Projects
redirect_to_service_page redirect_to_service_page
end end
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 end
end end
...@@ -337,6 +337,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -337,6 +337,7 @@ class ProjectsController < Projects::ApplicationController
def project_params_ee def project_params_ee
%i[ %i[
approvals_before_merge approvals_before_merge
approvals
approver_group_ids approver_group_ids
approver_ids approver_ids
issues_template issues_template
...@@ -345,6 +346,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -345,6 +346,7 @@ class ProjectsController < Projects::ApplicationController
mirror mirror
mirror_trigger_builds mirror_trigger_builds
mirror_user_id mirror_user_id
disable_overriding_approvers_per_merge_request
repository_size_limit repository_size_limit
reset_approvals_on_push reset_approvals_on_push
service_desk_enabled 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 @@ ...@@ -19,6 +19,8 @@
# iids: integer[] # iids: integer[]
# #
class IssuableFinder class IssuableFinder
include CreatedAtFilter
NONE = '0'.freeze NONE = '0'.freeze
IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page].freeze IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page].freeze
...@@ -36,6 +38,7 @@ class IssuableFinder ...@@ -36,6 +38,7 @@ class IssuableFinder
def execute def execute
items = init_collection items = init_collection
items = by_scope(items) items = by_scope(items)
items = by_created_at(items)
items = by_state(items) items = by_state(items)
items = by_group(items) items = by_group(items)
items = by_search(items) items = by_search(items)
...@@ -47,7 +50,6 @@ class IssuableFinder ...@@ -47,7 +50,6 @@ class IssuableFinder
items = by_iids(items) items = by_iids(items)
items = by_milestone(items) items = by_milestone(items)
items = by_label(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 # 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) items = by_project(items)
...@@ -438,18 +440,6 @@ class IssuableFinder ...@@ -438,18 +440,6 @@ class IssuableFinder
params[:non_archived].present? ? items.non_archived : items params[:non_archived].present? ? items.non_archived : items
end 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? def current_user_related?
params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me' params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me'
end end
......
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
# skip_ldap: boolean # skip_ldap: boolean
# #
class UsersFinder class UsersFinder
include CreatedAtFilter
attr_accessor :current_user, :params attr_accessor :current_user, :params
def initialize(current_user, params = {}) def initialize(current_user, params = {})
...@@ -30,6 +32,7 @@ class UsersFinder ...@@ -30,6 +32,7 @@ class UsersFinder
users = by_active(users) users = by_active(users)
users = by_external_identity(users) users = by_external_identity(users)
users = by_external(users) users = by_external(users)
users = by_created_at(users)
users = by_non_ldap(users) users = by_non_ldap(users)
users users
......
...@@ -84,7 +84,8 @@ module ButtonHelper ...@@ -84,7 +84,8 @@ module ButtonHelper
html: true, html: true,
placement: placement, placement: placement,
container: 'body', 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 end
......
...@@ -8,7 +8,7 @@ module EE ...@@ -8,7 +8,7 @@ module EE
end end
def geo_primary_ssh_url_to_repo(project) 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 end
def geo_primary_http_url_to_repo(project) def geo_primary_http_url_to_repo(project)
......
...@@ -247,6 +247,7 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -247,6 +247,7 @@ class ApplicationSetting < ActiveRecord::Base
koding_url: nil, koding_url: nil,
max_artifacts_size: Settings.artifacts['max_size'], max_artifacts_size: Settings.artifacts['max_size'],
max_attachment_size: Settings.gitlab['max_attachment_size'], max_attachment_size: Settings.gitlab['max_attachment_size'],
performance_bar_allowed_group_id: nil,
plantuml_enabled: false, plantuml_enabled: false,
plantuml_url: nil, plantuml_url: nil,
recaptcha_enabled: false, recaptcha_enabled: false,
...@@ -383,6 +384,48 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -383,6 +384,48 @@ class ApplicationSetting < ActiveRecord::Base
super(levels.map { |level| Gitlab::VisibilityLevel.level_value(level) }) super(levels.map { |level| Gitlab::VisibilityLevel.level_value(level) })
end 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 # Choose one of the available repository storage options. Currently all have
# equal weighting. # equal weighting.
def pick_repository_storage def pick_repository_storage
......
...@@ -10,5 +10,11 @@ module BlobViewer ...@@ -10,5 +10,11 @@ module BlobViewer
def visible_to?(current_user) def visible_to?(current_user)
can?(current_user, :read_wiki, project) can?(current_user, :read_wiki, project)
end end
def render_error
return if project.has_external_wiki? || (project.wiki_enabled? && project.wiki.has_home_page?)
:no_wiki
end
end end
end end
...@@ -222,7 +222,7 @@ module Ci ...@@ -222,7 +222,7 @@ module Ci
.reorder(iid: :desc) .reorder(iid: :desc)
merge_requests.find do |merge_request| merge_requests.find do |merge_request|
merge_request.commits_sha.include?(pipeline.sha) merge_request.commit_shas.include?(pipeline.sha)
end end
end end
end end
......
...@@ -138,7 +138,7 @@ class Commit ...@@ -138,7 +138,7 @@ class Commit
safe_message.split("\n", 2)[1].try(:chomp) safe_message.split("\n", 2)[1].try(:chomp)
end end
def description? def description?
description.present? description.present?
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 ...@@ -165,6 +165,10 @@ module EE
repository.fetch_upstream(self.import_url) repository.fetch_upstream(self.import_url)
end end
def can_override_approvers?
!disable_overriding_approvers_per_merge_request?
end
def shared_runners_available? def shared_runners_available?
super && !namespace.shared_runners_minutes_used? super && !namespace.shared_runners_minutes_used?
end end
......
...@@ -2,7 +2,7 @@ class GlobalLabel ...@@ -2,7 +2,7 @@ class GlobalLabel
attr_accessor :title, :labels attr_accessor :title, :labels
alias_attribute :name, :title alias_attribute :name, :title
delegate :color, :description, to: :@first_label delegate :color, :text_color, :description, to: :@first_label
def for_display def for_display
@first_label @first_label
......
...@@ -13,6 +13,7 @@ class Issue < ActiveRecord::Base ...@@ -13,6 +13,7 @@ class Issue < ActiveRecord::Base
include FasterCacheKeys include FasterCacheKeys
include RelativePositioning include RelativePositioning
include IgnorableColumn include IgnorableColumn
include CreatedAtFilterable
ignore_column :position ignore_column :position
...@@ -60,8 +61,6 @@ class Issue < ActiveRecord::Base ...@@ -60,8 +61,6 @@ class Issue < ActiveRecord::Base
scope :order_weight_desc, -> { reorder('weight IS NOT NULL, weight DESC') } scope :order_weight_desc, -> { reorder('weight IS NOT NULL, weight DESC') }
scope :order_weight_asc, -> { reorder('weight ASC') } scope :order_weight_asc, -> { reorder('weight ASC') }
scope :created_after, -> (datetime) { where("created_at >= ?", datetime) }
scope :preload_associations, -> { preload(:labels, project: :namespace) } scope :preload_associations, -> { preload(:labels, project: :namespace) }
after_save :expire_etag_cache after_save :expire_etag_cache
......
...@@ -6,6 +6,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -6,6 +6,7 @@ class MergeRequest < ActiveRecord::Base
include Sortable include Sortable
include Elastic::MergeRequestsSearch include Elastic::MergeRequestsSearch
include IgnorableColumn include IgnorableColumn
include CreatedAtFilterable
ignore_column :position ignore_column :position
...@@ -34,7 +35,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -34,7 +35,7 @@ class MergeRequest < ActiveRecord::Base
after_create :ensure_merge_request_diff, unless: :importing? after_create :ensure_merge_request_diff, unless: :importing?
after_update :reload_diff_if_branch_changed 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 to: :merge_request_diff, prefix: nil
delegate :codeclimate_artifact, to: :head_pipeline, prefix: :head, allow_nil: true delegate :codeclimate_artifact, to: :head_pipeline, prefix: :head, allow_nil: true
...@@ -542,7 +543,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -542,7 +543,7 @@ class MergeRequest < ActiveRecord::Base
def related_notes def related_notes
# Fetch comments only from last 100 commits # Fetch comments only from last 100 commits
commits_for_notes_limit = 100 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( Note.where(
"(project_id = :target_project_id AND noteable_type = 'MergeRequest' AND noteable_id = :mr_id) OR" + "(project_id = :target_project_id AND noteable_type = 'MergeRequest' AND noteable_id = :mr_id) OR" +
...@@ -865,15 +866,15 @@ class MergeRequest < ActiveRecord::Base ...@@ -865,15 +866,15 @@ class MergeRequest < ActiveRecord::Base
return Ci::Pipeline.none unless source_project return Ci::Pipeline.none unless source_project
@all_pipelines ||= source_project.pipelines @all_pipelines ||= source_project.pipelines
.where(sha: all_commits_sha, ref: source_branch) .where(sha: all_commit_shas, ref: source_branch)
.order(id: :desc) .order(id: :desc)
end end
# Note that this could also return SHA from now dangling commits # Note that this could also return SHA from now dangling commits
# #
def all_commits_sha def all_commit_shas
if persisted? 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 elsif compare_commits
compare_commits.to_a.reverse.map(&:id) compare_commits.to_a.reverse.map(&:id)
else else
......
...@@ -11,6 +11,7 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -11,6 +11,7 @@ class MergeRequestDiff < ActiveRecord::Base
belongs_to :merge_request belongs_to :merge_request
has_many :merge_request_diff_files, -> { order(:merge_request_diff_id, :relative_order) } 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_commits # rubocop:disable Cop/ActiveRecordSerialize
serialize :st_diffs # rubocop:disable Cop/ActiveRecordSerialize serialize :st_diffs # rubocop:disable Cop/ActiveRecordSerialize
...@@ -47,14 +48,13 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -47,14 +48,13 @@ class MergeRequestDiff < ActiveRecord::Base
# Collect information about commits and diff from repository # Collect information about commits and diff from repository
# and save it to the database as serialized data # and save it to the database as serialized data
def save_git_content def save_git_content
ensure_commits_sha ensure_commit_shas
save_commits save_commits
reload_commits
save_diffs save_diffs
keep_around_commits keep_around_commits
end end
def ensure_commits_sha def ensure_commit_shas
merge_request.fetch_ref merge_request.fetch_ref
self.start_commit_sha ||= merge_request.target_branch_sha self.start_commit_sha ||= merge_request.target_branch_sha
self.head_commit_sha ||= merge_request.source_branch_sha self.head_commit_sha ||= merge_request.source_branch_sha
...@@ -66,7 +66,7 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -66,7 +66,7 @@ class MergeRequestDiff < ActiveRecord::Base
# created before version 8.4 that does not store head_commit_sha in separate db field. # created before version 8.4 that does not store head_commit_sha in separate db field.
def head_commit_sha def head_commit_sha
if persisted? && super.nil? if persisted? && super.nil?
last_commit.try(:sha) last_commit_sha
else else
super super
end end
...@@ -97,16 +97,11 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -97,16 +97,11 @@ class MergeRequestDiff < ActiveRecord::Base
end end
def commits def commits
@commits ||= load_commits(st_commits) @commits ||= load_commits
end end
def reload_commits def last_commit_sha
@commits = nil commit_shas.first
commits
end
def last_commit
commits.first
end end
def first_commit def first_commit
...@@ -131,8 +126,12 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -131,8 +126,12 @@ class MergeRequestDiff < ActiveRecord::Base
project.commit(head_commit_sha) project.commit(head_commit_sha)
end end
def commits_sha def commit_shas
st_commits.map { |commit| commit[:id] } if st_commits.present?
st_commits.map { |commit| commit[:id] }
else
merge_request_diff_commits.map(&:sha)
end
end end
def diff_refs=(new_diff_refs) def diff_refs=(new_diff_refs)
...@@ -207,7 +206,11 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -207,7 +206,11 @@ class MergeRequestDiff < ActiveRecord::Base
end end
def commits_count def commits_count
st_commits.count if st_commits.present?
st_commits.size
else
merge_request_diff_commits.size
end
end end
def utf8_st_diffs def utf8_st_diffs
...@@ -231,29 +234,6 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -231,29 +234,6 @@ class MergeRequestDiff < ActiveRecord::Base
raw.any? { |element| VALID_CLASSES.include?(element.class) } raw.any? { |element| VALID_CLASSES.include?(element.class) }
end 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) def create_merge_request_diff_files(diffs)
rows = diffs.map.with_index do |diff, index| rows = diffs.map.with_index do |diff, index|
diff.to_hash.merge( diff.to_hash.merge(
...@@ -294,12 +274,18 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -294,12 +274,18 @@ class MergeRequestDiff < ActiveRecord::Base
end end
end end
# Load diffs between branches related to current merge request diff from repo def load_commits
# and save it as array of hashes in st_diffs db field 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 def save_diffs
new_attributes = {} new_attributes = {}
if commits.size.zero? if compare.commits.size.zero?
new_attributes[:state] = :empty new_attributes[:state] = :empty
else else
diff_collection = compare.diffs(Commit.max_diff_options) diff_collection = compare.diffs(Commit.max_diff_options)
...@@ -319,7 +305,13 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -319,7 +305,13 @@ class MergeRequestDiff < ActiveRecord::Base
new_attributes[:state] = :overflow if diff_collection.overflow? new_attributes[:state] = :overflow if diff_collection.overflow?
end 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 end
def repository def repository
...@@ -332,29 +324,6 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -332,29 +324,6 @@ class MergeRequestDiff < ActiveRecord::Base
project.merge_base_commit(head_commit_sha, start_commit_sha).try(:sha) project.merge_base_commit(head_commit_sha, start_commit_sha).try(:sha)
end 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 def keep_around_commits
[repository, merge_request.source_project.repository].each do |repo| [repository, merge_request.source_project.repository].each do |repo|
repo.keep_around(start_commit_sha) 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 ...@@ -70,6 +70,10 @@ class ProjectWiki
!!repository.exists? !!repository.exists?
end end
def has_home_page?
!!find_page('home')
end
# Returns an Array of Gitlab WikiPage instances or an # Returns an Array of Gitlab WikiPage instances or an
# empty Array if this Wiki has no pages. # empty Array if this Wiki has no pages.
def pages def pages
......
...@@ -4,7 +4,8 @@ class SlackIntegration < ActiveRecord::Base ...@@ -4,7 +4,8 @@ class SlackIntegration < ActiveRecord::Base
validates :team_id, presence: true validates :team_id, presence: true
validates :team_name, presence: true validates :team_name, presence: true
validates :alias, 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 :user_id, presence: true
validates :service, presence: true validates :service, presence: true
......
...@@ -12,6 +12,7 @@ class User < ActiveRecord::Base ...@@ -12,6 +12,7 @@ class User < ActiveRecord::Base
include TokenAuthenticatable include TokenAuthenticatable
include IgnorableColumn include IgnorableColumn
include FeatureGate include FeatureGate
include CreatedAtFilterable
prepend EE::GeoAwareAvatar prepend EE::GeoAwareAvatar
prepend EE::User prepend EE::User
......
class LabelEntity < Grape::Entity class LabelEntity < Grape::Entity
expose :id expose :id, if: ->(label, _) { !label.is_a?(GlobalLabel) }
expose :title expose :title
expose :color expose :color
expose :description expose :description
......
...@@ -28,7 +28,8 @@ module Geo ...@@ -28,7 +28,8 @@ module Geo
if payload.is_a?(Hash) if payload.is_a?(Hash)
payload['message'] payload['message']
else else
payload # The return value can be a giant blob of HTML; ignore it
''
end end
Array([message, details].compact.join("\n")) Array([message, details].compact.join("\n"))
......
...@@ -9,6 +9,12 @@ module MergeRequests ...@@ -9,6 +9,12 @@ module MergeRequests
params[:target_project_id] ||= source_project.id 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 = MergeRequest.new
merge_request.source_project = source_project merge_request.source_project = source_project
merge_request.source_branch = params[:source_branch] merge_request.source_branch = params[:source_branch]
......
...@@ -69,7 +69,7 @@ module MergeRequests ...@@ -69,7 +69,7 @@ module MergeRequests
if merge_request.source_branch == @branch_name || force_push? if merge_request.source_branch == @branch_name || force_push?
merge_request.reload_diff(current_user) merge_request.reload_diff(current_user)
else else
mr_commit_ids = merge_request.commits_sha mr_commit_ids = merge_request.commit_shas
push_commit_ids = @commits.map(&:id) push_commit_ids = @commits.map(&:id)
matches = mr_commit_ids & push_commit_ids matches = mr_commit_ids & push_commit_ids
merge_request.reload_diff(current_user) if matches.any? merge_request.reload_diff(current_user) if matches.any?
...@@ -145,7 +145,7 @@ module MergeRequests ...@@ -145,7 +145,7 @@ module MergeRequests
return unless @commits.present? return unless @commits.present?
merge_requests_for_source_branch.each do |merge_request| 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| new_commits, existing_commits = @commits.partition do |commit|
mr_commit_ids.include?(commit.id) mr_commit_ids.include?(commit.id)
...@@ -161,7 +161,7 @@ module MergeRequests ...@@ -161,7 +161,7 @@ module MergeRequests
return unless @commits.present? return unless @commits.present?
merge_requests_for_source_branch.each do |merge_request| 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| wip_commit = @commits.detect do |commit|
commit.work_in_progress? && commit_shas.include?(commit.sha) commit.work_in_progress? && commit_shas.include?(commit.sha)
......
...@@ -17,6 +17,12 @@ module MergeRequests ...@@ -17,6 +17,12 @@ module MergeRequests
merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
end 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 old_approvers = merge_request.overall_approvers.to_a
handle_wip_event(merge_request) handle_wip_event(merge_request)
......
...@@ -360,6 +360,22 @@ ...@@ -360,6 +360,22 @@
%strong.cred WARNING: %strong.cred WARNING:
Environment variable `prometheus_multiproc_dir` does not exist or is not pointing to a valid directory. 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 %fieldset
%legend Background Jobs %legend Background Jobs
%p %p
......
...@@ -122,8 +122,7 @@ ...@@ -122,8 +122,7 @@
= select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2" = select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2"
%hr %hr
= button_tag 'Add users to group', class: "btn btn-create" = button_tag 'Add users to group', class: "btn btn-create"
= render 'shared/members/requests', membership_source: @group, requesters: @requesters, force_mobile_view: true
= render 'shared/members/requests', membership_source: @group, requesters: @requesters
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
...@@ -132,7 +131,7 @@ ...@@ -132,7 +131,7 @@
%span.badge= @group.members.size %span.badge= @group.members.size
.pull-right .pull-right
= link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@group, :members]), class: "btn btn-xs" = 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 } = render partial: 'shared/members/member', collection: @members, as: :member, locals: { show_controls: false }
.panel-footer .panel-footer
= paginate @members, param_name: 'members_page', theme: 'gitlab' = paginate @members, param_name: 'members_page', theme: 'gitlab'
...@@ -162,12 +162,12 @@ ...@@ -162,12 +162,12 @@
.pull-right .pull-right
= link_to admin_group_path(@group), class: 'btn btn-xs' do = link_to admin_group_path(@group), class: 'btn btn-xs' do
= icon('pencil-square-o', text: 'Manage access') = 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 } = render partial: 'shared/members/member', collection: @group_members, as: :member, locals: { show_controls: false }
.panel-footer .panel-footer
= paginate @group_members, param_name: 'group_members_page', theme: 'gitlab' = 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.panel-default
.panel-heading .panel-heading
...@@ -176,7 +176,7 @@ ...@@ -176,7 +176,7 @@
%span.badge= @project.users.size %span.badge= @project.users.size
.pull-right .pull-right
= link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@project, :members]), class: "btn btn-xs" = 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 } = render partial: 'shared/members/member', collection: @project_members, as: :member, locals: { show_controls: false }
.panel-footer .panel-footer
= paginate @project_members, param_name: 'project_members_page', theme: 'gitlab' = paginate @project_members, param_name: 'project_members_page', theme: 'gitlab'
...@@ -29,6 +29,6 @@ ...@@ -29,6 +29,6 @@
Members with access to Members with access to
%strong= @group.name %strong= @group.name
%span.badge= @members.total_count %span.badge= @members.total_count
%ul.content-list %ul.content-list.members-list
= render partial: 'shared/members/member', collection: @members, as: :member = render partial: 'shared/members/member', collection: @members, as: :member
= paginate @members, theme: 'gitlab' = paginate @members, theme: 'gitlab'
= icon('info-circle fw') = icon('info-circle fw')
= succeed '.' do = succeed '.' do
To learn more about this project, read 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) - return unless project.feature_available?(:merge_request_approvers)
- can_override_approvers = project.can_override_approvers?
.form-group.reset-approvals-on-push .form-group.reset-approvals-on-push
.checkbox .checkbox
= label_tag :require_approvals do = label_tag :require_approvals do
= check_box_tag :require_approvals, nil, project.approvals_before_merge.nonzero?, class: 'js-require-approvals-toggle' = 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' = 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' } .nested-settings{ class: project.approvals_before_merge.nonzero? ? '' : 'hidden' }
.form-group .form-group
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
%button.btn.btn-success.js-add-approvers{ type: 'button', title: 'Add approvers(s)' } %button.btn.btn-success.js-add-approvers{ type: 'button', title: 'Add approvers(s)' }
Add Add
.help-block .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.panel-default.prepend-top-10.js-current-approvers
.panel-heading .panel-heading
...@@ -61,11 +61,18 @@ ...@@ -61,11 +61,18 @@
Approvals required Approvals required
= form.number_field :approvals_before_merge, class: "form-control", min: 0 = form.number_field :approvals_before_merge, class: "form-control", min: 0
.help-block .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 .form-group.reset-approvals-on-push
.checkbox .checkbox
= form.label :reset_approvals_on_push do = form.label :reset_approvals_on_push do
= form.check_box :reset_approvals_on_push = form.check_box :reset_approvals_on_push
%strong Reset approvals on push %strong Remove all approvals in a merge request when new commits are pushed to its source branch
.descr Approvals are reset when new data is pushed to the merge request
.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 .row.prepend-top-default
.col-lg-4.settings-sidebar .col-lg-12
%h4.prepend-top-0 %h4
Project members Project members
- if can?(current_user, :admin_project_member, @project) - if can?(current_user, :admin_project_member, @project)
%p %p
...@@ -13,7 +13,6 @@ ...@@ -13,7 +13,6 @@
%i Masters %i Masters
or or
%i Owners %i Owners
.col-lg-8
.light .light
- if can?(current_user, :admin_project_member, @project) && !membership_locked? - if can?(current_user, :admin_project_member, @project) && !membership_locked?
%ul.nav-links.project-member-tabs{ role: 'tablist' } %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 @@ ...@@ -11,5 +11,5 @@
%button.member-search-btn{ type: "submit", "aria-label" => "Submit search" } %button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
= icon("search") = icon("search")
= render 'shared/members/sort_dropdown' = render 'shared/members/sort_dropdown'
%ul.content-list %ul.content-list.members-list
= render partial: 'shared/members/member', collection: members, as: :member = render partial: 'shared/members/member', collection: members, as: :member
...@@ -5,8 +5,8 @@ ...@@ -5,8 +5,8 @@
%colgroup %colgroup
%col %col
%col %col
%col.hidden-xs %col
%col{ width: "120" } %col
%thead %thead
%tr %tr
%th Team name %th Team name
...@@ -18,9 +18,11 @@ ...@@ -18,9 +18,11 @@
= slack_integration.team_name = slack_integration.team_name
%td %td
= slack_integration.alias = slack_integration.alias
%td.light %td
= time_ago_in_words slack_integration.created_at = time_ago_in_words slack_integration.created_at
ago ago
%td.light %td
- project = @service.project .controls
= link_to 'Remove', namespace_project_settings_slack_path(project.namespace, project), method: :delete, class: 'btn btn-danger', data: { confirm: 'Are you sure?' } - project = @service.project
= 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 @@ ...@@ -21,7 +21,8 @@
remote: remote:
= clipboard_button(target: 'pre#geo-info-2') = clipboard_button(target: 'pre#geo-info-2')
%pre#geo-info-2.dark %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 %p
%strong= 'Done.' %strong= 'Done.'
......
...@@ -4,31 +4,23 @@ ...@@ -4,31 +4,23 @@
- return unless issuable.is_a?(MergeRequest) - return unless issuable.is_a?(MergeRequest)
- return unless issuable.requires_approve? - return unless issuable.requires_approve?
.form-group - can_override_approvers = issuable.target_project.can_override_approvers?
= 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.
.form-group .form-group
= form.label :approver_ids, class: 'control-label' do = form.label :approver_ids, class: 'control-label' do
Approvers Approvers
.col-sm-10 .col-sm-10
- author = issuable.author || current_user - if can_override_approvers
- 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: issuable.all_approvers_including_groups, project: issuable.target_project)
= users_select_tag("merge_request[approver_ids]", multiple: true, class: 'input-large', email_user: true, skip_users: skip_users, project: issuable.target_project) .help-block
.help-block This merge request must be approved by these users.
This merge request must be approved by these users. You can override the project settings by setting your own list of approvers.
You can override the project settings by setting your own list of approvers.
- skip_groups = issuable.overall_approver_groups.pluck(:group_id) - skip_groups = issuable.overall_approver_groups.pluck(:group_id)
= groups_select_tag('merge_request[approver_group_ids]', multiple: true, data: { skip_groups: skip_groups, all_available: true, project: issuable.target_project }, class: 'input-large') = groups_select_tag('merge_request[approver_group_ids]', multiple: true, data: { skip_groups: skip_groups, all_available: true, project: issuable.target_project }, class: 'input-large')
.help-block .help-block
This merge request must be approved by members of these groups. This merge request must be approved by members of these groups.
You can override the project settings by setting your own list of approvers. You can override the project settings by setting your own list of approvers.
.panel.panel-default.prepend-top-10 .panel.panel-default.prepend-top-10
.panel-heading .panel-heading
...@@ -42,29 +34,39 @@ ...@@ -42,29 +34,39 @@
- issuable.overall_approvers.each do |approver| - issuable.overall_approvers.each do |approver|
%li{ id: dom_id(approver.user), class: item_classes + ['approver'] } %li{ id: dom_id(approver.user), class: item_classes + ['approver'] }
= link_to approver.user.name, approver.user = link_to approver.user.name, approver.user
.pull-right - if can_override_approvers
- if unsaved_approvers .pull-right
= 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 - if unsaved_approvers
= icon("sign-out") = 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
Remove = icon("sign-out")
- else Remove
= link_to project_merge_request_approver_path(@project, issuable, approver), data: { confirm: "Are you sure you want to remove approver #{approver.user.name}"}, method: :delete, class: "btn-xs btn btn-remove", title: 'Remove approver' do - else
= icon("sign-out") = link_to project_merge_request_approver_path(@project, issuable, approver), data: { confirm: "Are you sure you want to remove approver #{approver.user.name}"}, method: :delete, class: "btn-xs btn btn-remove", title: 'Remove approver' do
Remove = icon("sign-out")
Remove
- issuable.overall_approver_groups.each do |approver_group| - issuable.overall_approver_groups.each do |approver_group|
%li{ id: dom_id(approver_group.group), class: item_classes + ['approver-group'] } %li{ id: dom_id(approver_group.group), class: item_classes + ['approver-group'] }
Group: Group:
= link_to approver_group.group.name, approver_group.group = link_to approver_group.group.name, approver_group.group
.pull-right - if can_override_approvers
- if unsaved_approvers .pull-right
= 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 - if unsaved_approvers
= icon("sign-out") = 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
Remove = icon("sign-out")
- else Remove
= 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 - else
= icon("sign-out") = 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
Remove = icon("sign-out")
.help-block.suggested-approvers Remove
- if @suggested_approvers.any?
Suggested approvers: .col-sm-12
= raw @suggested_approvers.map { |approver| link_to sanitize(approver.name), "#", id: dom_id(approver) }.join(", ") .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:
= raw @suggested_approvers.map { |approver| link_to sanitize(approver.name), "#", id: dom_id(approver) }.join(", ")
- show_roles = local_assigns.fetch(:show_roles, true) - show_roles = local_assigns.fetch(:show_roles, true)
- show_controls = local_assigns.fetch(:show_controls, 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) - user = local_assigns.fetch(:user, member.user)
- source = member.source - source = member.source
- can_admin_member = can?(current_user, action_member_permission(:update, member), member) - can_admin_member = can?(current_user, action_member_permission(:update, member), member)
...@@ -11,46 +12,53 @@ ...@@ -11,46 +12,53 @@
%span.list-item-name %span.list-item-name
- if user - if user
= image_tag avatar_icon(user, 40), class: "avatar s40", alt: '' = image_tag avatar_icon(user, 40), class: "avatar s40", alt: ''
%strong .user-info
= link_to user.name, user_path(user) = link_to user.name, user_path(user), class: 'member'
%span.cgray= user.to_reference %span.cgray= user.to_reference
- if user == current_user - if user == current_user
%span.label.label-success.prepend-left-5 It's you %span.label.label-success.prepend-left-5 It's you
- if user.blocked? - if user.blocked?
%label.label.label-danger %label.label.label-danger
%strong Blocked %strong Blocked
- if source.instance_of?(Group) && source != @group - if source.instance_of?(Group) && source != @group
&middot; &middot;
= link_to source.full_name, source, class: "member-group-link" = link_to source.full_name, source, class: "member-group-link"
.hidden-xs.cgray .cgray
- if member.request? - if member.request?
Requested Requested
= time_ago_with_tooltip(member.requested_at) = time_ago_with_tooltip(member.requested_at)
- else - else
Joined #{time_ago_with_tooltip(member.created_at)} Joined #{time_ago_with_tooltip(member.created_at)}
- if member.expires? - if member.expires?
· ·
%span{ class: "#{"text-warning" if member.expires_soon?} has-tooltip", title: member.expires_at.to_time.in_time_zone.to_s(:medium) } %span{ class: "#{"text-warning" if member.expires_soon?} has-tooltip", title: member.expires_at.to_time.in_time_zone.to_s(:medium) }
Expires in #{distance_of_time_in_words_to_now(member.expires_at)} Expires in #{distance_of_time_in_words_to_now(member.expires_at)}
- else - else
= image_tag avatar_icon(member.invite_email, 40), class: "avatar s40", alt: '' = image_tag avatar_icon(member.invite_email, 40), class: "avatar s40", alt: ''
%strong= member.invite_email .user-info
.cgray .member= member.invite_email
Invited .cgray
- if member.created_by Invited
by - if member.created_by
= link_to member.created_by.name, user_path(member.created_by) by
= time_ago_with_tooltip(member.created_at) = link_to member.created_by.name, user_path(member.created_by)
= time_ago_with_tooltip(member.created_at)
- if show_roles - if show_roles
- current_resource = @project || @group - current_resource = @project || @group
.controls.member-controls .controls.member-controls
= render 'shared/members/ee/ldap_tag', can_override: can_override_member, visible: false = render 'shared/members/ee/ldap_tag', can_override: can_override_member, visible: false
- if show_controls && member.source == current_resource - 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) - 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| = form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f|
= f.hidden_field :access_level = f.hidden_field :access_level
...@@ -80,13 +88,17 @@ ...@@ -80,13 +88,17 @@
- if member.invite? && can?(current_user, action_member_permission(:admin, member), member.source) - if member.invite? && can?(current_user, action_member_permission(:admin, member), member.source)
= link_to 'Resend invite', polymorphic_path([:resend_invite, member]), = link_to 'Resend invite', polymorphic_path([:resend_invite, member]),
method: :post, 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 - 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, method: :post,
class: 'btn btn-success prepend-left-10', 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 can?(current_user, action_member_permission(:destroy, member), member)
- if current_user == user - if current_user == user
...@@ -101,9 +113,10 @@ ...@@ -101,9 +113,10 @@
data: { confirm: remove_member_message(member) }, data: { confirm: remove_member_message(member) },
class: 'btn btn-remove prepend-left-10', class: 'btn btn-remove prepend-left-10',
title: remove_member_title(member) do title: remove_member_title(member) do
%span.visible-xs-block %span{ class: ('visible-xs-block' unless force_mobile_view) }
Delete Delete
= icon('trash', class: 'hidden-xs') - 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 = render 'shared/members/ee/override_member_buttons', group: @group, member: member, user: user, action: :edit, can_override: can_override_member
- else - else
%span.member-access-text= member.human_access %span.member-access-text= member.human_access
......
- force_mobile_view = local_assigns.fetch(:force_mobile_view, false)
- if requesters.any? - if requesters.any?
.panel.panel-default.prepend-top-default .panel.panel-default.prepend-top-default{ class: ('panel-mobile' if force_mobile_view ) }
.panel-heading .panel-heading
Users requesting access to Users requesting access to
%strong= membership_source.name %strong= membership_source.name
%span.badge= requesters.size %span.badge= requesters.size
%ul.content-list %ul.content-list.members-list
= render partial: 'shared/members/member', collection: requesters, as: :member = 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 ...@@ -29,7 +29,8 @@ module Gitlab
#{config.root}/app/models/project_services #{config.root}/app/models/project_services
#{config.root}/app/workers/concerns #{config.root}/app/workers/concerns
#{config.root}/app/services/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") config.generators.templates.push("#{config.root}/generator_templates")
...@@ -175,8 +176,9 @@ module Gitlab ...@@ -175,8 +176,9 @@ module Gitlab
config.after_initialize do config.after_initialize do
Rails.application.reload_routes! Rails.application.reload_routes!
named_routes_set = Gitlab::Application.routes.named_routes
project_url_helpers = Module.new do 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') next unless name.include?('namespace_project')
define_method(name.sub('namespace_project', 'project')) do |project, *args| define_method(name.sub('namespace_project', 'project')) do |project, *args|
...@@ -185,6 +187,9 @@ module Gitlab ...@@ -185,6 +187,9 @@ module Gitlab
end end
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.include project_url_helpers
Gitlab::Routing.url_helpers.extend project_url_helpers Gitlab::Routing.url_helpers.extend project_url_helpers
......
...@@ -762,7 +762,10 @@ test: ...@@ -762,7 +762,10 @@ test:
client_id: 'YOUR_AUTH0_CLIENT_ID', client_id: 'YOUR_AUTH0_CLIENT_ID',
client_secret: 'YOUR_AUTH0_CLIENT_SECRET', client_secret: 'YOUR_AUTH0_CLIENT_SECRET',
namespace: 'YOUR_AUTH0_DOMAIN' } } 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: ldap:
enabled: false enabled: false
servers: 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 'constraints/project_url_constrainer'
require 'gitlab/routes/legacy_builds'
resources :projects, only: [:index, :new, :create] resources :projects, only: [:index, :new, :create]
...@@ -288,7 +287,7 @@ constraints(ProjectUrlConstrainer.new) do ...@@ -288,7 +287,7 @@ constraints(ProjectUrlConstrainer.new) do
end end
end end
Gitlab::Routes::LegacyBuilds.new(self).draw draw :legacy_builds
resources :hooks, only: [:index, :create, :edit, :update, :destroy], constraints: { id: /\d+/ } do resources :hooks, only: [:index, :create, :edit, :update, :destroy], constraints: { id: /\d+/ } do
member do member do
...@@ -439,7 +438,7 @@ constraints(ProjectUrlConstrainer.new) do ...@@ -439,7 +438,7 @@ constraints(ProjectUrlConstrainer.new) do
resource :ci_cd, only: [:show], controller: 'ci_cd' resource :ci_cd, only: [:show], controller: 'ci_cd'
resource :integrations, only: [:show] resource :integrations, only: [:show]
resource :slack, only: [:destroy] do resource :slack, only: [:destroy, :edit, :update] do
get :slack_auth get :slack_auth
end 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 @@ ...@@ -11,7 +11,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: 20170706121518) do ActiveRecord::Schema.define(version: 20170706151212) 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 "plpgsql" enable_extension "plpgsql"
...@@ -148,6 +148,7 @@ ActiveRecord::Schema.define(version: 20170706121518) do ...@@ -148,6 +148,7 @@ ActiveRecord::Schema.define(version: 20170706121518) do
t.string "slack_app_id" t.string "slack_app_id"
t.string "slack_app_secret" t.string "slack_app_secret"
t.string "slack_app_verification_token" t.string "slack_app_verification_token"
t.integer "performance_bar_allowed_group_id"
end end
create_table "approvals", force: :cascade do |t| create_table "approvals", force: :cascade do |t|
...@@ -884,6 +885,21 @@ ActiveRecord::Schema.define(version: 20170706121518) do ...@@ -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", ["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 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| create_table "merge_request_diff_files", id: false, force: :cascade do |t|
t.integer "merge_request_diff_id", null: false t.integer "merge_request_diff_id", null: false
t.integer "relative_order", null: false t.integer "relative_order", null: false
...@@ -1342,6 +1358,7 @@ ActiveRecord::Schema.define(version: 20170706121518) do ...@@ -1342,6 +1358,7 @@ ActiveRecord::Schema.define(version: 20170706121518) do
t.integer "cached_markdown_version" t.integer "cached_markdown_version"
t.datetime "last_repository_updated_at" t.datetime "last_repository_updated_at"
t.string "ci_config_path" t.string "ci_config_path"
t.boolean "disable_overriding_approvers_per_merge_request"
end end
add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree
...@@ -1544,7 +1561,7 @@ ActiveRecord::Schema.define(version: 20170706121518) do ...@@ -1544,7 +1561,7 @@ ActiveRecord::Schema.define(version: 20170706121518) do
add_index "slack_integrations", ["team_id", "alias"], name: "index_slack_integrations_on_team_id_and_alias", unique: true, using: :btree add_index "slack_integrations", ["team_id", "alias"], name: "index_slack_integrations_on_team_id_and_alias", unique: true, using: :btree
add_index "slack_integrations", ["service_id"], name: "index_slack_integrations_on_service_id", using: :btree add_index "slack_integrations", ["service_id"], name: "index_slack_integrations_on_service_id", using: :btree
create_table "snippets", force: :cascade do |t| create_table "snippets", force: :cascade do |t|
t.string "title" t.string "title"
t.text "content" t.text "content"
...@@ -1895,6 +1912,7 @@ ActiveRecord::Schema.define(version: 20170706121518) do ...@@ -1895,6 +1912,7 @@ ActiveRecord::Schema.define(version: 20170706121518) do
add_foreign_key "labels", "projects", name: "fk_7de4989a69", on_delete: :cascade 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", "boards", name: "fk_0d3f677137", on_delete: :cascade
add_foreign_key "lists", "labels", name: "fk_7a5553d60f", 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_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_diffs", "merge_requests", name: "fk_8483f3258f", on_delete: :cascade
add_foreign_key "merge_request_metrics", "ci_pipelines", column: "pipeline_id", 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. ...@@ -193,6 +193,7 @@ have access to GitLab administration tools and settings.
- [Operations](administration/operations.md): Keeping GitLab up and running. - [Operations](administration/operations.md): Keeping GitLab up and running.
- [Polling](administration/polling.md): Configure how often the GitLab UI polls for updates. - [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. - [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 ### 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: ...@@ -17,6 +17,7 @@ following locations:
- [Deploy Keys](deploy_keys.md) - [Deploy Keys](deploy_keys.md)
- [Environments](environments.md) - [Environments](environments.md)
- [Events](events.md) - [Events](events.md)
- [Feature flags](features.md)
- [Gitignores templates](templates/gitignores.md) - [Gitignores templates](templates/gitignores.md)
- [GitLab CI Config templates](templates/gitlab_ci_ymls.md) - [GitLab CI Config templates](templates/gitlab_ci_ymls.md)
- [Groups](groups.md) - [Groups](groups.md)
......
# Features API # Features flags API
All methods require administrator authorization. All methods require administrator authorization.
...@@ -61,7 +61,8 @@ POST /features/:name ...@@ -61,7 +61,8 @@ POST /features/:name
| `feature_group` | string | no | A Feature group name | | `feature_group` | string | no | A Feature group name |
| `user` | string | no | A GitLab username | | `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 ```bash
curl --data "value=30" --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/features/new_library 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 ...@@ -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 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 ## Single user
Get a single user. Get a single user.
......
...@@ -56,6 +56,7 @@ ...@@ -56,6 +56,7 @@
- [Single Table Inheritance](single_table_inheritance.md) - [Single Table Inheritance](single_table_inheritance.md)
- [Background Migrations](background_migrations.md) - [Background Migrations](background_migrations.md)
- [Storing SHA1 Hashes As Binary](sha1_as_binary.md) - [Storing SHA1 Hashes As Binary](sha1_as_binary.md)
- [Iterating Tables In Batches](iterating_tables_in_batches.md)
## i18n ## i18n
......
...@@ -3,5 +3,19 @@ ...@@ -3,5 +3,19 @@
Starting from GitLab 9.3 we support feature flags via Starting from GitLab 9.3 we support feature flags via
[Flipper](https://github.com/jnunemaker/flipper/). You should use the `Feature` [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 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 flags.
[admin API](../api/features.md).
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 @@ ...@@ -2,7 +2,7 @@
## Repository size limit ## 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 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 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 ...@@ -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, repositories' size. This limit can be set globally, per group, or per project,
with per project limits taking the highest priority. 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 Only a GitLab administrator can set those limits. Setting the limit to `0` means
there are no restrictions. there are no restrictions.
...@@ -28,5 +40,9 @@ allowed repository size. ...@@ -28,5 +40,9 @@ allowed repository size.
For more manually purging the files, read the docs on For more manually purging the files, read the docs on
[reducing the repository size using Git][repo-size]. [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 [ee-740]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/740
[repo-size]: ../../project/repository/reducing_the_repo_size_using_git.md [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 ...@@ -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. 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 ### Reset approvals on push
With this setting turned on, approvals are reset when a new push With this setting turned on, approvals are reset when a new push
......
...@@ -14,14 +14,12 @@ module API ...@@ -14,14 +14,12 @@ module API
end end
end end
def gate_target(params) def gate_targets(params)
if params[:feature_group] targets = []
Feature.group(params[:feature_group]) targets << Feature.group(params[:feature_group]) if params[:feature_group]
elsif params[:user] targets << User.find_by_username(params[:user]) if params[:user]
User.find_by_username(params[:user])
else targets
gate_value(params)
end
end end
end end
...@@ -42,18 +40,25 @@ module API ...@@ -42,18 +40,25 @@ module API
requires :value, type: String, desc: '`true` or `false` to enable/disable, an integer for percentage of time' 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 :feature_group, type: String, desc: 'A Feature group name'
optional :user, type: String, desc: 'A GitLab username' optional :user, type: String, desc: 'A GitLab username'
mutually_exclusive :feature_group, :user
end end
post ':name' do post ':name' do
feature = Feature.get(params[:name]) feature = Feature.get(params[:name])
target = gate_target(params) targets = gate_targets(params)
value = gate_value(params) value = gate_value(params)
case value case value
when true when true
feature.enable(target) if targets.present?
targets.each { |target| feature.enable(target) }
else
feature.enable
end
when false when false
feature.disable(target) if targets.present?
targets.each { |target| feature.disable(target) }
else
feature.disable
end
else else
feature.enable_percentage_of_time(value) feature.enable_percentage_of_time(value)
end end
......
...@@ -51,6 +51,8 @@ module API ...@@ -51,6 +51,8 @@ module API
optional :active, type: Boolean, default: false, desc: 'Filters only active users' optional :active, type: Boolean, default: false, desc: 'Filters only active users'
optional :external, type: Boolean, default: false, desc: 'Filters only external users' optional :external, type: Boolean, default: false, desc: 'Filters only external users'
optional :blocked, type: Boolean, default: false, desc: 'Filters only blocked 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 all_or_none_of :extern_uid, :provider
# EE # EE
...@@ -61,6 +63,10 @@ module API ...@@ -61,6 +63,10 @@ module API
get do get do
authenticated_as_admin! if params[:external].present? || (params[:extern_uid].present? && params[:provider].present?) 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 users = UsersFinder.new(current_user, params).execute
authorized = can?(current_user, :read_users_list) authorized = can?(current_user, :read_users_list)
......
...@@ -30,6 +30,8 @@ module Banzai ...@@ -30,6 +30,8 @@ module Banzai
attributes = attributes.reject { |_, v| v.nil? } attributes = attributes.reject { |_, v| v.nil? }
attributes[:reference_type] ||= self.class.reference_type attributes[:reference_type] ||= self.class.reference_type
attributes[:container] ||= 'body'
attributes[:placement] ||= 'bottom'
attributes.delete(:original) if context[:no_original_data] attributes.delete(:original) if context[:no_original_data]
attributes.map do |key, value| attributes.map do |key, value|
%Q(data-#{key.to_s.dasherize}="#{escape_once(value)}") %Q(data-#{key.to_s.dasherize}="#{escape_once(value)}")
......
...@@ -13,6 +13,10 @@ module Gitlab ...@@ -13,6 +13,10 @@ module Gitlab
MergeRequestDiff.arel_table MergeRequestDiff.arel_table
end end
def mr_diff_commits_table
MergeRequestDiffCommit.arel_table
end
def mr_closing_issues_table def mr_closing_issues_table
MergeRequestsClosingIssues.arel_table MergeRequestsClosingIssues.arel_table
end end
......
...@@ -2,40 +2,59 @@ module Gitlab ...@@ -2,40 +2,59 @@ module Gitlab
module CycleAnalytics module CycleAnalytics
class PlanEventFetcher < BaseEventFetcher class PlanEventFetcher < BaseEventFetcher
def initialize(*args) 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]] issue_metrics_table[:first_mentioned_in_commit_at]]
super(*args) super(*args)
end end
def events_query 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 super
end end
private 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) def serialize(event)
st_commit = first_time_reference_commit(event.delete('commits'), event) commit = first_time_reference_commit(event)
return unless st_commit return unless commit
serialize_commit(event, st_commit, query) serialize_commit(event, commit, query)
end end
def first_time_reference_commit(commits, event) def first_time_reference_commit(event)
return nil unless event && merge_request_diff_commits
commits =
if event['st_commits'].present?
YAML.load(event['st_commits'])
else
merge_request_diff_commits[event['id'].to_i]
end
return nil if commits.blank? 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'] 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 commit[:committed_date].to_i == DateTime.parse(event['first_mentioned_in_commit_at'].to_s).to_i
end end
end end
def serialize_commit(event, st_commit, query) def serialize_commit(event, commit, query)
commit = Commit.new(Gitlab::Git::Commit.new(st_commit), @project) commit = Commit.new(Gitlab::Git::Commit.new(commit.to_hash), @project)
AnalyticsCommitSerializer.new(project: @project, total_time: event['total_time']).represent(commit) AnalyticsCommitSerializer.new(project: @project, total_time: event['total_time']).represent(commit)
end end
......
...@@ -27,6 +27,7 @@ project_tree: ...@@ -27,6 +27,7 @@ project_tree:
- :author - :author
- :events - :events
- merge_request_diff: - merge_request_diff:
- :merge_request_diff_commits
- :merge_request_diff_files - :merge_request_diff_files
- :events - :events
- :timelogs - :timelogs
......
...@@ -34,7 +34,7 @@ module Gitlab ...@@ -34,7 +34,7 @@ module Gitlab
write_csv do |csv| write_csv do |csv|
ActiveRecord::Base.transaction do 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 = relation.pluck(:id, :encrypted_otp_secret, :encrypted_otp_secret_iv, :encrypted_otp_secret_salt)
rows.each do |row| rows.each do |row|
user = %i[id ciphertext iv salt].zip(row).to_h 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