Commit 1b27c457 authored by Luke Bennett's avatar Luke Bennett

Merge branch 'master' into '41416-making-instance-wide-data-tools-more-accessible'

# Conflicts:
#   app/models/application_setting.rb
#   lib/api/settings.rb
parents 9eb87018 1a956035
...@@ -2,6 +2,10 @@ ...@@ -2,6 +2,10 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 11.1.4 (2018-07-30)
- No changes.
## 11.1.3 (2018-07-27) ## 11.1.3 (2018-07-27)
### Fixed (8 changes, 1 of them is from the community) ### Fixed (8 changes, 1 of them is from the community)
......
...@@ -306,7 +306,7 @@ group :metrics do ...@@ -306,7 +306,7 @@ group :metrics do
gem 'influxdb', '~> 0.2', require: false gem 'influxdb', '~> 0.2', require: false
# Prometheus # Prometheus
gem 'prometheus-client-mmap', '~> 0.9.3' gem 'prometheus-client-mmap', '~> 0.9.4'
gem 'raindrops', '~> 0.18' gem 'raindrops', '~> 0.18'
end end
......
...@@ -635,7 +635,7 @@ GEM ...@@ -635,7 +635,7 @@ GEM
parser parser
unparser unparser
procto (0.0.3) procto (0.0.3)
prometheus-client-mmap (0.9.3) prometheus-client-mmap (0.9.4)
pry (0.10.4) pry (0.10.4)
coderay (~> 1.1.0) coderay (~> 1.1.0)
method_source (~> 0.8.1) method_source (~> 0.8.1)
...@@ -1126,7 +1126,7 @@ DEPENDENCIES ...@@ -1126,7 +1126,7 @@ DEPENDENCIES
peek-sidekiq (~> 1.0.3) peek-sidekiq (~> 1.0.3)
pg (~> 0.18.2) pg (~> 0.18.2)
premailer-rails (~> 1.9.7) premailer-rails (~> 1.9.7)
prometheus-client-mmap (~> 0.9.3) prometheus-client-mmap (~> 0.9.4)
pry-byebug (~> 3.4.1) pry-byebug (~> 3.4.1)
pry-rails (~> 0.3.4) pry-rails (~> 0.3.4)
rack-attack (~> 4.4.1) rack-attack (~> 4.4.1)
......
...@@ -639,7 +639,7 @@ GEM ...@@ -639,7 +639,7 @@ GEM
parser parser
unparser unparser
procto (0.0.3) procto (0.0.3)
prometheus-client-mmap (0.9.3) prometheus-client-mmap (0.9.4)
pry (0.10.4) pry (0.10.4)
coderay (~> 1.1.0) coderay (~> 1.1.0)
method_source (~> 0.8.1) method_source (~> 0.8.1)
...@@ -1136,7 +1136,7 @@ DEPENDENCIES ...@@ -1136,7 +1136,7 @@ DEPENDENCIES
peek-sidekiq (~> 1.0.3) peek-sidekiq (~> 1.0.3)
pg (~> 0.18.2) pg (~> 0.18.2)
premailer-rails (~> 1.9.7) premailer-rails (~> 1.9.7)
prometheus-client-mmap (~> 0.9.3) prometheus-client-mmap (~> 0.9.4)
pry-byebug (~> 3.4.1) pry-byebug (~> 3.4.1)
pry-rails (~> 0.3.4) pry-rails (~> 0.3.4)
rack-attack (~> 4.4.1) rack-attack (~> 4.4.1)
......
...@@ -2,6 +2,9 @@ ...@@ -2,6 +2,9 @@
import Sortable from 'sortablejs'; import Sortable from 'sortablejs';
import Vue from 'vue'; import Vue from 'vue';
import { n__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import Tooltip from '~/vue_shared/directives/tooltip';
import AccessorUtilities from '../../lib/utils/accessor'; import AccessorUtilities from '../../lib/utils/accessor';
import boardList from './board_list.vue'; import boardList from './board_list.vue';
import BoardBlankState from './board_blank_state.vue'; import BoardBlankState from './board_blank_state.vue';
...@@ -17,6 +20,10 @@ gl.issueBoards.Board = Vue.extend({ ...@@ -17,6 +20,10 @@ gl.issueBoards.Board = Vue.extend({
boardList, boardList,
'board-delete': gl.issueBoards.BoardDelete, 'board-delete': gl.issueBoards.BoardDelete,
BoardBlankState, BoardBlankState,
Icon,
},
directives: {
Tooltip,
}, },
props: { props: {
list: { list: {
...@@ -46,6 +53,12 @@ gl.issueBoards.Board = Vue.extend({ ...@@ -46,6 +53,12 @@ gl.issueBoards.Board = Vue.extend({
filter: Store.filter, filter: Store.filter,
}; };
}, },
computed: {
counterTooltip() {
const { issuesSize } = this.list;
return `${n__('%d issue', '%d issues', issuesSize)}`;
},
},
watch: { watch: {
filter: { filter: {
handler() { handler() {
......
...@@ -136,6 +136,8 @@ class List { ...@@ -136,6 +136,8 @@ class List {
} }
this.createIssues(data.issues); this.createIssues(data.issues);
return data;
}); });
} }
......
...@@ -125,11 +125,17 @@ gl.issueBoards.BoardsStore = { ...@@ -125,11 +125,17 @@ gl.issueBoards.BoardsStore = {
} else if (listTo.type === 'backlog' && listFrom.type === 'assignee') { } else if (listTo.type === 'backlog' && listFrom.type === 'assignee') {
issue.removeAssignee(listFrom.assignee); issue.removeAssignee(listFrom.assignee);
listFrom.removeIssue(issue); listFrom.removeIssue(issue);
} else if ((listTo.type !== 'label' && listFrom.type === 'assignee') || } else if (this.shouldRemoveIssue(listFrom, listTo)) {
(listTo.type !== 'assignee' && listFrom.type === 'label')) {
listFrom.removeIssue(issue); listFrom.removeIssue(issue);
} }
}, },
shouldRemoveIssue(listFrom, listTo) {
return (
(listTo.type !== 'label' && listFrom.type === 'assignee') ||
(listTo.type !== 'assignee' && listFrom.type === 'label') ||
(listFrom.type === 'backlog')
);
},
moveIssueInList (list, issue, oldIndex, newIndex, idArray) { moveIssueInList (list, issue, oldIndex, newIndex, idArray) {
const beforeId = parseInt(idArray[newIndex - 1], 10) || null; const beforeId = parseInt(idArray[newIndex - 1], 10) || null;
const afterId = parseInt(idArray[newIndex + 1], 10) || null; const afterId = parseInt(idArray[newIndex + 1], 10) || null;
......
...@@ -50,7 +50,7 @@ export default { ...@@ -50,7 +50,7 @@ export default {
}; };
}, },
computed: { computed: {
...mapGetters('diffs', ['diffHasExpandedDiscussions']), ...mapGetters('diffs', ['diffHasExpandedDiscussions', 'diffHasDiscussions']),
hasExpandedDiscussions() { hasExpandedDiscussions() {
return this.diffHasExpandedDiscussions(this.diffFile); return this.diffHasExpandedDiscussions(this.diffFile);
}, },
...@@ -108,6 +108,9 @@ export default { ...@@ -108,6 +108,9 @@ export default {
false, false,
); );
}, },
gfmCopyText() {
return `\`${this.diffFile.filePath}\``;
},
}, },
methods: { methods: {
...mapActions('diffs', ['toggleFileDiscussions']), ...mapActions('diffs', ['toggleFileDiscussions']),
...@@ -191,6 +194,7 @@ export default { ...@@ -191,6 +194,7 @@ export default {
<clipboard-button <clipboard-button
:title="__('Copy file path to clipboard')" :title="__('Copy file path to clipboard')"
:text="diffFile.filePath" :text="diffFile.filePath"
:gfm="gfmCopyText"
css-class="btn-default btn-transparent btn-clipboard" css-class="btn-default btn-transparent btn-clipboard"
/> />
...@@ -217,6 +221,7 @@ export default { ...@@ -217,6 +221,7 @@ export default {
v-if="diffFile.blob && diffFile.blob.readableText" v-if="diffFile.blob && diffFile.blob.readableText"
> >
<button <button
:disabled="!diffHasDiscussions(diffFile)"
:class="{ active: hasExpandedDiscussions }" :class="{ active: hasExpandedDiscussions }"
:title="s__('MergeRequests|Toggle comments for this file')" :title="s__('MergeRequests|Toggle comments for this file')"
class="js-btn-vue-toggle-comments btn" class="js-btn-vue-toggle-comments btn"
......
...@@ -47,6 +47,14 @@ export const diffHasExpandedDiscussions = (state, getters) => diff => { ...@@ -47,6 +47,14 @@ export const diffHasExpandedDiscussions = (state, getters) => diff => {
); );
}; };
/**
* Checks if the diff has any discussion
* @param {Boolean} diff
* @returns {Boolean}
*/
export const diffHasDiscussions = (state, getters) => diff =>
getters.getDiffFileDiscussions(diff).length > 0;
/** /**
* Returns an array with the discussions of the given diff * Returns an array with the discussions of the given diff
* @param {Object} diff * @param {Object} diff
......
<script> <script>
import Mousetrap from 'mousetrap'; import Mousetrap from 'mousetrap';
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapActions, mapState, mapGetters } from 'vuex';
import { __ } from '~/locale';
import NewModal from './new_dropdown/modal.vue'; import NewModal from './new_dropdown/modal.vue';
import IdeSidebar from './ide_side_bar.vue'; import IdeSidebar from './ide_side_bar.vue';
import RepoTabs from './repo_tabs.vue'; import RepoTabs from './repo_tabs.vue';
...@@ -25,7 +26,6 @@ export default { ...@@ -25,7 +26,6 @@ export default {
}, },
computed: { computed: {
...mapState([ ...mapState([
'changedFiles',
'openFiles', 'openFiles',
'viewer', 'viewer',
'currentMergeRequestId', 'currentMergeRequestId',
...@@ -34,18 +34,10 @@ export default { ...@@ -34,18 +34,10 @@ export default {
'currentProjectId', 'currentProjectId',
'errorMessage', 'errorMessage',
]), ]),
...mapGetters(['activeFile', 'hasChanges']), ...mapGetters(['activeFile', 'hasChanges', 'someUncommitedChanges']),
}, },
mounted() { mounted() {
const returnValue = 'Are you sure you want to lose unsaved changes?'; window.onbeforeunload = e => this.onBeforeUnload(e);
window.onbeforeunload = e => {
if (!this.changedFiles.length) return undefined;
Object.assign(e, {
returnValue,
});
return returnValue;
};
Mousetrap.bind(['t', 'command+p', 'ctrl+p'], e => { Mousetrap.bind(['t', 'command+p', 'ctrl+p'], e => {
if (e.preventDefault) { if (e.preventDefault) {
...@@ -59,6 +51,16 @@ export default { ...@@ -59,6 +51,16 @@ export default {
}, },
methods: { methods: {
...mapActions(['toggleFileFinder']), ...mapActions(['toggleFileFinder']),
onBeforeUnload(e = {}) {
const returnValue = __('Are you sure you want to lose unsaved changes?');
if (!this.someUncommitedChanges) return undefined;
Object.assign(e, {
returnValue,
});
return returnValue;
},
mousetrapStopCallback(e, el, combo) { mousetrapStopCallback(e, el, combo) {
if ( if (
(combo === 't' && el.classList.contains('dropdown-input-field')) || (combo === 't' && el.classList.contains('dropdown-input-field')) ||
......
...@@ -2,11 +2,34 @@ ...@@ -2,11 +2,34 @@
* exports HTTP status codes * exports HTTP status codes
*/ */
export default { const httpStatusCodes = {
ABORTED: 0, ABORTED: 0,
NO_CONTENT: 204,
OK: 200, OK: 200,
CREATED: 201,
ACCEPTED: 202,
NON_AUTHORITATIVE_INFORMATION: 203,
NO_CONTENT: 204,
RESET_CONTENT: 205,
PARTIAL_CONTENT: 206,
MULTI_STATUS: 207,
ALREADY_REPORTED: 208,
IM_USED: 226,
MULTIPLE_CHOICES: 300, MULTIPLE_CHOICES: 300,
BAD_REQUEST: 400, BAD_REQUEST: 400,
NOT_FOUND: 404, NOT_FOUND: 404,
}; };
export const successCodes = [
httpStatusCodes.OK,
httpStatusCodes.CREATED,
httpStatusCodes.ACCEPTED,
httpStatusCodes.NON_AUTHORITATIVE_INFORMATION,
httpStatusCodes.NO_CONTENT,
httpStatusCodes.RESET_CONTENT,
httpStatusCodes.PARTIAL_CONTENT,
httpStatusCodes.MULTI_STATUS,
httpStatusCodes.ALREADY_REPORTED,
httpStatusCodes.IM_USED,
];
export default httpStatusCodes;
import httpStatusCodes from './http_status'; import httpStatusCodes, { successCodes } from './http_status';
import { normalizeHeaders } from './common_utils'; import { normalizeHeaders } from './common_utils';
/** /**
...@@ -62,7 +62,7 @@ export default class Poll { ...@@ -62,7 +62,7 @@ export default class Poll {
checkConditions(response) { checkConditions(response) {
const headers = normalizeHeaders(response.headers); const headers = normalizeHeaders(response.headers);
const pollInterval = parseInt(headers[this.intervalHeader], 10); const pollInterval = parseInt(headers[this.intervalHeader], 10);
if (pollInterval > 0 && response.status === httpStatusCodes.OK && this.canPoll) { if (pollInterval > 0 && successCodes.indexOf(response.status) !== -1 && this.canPoll) {
clearTimeout(this.timeoutID); clearTimeout(this.timeoutID);
this.timeoutID = setTimeout(() => { this.timeoutID = setTimeout(() => {
this.makeRequest(); this.makeRequest();
......
...@@ -31,6 +31,11 @@ export default { ...@@ -31,6 +31,11 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
gfm: {
type: String,
required: false,
default: null,
},
title: { title: {
type: String, type: String,
required: true, required: true,
...@@ -51,6 +56,14 @@ export default { ...@@ -51,6 +56,14 @@ export default {
default: 'btn-default', default: 'btn-default',
}, },
}, },
computed: {
clipboardText() {
if (this.gfm !== null) {
return JSON.stringify({ text: this.text, gfm: this.gfm });
}
return this.text;
},
},
}; };
</script> </script>
...@@ -59,7 +72,7 @@ export default { ...@@ -59,7 +72,7 @@ export default {
v-tooltip v-tooltip
:class="cssClass" :class="cssClass"
:title="title" :title="title"
:data-clipboard-text="text" :data-clipboard-text="clipboardText"
:data-container="tooltipContainer" :data-container="tooltipContainer"
:data-placement="tooltipPlacement" :data-placement="tooltipPlacement"
type="button" type="button"
......
...@@ -444,3 +444,5 @@ textarea { ...@@ -444,3 +444,5 @@ textarea {
color: $placeholder-text-color; color: $placeholder-text-color;
} }
} }
.lh-100 { line-height: 1; }
...@@ -205,7 +205,7 @@ ...@@ -205,7 +205,7 @@
.board-title { .board-title {
margin: 0; margin: 0;
padding: 12px $gl-padding; padding: $gl-padding-8 $gl-padding;
font-size: 1em; font-size: 1em;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
display: flex; display: flex;
......
.issue-count-badge { .issue-count-badge {
display: inline-flex; display: inline-flex;
align-items: stretch;
height: 24px;
}
.issue-count-badge-count {
display: flex;
align-items: center;
padding-right: 10px;
padding-left: 10px;
border: 1px solid $border-color;
border-radius: $border-radius-base; border-radius: $border-radius-base;
line-height: 1; border: 1px solid $border-color;
padding: 5px $gl-padding-8;
&.has-btn {
border-right: 0;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
} }
.issue-count-badge-add-button { .issue-count-badge-count {
display: flex; display: inline-flex;
align-items: center; align-items: center;
border: 1px solid $border-color;
border-radius: 0 $border-radius-base $border-radius-base 0;
line-height: 1;
} }
...@@ -2,7 +2,7 @@ class Admin::JobsController < Admin::ApplicationController ...@@ -2,7 +2,7 @@ class Admin::JobsController < Admin::ApplicationController
def index def index
@scope = params[:scope] @scope = params[:scope]
@all_builds = Ci::Build @all_builds = Ci::Build
@builds = @all_builds.order('created_at DESC') @builds = @all_builds.order('id DESC')
@builds = @builds =
case @scope case @scope
when 'pending' when 'pending'
......
...@@ -397,7 +397,7 @@ class ApplicationController < ActionController::Base ...@@ -397,7 +397,7 @@ class ApplicationController < ActionController::Base
# actually stored in the session and a token is needed # actually stored in the session and a token is needed
# for every request. If you want the token to work as a # for every request. If you want the token to work as a
# sign in token, you can simply remove store: false. # sign in token, you can simply remove store: false.
sign_in user, store: false sign_in(user, store: false, message: :sessionless_sign_in)
end end
end end
......
...@@ -12,8 +12,9 @@ module Boards ...@@ -12,8 +12,9 @@ module Boards
skip_before_action :authenticate_user!, only: [:index] skip_before_action :authenticate_user!, only: [:index]
def index def index
issues = Boards::Issues::ListService.new(board_parent, current_user, filter_params).execute list_service = Boards::Issues::ListService.new(board_parent, current_user, filter_params)
issues = issues.page(params[:page]).per(params[:per] || 20) issues = list_service.execute
issues = issues.page(params[:page]).per(params[:per] || 20).without_count
make_sure_position_is_set(issues) if Gitlab::Database.read_write? make_sure_position_is_set(issues) if Gitlab::Database.read_write?
issues = issues.preload(:project, issues = issues.preload(:project,
:milestone, :milestone,
...@@ -22,10 +23,7 @@ module Boards ...@@ -22,10 +23,7 @@ module Boards
notes: [:award_emoji, :author] notes: [:award_emoji, :author]
) )
render json: { render_issues(issues, list_service.metadata)
issues: serialize_as_json(issues),
size: issues.total_count
}
end end
def create def create
...@@ -51,6 +49,13 @@ module Boards ...@@ -51,6 +49,13 @@ module Boards
private private
def render_issues(issues, metadata)
data = { issues: serialize_as_json(issues) }
data.merge!(metadata)
render json: data
end
def make_sure_position_is_set(issues) def make_sure_position_is_set(issues)
issues.each do |issue| issues.each do |issue|
issue.move_to_end && issue.save unless issue.relative_position issue.move_to_end && issue.save unless issue.relative_position
......
...@@ -60,7 +60,7 @@ module AuthenticatesWithTwoFactor ...@@ -60,7 +60,7 @@ module AuthenticatesWithTwoFactor
remember_me(user) if user_params[:remember_me] == '1' remember_me(user) if user_params[:remember_me] == '1'
user.save! user.save!
sign_in(user) sign_in(user, message: :two_factor_authenticated)
else else
user.increment_failed_attempts! user.increment_failed_attempts!
Gitlab::AppLogger.info("Failed Login: user=#{user.username} ip=#{request.remote_ip} method=OTP") Gitlab::AppLogger.info("Failed Login: user=#{user.username} ip=#{request.remote_ip} method=OTP")
...@@ -77,7 +77,7 @@ module AuthenticatesWithTwoFactor ...@@ -77,7 +77,7 @@ module AuthenticatesWithTwoFactor
session.delete(:challenge) session.delete(:challenge)
remember_me(user) if user_params[:remember_me] == '1' remember_me(user) if user_params[:remember_me] == '1'
sign_in(user) sign_in(user, message: :two_factor_authenticated)
else else
user.increment_failed_attempts! user.increment_failed_attempts!
Gitlab::AppLogger.info("Failed Login: user=#{user.username} ip=#{request.remote_ip} method=U2F") Gitlab::AppLogger.info("Failed Login: user=#{user.username} ip=#{request.remote_ip} method=U2F")
......
...@@ -13,7 +13,9 @@ class Projects::MirrorsController < Projects::ApplicationController ...@@ -13,7 +13,9 @@ class Projects::MirrorsController < Projects::ApplicationController
end end
def update def update
if project.update(mirror_params) result = ::Projects::UpdateService.new(project, current_user, mirror_params).execute
if result[:status] == :success
flash[:notice] = 'Mirroring settings were successfully updated.' flash[:notice] = 'Mirroring settings were successfully updated.'
else else
flash[:alert] = project.errors.full_messages.join(', ').html_safe flash[:alert] = project.errors.full_messages.join(', ').html_safe
......
class Projects::WikisController < Projects::ApplicationController class Projects::WikisController < Projects::ApplicationController
include PreviewMarkdown include PreviewMarkdown
include Gitlab::Utils::StrongMemoize
before_action :authorize_read_wiki! before_action :authorize_read_wiki!
before_action :authorize_create_wiki!, only: [:edit, :create, :history] before_action :authorize_create_wiki!, only: [:edit, :create, :history]
before_action :authorize_admin_wiki!, only: :destroy before_action :authorize_admin_wiki!, only: :destroy
before_action :load_project_wiki before_action :load_project_wiki
before_action :load_page, only: [:show, :edit, :update, :history, :destroy]
before_action :valid_encoding?, only: [:show, :edit, :update], if: :load_page
before_action only: [:edit, :update], unless: :valid_encoding? do
redirect_to(project_wiki_path(@project, @page))
end
def pages def pages
@wiki_pages = Kaminari.paginate_array(@project_wiki.pages).page(params[:page]) @wiki_pages = Kaminari.paginate_array(@project_wiki.pages).page(params[:page])
...@@ -12,11 +18,11 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -12,11 +18,11 @@ class Projects::WikisController < Projects::ApplicationController
end end
def show def show
@page = @project_wiki.find_page(params[:id], params[:version_id])
view_param = @project_wiki.empty? ? params[:view] : 'create' view_param = @project_wiki.empty? ? params[:view] : 'create'
if @page if @page
set_encoding_error unless valid_encoding?
render 'show' render 'show'
elsif file = @project_wiki.find_file(params[:id], params[:version_id]) elsif file = @project_wiki.find_file(params[:id], params[:version_id])
response.headers['Content-Security-Policy'] = "default-src 'none'" response.headers['Content-Security-Policy'] = "default-src 'none'"
...@@ -38,13 +44,11 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -38,13 +44,11 @@ class Projects::WikisController < Projects::ApplicationController
end end
def edit def edit
@page = @project_wiki.find_page(params[:id])
end end
def update def update
return render('empty') unless can?(current_user, :create_wiki, @project) return render('empty') unless can?(current_user, :create_wiki, @project)
@page = @project_wiki.find_page(params[:id])
@page = WikiPages::UpdateService.new(@project, current_user, wiki_params).execute(@page) @page = WikiPages::UpdateService.new(@project, current_user, wiki_params).execute(@page)
if @page.valid? if @page.valid?
...@@ -79,8 +83,6 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -79,8 +83,6 @@ class Projects::WikisController < Projects::ApplicationController
end end
def history def history
@page = @project_wiki.find_page(params[:id])
if @page if @page
@page_versions = Kaminari.paginate_array(@page.versions(page: params[:page].to_i), @page_versions = Kaminari.paginate_array(@page.versions(page: params[:page].to_i),
total_count: @page.count_versions) total_count: @page.count_versions)
...@@ -94,8 +96,6 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -94,8 +96,6 @@ class Projects::WikisController < Projects::ApplicationController
end end
def destroy def destroy
@page = @project_wiki.find_page(params[:id])
WikiPages::DestroyService.new(@project, current_user).execute(@page) WikiPages::DestroyService.new(@project, current_user).execute(@page)
redirect_to project_wiki_path(@project, :home), redirect_to project_wiki_path(@project, :home),
...@@ -141,4 +141,25 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -141,4 +141,25 @@ class Projects::WikisController < Projects::ApplicationController
page.update_attributes(args) # rubocop:disable Rails/ActiveRecordAliases page.update_attributes(args) # rubocop:disable Rails/ActiveRecordAliases
end end
end end
def load_page
@page ||= @project_wiki.find_page(*page_params)
end
def page_params
keys = [:id]
keys << :version_id if params[:action] == 'show'
params.values_at(*keys)
end
def valid_encoding?
strong_memoize(:valid_encoding) do
@page.content.encoding == Encoding::UTF_8
end
end
def set_encoding_error
flash.now[:notice] = "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository."
end
end end
...@@ -89,6 +89,14 @@ class SessionsController < Devise::SessionsController ...@@ -89,6 +89,14 @@ class SessionsController < Devise::SessionsController
).increment ).increment
end end
##
# We do have some duplication between lib/gitlab/auth/activity.rb here, but
# leaving this method here because of backwards compatibility.
#
def login_counter
@login_counter ||= Gitlab::Metrics.counter(:user_session_logins_total, 'User sign in count')
end
def log_failed_login def log_failed_login
Gitlab::AppLogger.info("Failed Login: username=#{user_params[:login]} ip=#{request.remote_ip}") Gitlab::AppLogger.info("Failed Login: username=#{user_params[:login]} ip=#{request.remote_ip}")
end end
...@@ -97,10 +105,6 @@ class SessionsController < Devise::SessionsController ...@@ -97,10 +105,6 @@ class SessionsController < Devise::SessionsController
(options = env["warden.options"]) && options[:action] == "unauthenticated" (options = env["warden.options"]) && options[:action] == "unauthenticated"
end end
def login_counter
@login_counter ||= Gitlab::Metrics.counter(:user_session_logins_total, 'User sign in count')
end
# Handle an "initial setup" state, where there's only one user, it's an admin, # Handle an "initial setup" state, where there's only one user, it's an admin,
# and they require a password change. # and they require a password change.
def check_initial_setup def check_initial_setup
......
...@@ -130,7 +130,7 @@ class IssuableFinder ...@@ -130,7 +130,7 @@ class IssuableFinder
counts[:all] = counts.values.sum counts[:all] = counts.values.sum
counts counts.with_indifferent_access
end end
def group def group
......
...@@ -148,6 +148,7 @@ module ApplicationSettingsHelper ...@@ -148,6 +148,7 @@ module ApplicationSettingsHelper
:after_sign_up_text, :after_sign_up_text,
:akismet_api_key, :akismet_api_key,
:akismet_enabled, :akismet_enabled,
:allow_local_requests_from_hooks_and_services,
:authorized_keys_enabled, :authorized_keys_enabled,
:auto_devops_enabled, :auto_devops_enabled,
:auto_devops_domain, :auto_devops_domain,
...@@ -174,6 +175,7 @@ module ApplicationSettingsHelper ...@@ -174,6 +175,7 @@ module ApplicationSettingsHelper
:ed25519_key_restriction, :ed25519_key_restriction,
:email_author_in_body, :email_author_in_body,
:enabled_git_access_protocol, :enabled_git_access_protocol,
:enforce_terms,
:gitaly_timeout_default, :gitaly_timeout_default,
:gitaly_timeout_medium, :gitaly_timeout_medium,
:gitaly_timeout_fast, :gitaly_timeout_fast,
...@@ -182,6 +184,7 @@ module ApplicationSettingsHelper ...@@ -182,6 +184,7 @@ module ApplicationSettingsHelper
:help_page_hide_commercial_content, :help_page_hide_commercial_content,
:help_page_support_url, :help_page_support_url,
:help_page_text, :help_page_text,
:hide_third_party_offers,
:home_page_url, :home_page_url,
:housekeeping_bitmaps_enabled, :housekeeping_bitmaps_enabled,
:housekeeping_enabled, :housekeeping_enabled,
...@@ -203,6 +206,7 @@ module ApplicationSettingsHelper ...@@ -203,6 +206,7 @@ module ApplicationSettingsHelper
:metrics_port, :metrics_port,
:metrics_sample_interval, :metrics_sample_interval,
:metrics_timeout, :metrics_timeout,
:mirror_available,
:pages_domain_verification_enabled, :pages_domain_verification_enabled,
:password_authentication_enabled_for_web, :password_authentication_enabled_for_web,
:password_authentication_enabled_for_git, :password_authentication_enabled_for_git,
...@@ -233,6 +237,7 @@ module ApplicationSettingsHelper ...@@ -233,6 +237,7 @@ module ApplicationSettingsHelper
:sign_in_text, :sign_in_text,
:signup_enabled, :signup_enabled,
:terminal_max_session_time, :terminal_max_session_time,
:terms,
:throttle_unauthenticated_enabled, :throttle_unauthenticated_enabled,
:throttle_unauthenticated_requests_per_period, :throttle_unauthenticated_requests_per_period,
:throttle_unauthenticated_period_in_seconds, :throttle_unauthenticated_period_in_seconds,
...@@ -250,12 +255,7 @@ module ApplicationSettingsHelper ...@@ -250,12 +255,7 @@ module ApplicationSettingsHelper
:instance_statistics_visibility_private, :instance_statistics_visibility_private,
:user_default_external, :user_default_external,
:user_oauth_applications, :user_oauth_applications,
:version_check_enabled, :version_check_enabled
:allow_local_requests_from_hooks_and_services,
:hide_third_party_offers,
:enforce_terms,
:terms,
:mirror_available
] ]
end end
end end
...@@ -227,26 +227,28 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -227,26 +227,28 @@ class ApplicationSetting < ActiveRecord::Base
def self.defaults def self.defaults
{ {
after_sign_up_text: nil, after_sign_up_text: nil,
allow_local_requests_from_hooks_and_services: false,
akismet_enabled: false, akismet_enabled: false,
authorized_keys_enabled: true, # TODO default to false if the instance is configured to use AuthorizedKeysCommand authorized_keys_enabled: true, # TODO default to false if the instance is configured to use AuthorizedKeysCommand
container_registry_token_expire_delay: 5, container_registry_token_expire_delay: 5,
default_artifacts_expire_in: '30 days', default_artifacts_expire_in: '30 days',
default_branch_protection: Settings.gitlab['default_branch_protection'], default_branch_protection: Settings.gitlab['default_branch_protection'],
default_group_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_projects_limit: Settings.gitlab['default_projects_limit'], default_projects_limit: Settings.gitlab['default_projects_limit'],
default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
default_group_visibility: Settings.gitlab.default_projects_features['visibility_level'],
disabled_oauth_sign_in_sources: [], disabled_oauth_sign_in_sources: [],
domain_whitelist: Settings.gitlab['domain_whitelist'], domain_whitelist: Settings.gitlab['domain_whitelist'],
dsa_key_restriction: 0, dsa_key_restriction: 0,
ecdsa_key_restriction: 0, ecdsa_key_restriction: 0,
ed25519_key_restriction: 0, ed25519_key_restriction: 0,
gitaly_timeout_default: 55,
gitaly_timeout_fast: 10,
gitaly_timeout_medium: 30,
gravatar_enabled: Settings.gravatar['enabled'], gravatar_enabled: Settings.gravatar['enabled'],
help_page_text: nil,
help_page_hide_commercial_content: false, help_page_hide_commercial_content: false,
unique_ips_limit_per_user: 10, help_page_text: nil,
unique_ips_limit_time_window: 3600, hide_third_party_offers: false,
unique_ips_limit_enabled: false,
housekeeping_bitmaps_enabled: true, housekeeping_bitmaps_enabled: true,
housekeeping_enabled: true, housekeeping_enabled: true,
housekeeping_full_repack_period: 50, housekeeping_full_repack_period: 50,
...@@ -257,12 +259,14 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -257,12 +259,14 @@ 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'],
password_authentication_enabled_for_web: Settings.gitlab['signin_enabled'], mirror_available: true,
password_authentication_enabled_for_git: true, password_authentication_enabled_for_git: true,
password_authentication_enabled_for_web: Settings.gitlab['signin_enabled'],
performance_bar_allowed_group_id: nil, performance_bar_allowed_group_id: nil,
rsa_key_restriction: 0, rsa_key_restriction: 0,
plantuml_enabled: false, plantuml_enabled: false,
plantuml_url: nil, plantuml_url: nil,
polling_interval_multiplier: 1,
project_export_enabled: true, project_export_enabled: true,
recaptcha_enabled: false, recaptcha_enabled: false,
repository_checks_enabled: true, repository_checks_enabled: true,
...@@ -277,18 +281,19 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -277,18 +281,19 @@ class ApplicationSetting < ActiveRecord::Base
sign_in_text: nil, sign_in_text: nil,
signup_enabled: Settings.gitlab['signup_enabled'], signup_enabled: Settings.gitlab['signup_enabled'],
terminal_max_session_time: 0, terminal_max_session_time: 0,
throttle_unauthenticated_enabled: false,
throttle_unauthenticated_requests_per_period: 3600,
throttle_unauthenticated_period_in_seconds: 3600,
throttle_authenticated_web_enabled: false,
throttle_authenticated_web_requests_per_period: 7200,
throttle_authenticated_web_period_in_seconds: 3600,
throttle_authenticated_api_enabled: false, throttle_authenticated_api_enabled: false,
throttle_authenticated_api_requests_per_period: 7200,
throttle_authenticated_api_period_in_seconds: 3600, throttle_authenticated_api_period_in_seconds: 3600,
throttle_authenticated_api_requests_per_period: 7200,
throttle_authenticated_web_enabled: false,
throttle_authenticated_web_period_in_seconds: 3600,
throttle_authenticated_web_requests_per_period: 7200,
throttle_unauthenticated_enabled: false,
throttle_unauthenticated_period_in_seconds: 3600,
throttle_unauthenticated_requests_per_period: 3600,
two_factor_grace_period: 48, two_factor_grace_period: 48,
user_default_external: false, unique_ips_limit_enabled: false,
polling_interval_multiplier: 1, unique_ips_limit_per_user: 10,
unique_ips_limit_time_window: 3600,
usage_ping_enabled: Settings.gitlab['usage_ping_enabled'], usage_ping_enabled: Settings.gitlab['usage_ping_enabled'],
instance_statistics_visibility_private: false, instance_statistics_visibility_private: false,
gitaly_timeout_fast: 10, gitaly_timeout_fast: 10,
......
...@@ -22,9 +22,10 @@ module Ci ...@@ -22,9 +22,10 @@ module Ci
has_many :trace_chunks, class_name: 'Ci::BuildTraceChunk', foreign_key: :build_id has_many :trace_chunks, class_name: 'Ci::BuildTraceChunk', foreign_key: :build_id
has_many :job_artifacts, class_name: 'Ci::JobArtifact', foreign_key: :job_id, dependent: :destroy, inverse_of: :job # rubocop:disable Cop/ActiveRecordDependent has_many :job_artifacts, class_name: 'Ci::JobArtifact', foreign_key: :job_id, dependent: :destroy, inverse_of: :job # rubocop:disable Cop/ActiveRecordDependent
has_one :job_artifacts_archive, -> { where(file_type: Ci::JobArtifact.file_types[:archive]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id
has_one :job_artifacts_metadata, -> { where(file_type: Ci::JobArtifact.file_types[:metadata]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id Ci::JobArtifact.file_types.each do |key, value|
has_one :job_artifacts_trace, -> { where(file_type: Ci::JobArtifact.file_types[:trace]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id has_one :"job_artifacts_#{key}", -> { where(file_type: value) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id
end
has_one :metadata, class_name: 'Ci::BuildMetadata' has_one :metadata, class_name: 'Ci::BuildMetadata'
has_one :runner_session, class_name: 'Ci::BuildRunnerSession', validate: true, inverse_of: :build has_one :runner_session, class_name: 'Ci::BuildRunnerSession', validate: true, inverse_of: :build
...@@ -386,6 +387,10 @@ module Ci ...@@ -386,6 +387,10 @@ module Ci
trace.exist? trace.exist?
end end
def has_test_reports?
job_artifacts.test_reports.any?
end
def has_old_trace? def has_old_trace?
old_trace.present? old_trace.present?
end end
...@@ -453,16 +458,22 @@ module Ci ...@@ -453,16 +458,22 @@ module Ci
save save
end end
def erase_test_reports!
# TODO: Use fast_destroy_all in the context of https://gitlab.com/gitlab-org/gitlab-ce/issues/35240
job_artifacts_junit&.destroy
end
def erase(opts = {}) def erase(opts = {})
return false unless erasable? return false unless erasable?
erase_artifacts! erase_artifacts!
erase_test_reports!
erase_trace! erase_trace!
update_erased!(opts[:erased_by]) update_erased!(opts[:erased_by])
end end
def erasable? def erasable?
complete? && (artifacts? || has_trace?) complete? && (artifacts? || has_test_reports? || has_trace?)
end end
def erased? def erased?
...@@ -539,10 +550,6 @@ module Ci ...@@ -539,10 +550,6 @@ module Ci
Gitlab::Ci::Build::Image.from_services(self) Gitlab::Ci::Build::Image.from_services(self)
end end
def artifacts
[options[:artifacts]]
end
def cache def cache
cache = options[:cache] cache = options[:cache]
......
...@@ -17,7 +17,7 @@ module Ci ...@@ -17,7 +17,7 @@ module Ci
{ {
subprotocols: ['terminal.gitlab.com'].freeze, subprotocols: ['terminal.gitlab.com'].freeze,
url: "#{url}/exec".sub("https://", "wss://"), url: "#{url}/exec".sub("https://", "wss://"),
headers: { Authorization: authorization.presence }.compact, headers: { Authorization: [authorization.presence] }.compact,
ca_pem: certificate.presence ca_pem: certificate.presence
} }
end end
......
...@@ -4,11 +4,17 @@ module Ci ...@@ -4,11 +4,17 @@ module Ci
include ObjectStorage::BackgroundMove include ObjectStorage::BackgroundMove
extend Gitlab::Ci::Model extend Gitlab::Ci::Model
TEST_REPORT_FILE_TYPES = %w[junit].freeze
DEFAULT_FILE_NAMES = { junit: 'junit.xml' }.freeze
TYPE_AND_FORMAT_PAIRS = { archive: :zip, metadata: :gzip, trace: :raw, junit: :gzip }.freeze
belongs_to :project belongs_to :project
belongs_to :job, class_name: "Ci::Build", foreign_key: :job_id belongs_to :job, class_name: "Ci::Build", foreign_key: :job_id
mount_uploader :file, JobArtifactUploader mount_uploader :file, JobArtifactUploader
validates :file_format, presence: true, unless: :trace?, on: :create
validate :valid_file_format?, unless: :trace?, on: :create
before_save :set_size, if: :file_changed? before_save :set_size, if: :file_changed?
after_save :update_project_statistics_after_save, if: :size_changed? after_save :update_project_statistics_after_save, if: :size_changed?
after_destroy :update_project_statistics_after_destroy, unless: :project_destroyed? after_destroy :update_project_statistics_after_destroy, unless: :project_destroyed?
...@@ -17,14 +23,33 @@ module Ci ...@@ -17,14 +23,33 @@ module Ci
scope :with_files_stored_locally, -> { where(file_store: [nil, ::JobArtifactUploader::Store::LOCAL]) } scope :with_files_stored_locally, -> { where(file_store: [nil, ::JobArtifactUploader::Store::LOCAL]) }
scope :test_reports, -> do
types = self.file_types.select { |file_type| TEST_REPORT_FILE_TYPES.include?(file_type) }.values
where(file_type: types)
end
delegate :exists?, :open, to: :file delegate :exists?, :open, to: :file
enum file_type: { enum file_type: {
archive: 1, archive: 1,
metadata: 2, metadata: 2,
trace: 3 trace: 3,
junit: 4
} }
enum file_format: {
raw: 1,
zip: 2,
gzip: 3
}
def valid_file_format?
unless TYPE_AND_FORMAT_PAIRS[self.file_type&.to_sym] == self.file_format&.to_sym
errors.add(:file_format, 'Invalid file format with specified file type')
end
end
def update_file_store def update_file_store
# The file.object_store is set during `uploader.store!` # The file.object_store is set during `uploader.store!`
# which happens after object is inserted/updated # which happens after object is inserted/updated
......
...@@ -26,6 +26,10 @@ module AtomicInternalId ...@@ -26,6 +26,10 @@ module AtomicInternalId
module ClassMethods module ClassMethods
def has_internal_id(column, scope:, init:, presence: true) # rubocop:disable Naming/PredicateName def has_internal_id(column, scope:, init:, presence: true) # rubocop:disable Naming/PredicateName
# We require init here to retain the ability to recalculate in the absence of a
# InternaLId record (we may delete records in `internal_ids` for example).
raise "has_internal_id requires a init block, none given." unless init
before_validation :"ensure_#{scope}_#{column}!", on: :create before_validation :"ensure_#{scope}_#{column}!", on: :create
validates column, presence: presence validates column, presence: presence
......
...@@ -149,6 +149,10 @@ class Milestone < ActiveRecord::Base ...@@ -149,6 +149,10 @@ class Milestone < ActiveRecord::Base
reorder(Gitlab::Database.nulls_last_order('due_date', 'ASC')) reorder(Gitlab::Database.nulls_last_order('due_date', 'ASC'))
when 'due_date_desc' when 'due_date_desc'
reorder(Gitlab::Database.nulls_last_order('due_date', 'DESC')) reorder(Gitlab::Database.nulls_last_order('due_date', 'DESC'))
when 'name_asc'
reorder(Arel::Nodes::Ascending.new(arel_table[:title].lower))
when 'name_desc'
reorder(Arel::Nodes::Descending.new(arel_table[:title].lower))
when 'start_date_asc' when 'start_date_asc'
reorder(Gitlab::Database.nulls_last_order('start_date', 'ASC')) reorder(Gitlab::Database.nulls_last_order('start_date', 'ASC'))
when 'start_date_desc' when 'start_date_desc'
......
...@@ -548,6 +548,10 @@ class Project < ActiveRecord::Base ...@@ -548,6 +548,10 @@ class Project < ActiveRecord::Base
repository.commit_by(oid: oid) repository.commit_by(oid: oid)
end end
def commits_by(oids:)
repository.commits_by(oids: oids)
end
# ref can't be HEAD, can only be branch/tag name or SHA # ref can't be HEAD, can only be branch/tag name or SHA
def latest_successful_builds_for(ref = default_branch) def latest_successful_builds_for(ref = default_branch)
latest_pipeline = pipelines.latest_successful_for(ref) latest_pipeline = pipelines.latest_successful_for(ref)
......
...@@ -5,7 +5,7 @@ class ProjectStatistics < ActiveRecord::Base ...@@ -5,7 +5,7 @@ class ProjectStatistics < ActiveRecord::Base
before_save :update_storage_size before_save :update_storage_size
COLUMNS_TO_REFRESH = [:repository_size, :lfs_objects_size, :commit_count].freeze COLUMNS_TO_REFRESH = [:repository_size, :lfs_objects_size, :commit_count].freeze
INCREMENTABLE_COLUMNS = [:build_artifacts_size].freeze INCREMENTABLE_COLUMNS = { build_artifacts_size: %i[storage_size] }.freeze
def total_repository_size def total_repository_size
repository_size + lfs_objects_size repository_size + lfs_objects_size
...@@ -38,11 +38,28 @@ class ProjectStatistics < ActiveRecord::Base ...@@ -38,11 +38,28 @@ class ProjectStatistics < ActiveRecord::Base
self.storage_size = repository_size + lfs_objects_size + build_artifacts_size self.storage_size = repository_size + lfs_objects_size + build_artifacts_size
end end
# Since this incremental update method does not call update_storage_size above,
# we have to update the storage_size here as additional column.
# Additional columns are updated depending on key => [columns], which allows
# to update statistics which are and also those which aren't included in storage_size
# or any other additional summary column in the future.
def self.increment_statistic(project_id, key, amount) def self.increment_statistic(project_id, key, amount)
raise ArgumentError, "Cannot increment attribute: #{key}" unless key.in?(INCREMENTABLE_COLUMNS) raise ArgumentError, "Cannot increment attribute: #{key}" unless INCREMENTABLE_COLUMNS.key?(key)
return if amount == 0 return if amount == 0
where(project_id: project_id) where(project_id: project_id)
.update_all(["#{key} = COALESCE(#{key}, 0) + (?)", amount]) .columns_to_increment(key, amount)
end
def self.columns_to_increment(key, amount)
updates = ["#{key} = COALESCE(#{key}, 0) + (#{amount})"]
if (additional = INCREMENTABLE_COLUMNS[key])
additional.each do |column|
updates << "#{column} = COALESCE(#{column}, 0) + (#{amount})"
end
end
update_all(updates.join(', '))
end end
end end
...@@ -312,6 +312,8 @@ class Repository ...@@ -312,6 +312,8 @@ class Repository
# types - An Array of file types (e.g. `:readme`) used to refresh extra # types - An Array of file types (e.g. `:readme`) used to refresh extra
# caches. # caches.
def refresh_method_caches(types) def refresh_method_caches(types)
return if types.empty?
to_refresh = [] to_refresh = []
types.each do |type| types.each do |type|
......
...@@ -248,6 +248,7 @@ class User < ActiveRecord::Base ...@@ -248,6 +248,7 @@ class User < ActiveRecord::Base
scope :todo_authors, ->(user_id, state) { where(id: Todo.where(user_id: user_id, state: state).select(:author_id)) } scope :todo_authors, ->(user_id, state) { where(id: Todo.where(user_id: user_id, state: state).select(:author_id)) }
scope :order_recent_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'DESC')) } scope :order_recent_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'DESC')) }
scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'ASC')) } scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'ASC')) }
scope :confirmed, -> { where.not(confirmed_at: nil) }
def self.with_two_factor_indistinct def self.with_two_factor_indistinct
joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id") joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id")
...@@ -293,14 +294,17 @@ class User < ActiveRecord::Base ...@@ -293,14 +294,17 @@ class User < ActiveRecord::Base
end end
# Find a User by their primary email or any associated secondary email # Find a User by their primary email or any associated secondary email
def find_by_any_email(email) def find_by_any_email(email, confirmed: false)
by_any_email(email).take by_any_email(email, confirmed: confirmed).take
end end
# Returns a relation containing all the users for the given Email address # Returns a relation containing all the users for the given Email address
def by_any_email(email) def by_any_email(email, confirmed: false)
users = where(email: email) users = where(email: email)
users = users.confirmed if confirmed
emails = joins(:emails).where(emails: { email: email }) emails = joins(:emails).where(emails: { email: email })
emails = emails.confirmed if confirmed
union = Gitlab::SQL::Union.new([users, emails]) union = Gitlab::SQL::Union.new([users, emails])
from("(#{union.to_sql}) #{table_name}") from("(#{union.to_sql}) #{table_name}")
......
module Ci
class BuildRunnerPresenter < SimpleDelegator
def artifacts
return unless options[:artifacts]
list = []
list << create_archive(options[:artifacts])
list << create_reports(options[:artifacts][:reports], expire_in: options[:artifacts][:expire_in])
list.flatten.compact
end
private
def create_archive(artifacts)
return unless artifacts[:untracked] || artifacts[:paths]
{
artifact_type: :archive,
artifact_format: :zip,
name: artifacts[:name],
untracked: artifacts[:untracked],
paths: artifacts[:paths],
when: artifacts[:when],
expire_in: artifacts[:expire_in]
}
end
def create_reports(reports, expire_in:)
return unless reports&.any?
reports.map do |k, v|
{
artifact_type: k.to_sym,
artifact_format: :gzip,
name: ::Ci::JobArtifact::DEFAULT_FILE_NAMES[k.to_sym],
paths: v,
when: 'always',
expire_in: expire_in
}
end
end
end
end
...@@ -132,6 +132,10 @@ class MergeRequestWidgetEntity < IssuableEntity ...@@ -132,6 +132,10 @@ class MergeRequestWidgetEntity < IssuableEntity
can?(request.current_user, :create_note, merge_request) can?(request.current_user, :create_note, merge_request)
end end
expose :can_create_issue do |merge_request|
can?(current_user, :create_issue, merge_request.project)
end
expose :can_update do |merge_request| expose :can_update do |merge_request|
can?(request.current_user, :update_merge_request, merge_request) can?(request.current_user, :update_merge_request, merge_request)
end end
......
...@@ -3,14 +3,35 @@ ...@@ -3,14 +3,35 @@
module Boards module Boards
module Issues module Issues
class ListService < Boards::BaseService class ListService < Boards::BaseService
include Gitlab::Utils::StrongMemoize
def execute def execute
issues = IssuesFinder.new(current_user, filter_params).execute fetch_issues.order_by_position_and_priority
issues = filter(issues) end
issues.order_by_position_and_priority
def metadata
keys = metadata_fields.keys
columns = metadata_fields.values_at(*keys).join(', ')
results = Issue.where(id: fetch_issues.select('issues.id')).pluck(columns)
Hash[keys.zip(results.flatten)]
end end
private private
def metadata_fields
{ size: 'COUNT(*)' }
end
# We memoize the query here since the finder methods we use are quite complex. This does not memoize the result of the query.
def fetch_issues
strong_memoize(:fetch_issues) do
issues = IssuesFinder.new(current_user, filter_params).execute
filter(issues).reorder(nil)
end
end
def filter(issues) def filter(issues)
issues = without_board_labels(issues) unless list&.movable? || list&.closed? issues = without_board_labels(issues) unless list&.movable? || list&.closed?
issues = with_list_label(issues) if list&.label? issues = with_list_label(issues) if list&.label?
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
class GitPushService < BaseService class GitPushService < BaseService
attr_accessor :push_data, :push_commits attr_accessor :push_data, :push_commits
include Gitlab::Access include Gitlab::Access
include Gitlab::Utils::StrongMemoize
# The N most recent commits to process in a single push payload. # The N most recent commits to process in a single push payload.
PROCESS_COMMIT_LIMIT = 100 PROCESS_COMMIT_LIMIT = 100
...@@ -21,14 +22,14 @@ class GitPushService < BaseService ...@@ -21,14 +22,14 @@ class GitPushService < BaseService
# 6. Checks if the project's main language has changed # 6. Checks if the project's main language has changed
# #
def execute def execute
@project.repository.after_create if @project.empty_repo? project.repository.after_create if project.empty_repo?
@project.repository.after_push_commit(branch_name) project.repository.after_push_commit(branch_name)
if push_remove_branch? if push_remove_branch?
@project.repository.after_remove_branch project.repository.after_remove_branch
@push_commits = [] @push_commits = []
elsif push_to_new_branch? elsif push_to_new_branch?
@project.repository.after_create_branch project.repository.after_create_branch
# Re-find the pushed commits. # Re-find the pushed commits.
if default_branch? if default_branch?
...@@ -38,14 +39,14 @@ class GitPushService < BaseService ...@@ -38,14 +39,14 @@ class GitPushService < BaseService
# Use the pushed commits that aren't reachable by the default branch # Use the pushed commits that aren't reachable by the default branch
# as a heuristic. This may include more commits than are actually pushed, but # as a heuristic. This may include more commits than are actually pushed, but
# that shouldn't matter because we check for existing cross-references later. # that shouldn't matter because we check for existing cross-references later.
@push_commits = @project.repository.commits_between(@project.default_branch, params[:newrev]) @push_commits = project.repository.commits_between(project.default_branch, params[:newrev])
# don't process commits for the initial push to the default branch # don't process commits for the initial push to the default branch
process_commit_messages process_commit_messages
end end
elsif push_to_existing_branch? elsif push_to_existing_branch?
# Collect data for this git push # Collect data for this git push
@push_commits = @project.repository.commits_between(params[:oldrev], params[:newrev]) @push_commits = project.repository.commits_between(params[:oldrev], params[:newrev])
process_commit_messages process_commit_messages
...@@ -64,7 +65,7 @@ class GitPushService < BaseService ...@@ -64,7 +65,7 @@ class GitPushService < BaseService
end end
def update_gitattributes def update_gitattributes
@project.repository.copy_gitattributes(params[:ref]) project.repository.copy_gitattributes(params[:ref])
end end
def update_caches def update_caches
...@@ -76,7 +77,7 @@ class GitPushService < BaseService ...@@ -76,7 +77,7 @@ class GitPushService < BaseService
else else
paths = Set.new paths = Set.new
@push_commits.last(PROCESS_COMMIT_LIMIT).each do |commit| last_pushed_commits.each do |commit|
commit.raw_deltas.each do |diff| commit.raw_deltas.each do |diff|
paths << diff.new_path paths << diff.new_path
end end
...@@ -88,11 +89,11 @@ class GitPushService < BaseService ...@@ -88,11 +89,11 @@ class GitPushService < BaseService
types = [] types = []
end end
ProjectCacheWorker.perform_async(@project.id, types, [:commit_count, :repository_size]) ProjectCacheWorker.perform_async(project.id, types, [:commit_count, :repository_size])
end end
def update_signatures def update_signatures
commit_shas = @push_commits.last(PROCESS_COMMIT_LIMIT).map(&:sha) commit_shas = last_pushed_commits.map(&:sha)
return if commit_shas.empty? return if commit_shas.empty?
...@@ -103,16 +104,14 @@ class GitPushService < BaseService ...@@ -103,16 +104,14 @@ class GitPushService < BaseService
commit_shas = Gitlab::Git::Commit.shas_with_signatures(project.repository, commit_shas) commit_shas = Gitlab::Git::Commit.shas_with_signatures(project.repository, commit_shas)
commit_shas.each do |sha| CreateGpgSignatureWorker.perform_async(commit_shas, project.id)
CreateGpgSignatureWorker.perform_async(sha, project.id)
end
end end
# Schedules processing of commit messages. # Schedules processing of commit messages.
def process_commit_messages def process_commit_messages
default = default_branch? default = default_branch?
@push_commits.last(PROCESS_COMMIT_LIMIT).each do |commit| last_pushed_commits.each do |commit|
if commit.matches_cross_reference_regex? if commit.matches_cross_reference_regex?
ProcessCommitWorker ProcessCommitWorker
.perform_async(project.id, current_user.id, commit.to_hash, default) .perform_async(project.id, current_user.id, commit.to_hash, default)
...@@ -123,10 +122,10 @@ class GitPushService < BaseService ...@@ -123,10 +122,10 @@ class GitPushService < BaseService
protected protected
def update_remote_mirrors def update_remote_mirrors
return unless @project.has_remote_mirror? return unless project.has_remote_mirror?
@project.mark_stuck_remote_mirrors_as_failed! project.mark_stuck_remote_mirrors_as_failed!
@project.update_remote_mirrors project.update_remote_mirrors
end end
def execute_related_hooks def execute_related_hooks
...@@ -134,14 +133,14 @@ class GitPushService < BaseService ...@@ -134,14 +133,14 @@ class GitPushService < BaseService
# could cause the last commit of a merge request to change. # could cause the last commit of a merge request to change.
# #
UpdateMergeRequestsWorker UpdateMergeRequestsWorker
.perform_async(@project.id, current_user.id, params[:oldrev], params[:newrev], params[:ref]) .perform_async(project.id, current_user.id, params[:oldrev], params[:newrev], params[:ref])
EventCreateService.new.push(@project, current_user, build_push_data) EventCreateService.new.push(project, current_user, build_push_data)
Ci::CreatePipelineService.new(@project, current_user, build_push_data).execute(:push) Ci::CreatePipelineService.new(project, current_user, build_push_data).execute(:push)
SystemHookPushWorker.perform_async(build_push_data.dup, :push_hooks) SystemHookPushWorker.perform_async(build_push_data.dup, :push_hooks)
@project.execute_hooks(build_push_data.dup, :push_hooks) project.execute_hooks(build_push_data.dup, :push_hooks)
@project.execute_services(build_push_data.dup, :push_hooks) project.execute_services(build_push_data.dup, :push_hooks)
if push_remove_branch? if push_remove_branch?
AfterBranchDeleteService AfterBranchDeleteService
...@@ -151,52 +150,50 @@ class GitPushService < BaseService ...@@ -151,52 +150,50 @@ class GitPushService < BaseService
end end
def perform_housekeeping def perform_housekeeping
housekeeping = Projects::HousekeepingService.new(@project) housekeeping = Projects::HousekeepingService.new(project)
housekeeping.increment! housekeeping.increment!
housekeeping.execute if housekeeping.needed? housekeeping.execute if housekeeping.needed?
rescue Projects::HousekeepingService::LeaseTaken rescue Projects::HousekeepingService::LeaseTaken
end end
def process_default_branch def process_default_branch
@push_commits_count = project.repository.commit_count_for_ref(params[:ref]) offset = [push_commits_count - PROCESS_COMMIT_LIMIT, 0].max
offset = [@push_commits_count - PROCESS_COMMIT_LIMIT, 0].max
@push_commits = project.repository.commits(params[:newrev], offset: offset, limit: PROCESS_COMMIT_LIMIT) @push_commits = project.repository.commits(params[:newrev], offset: offset, limit: PROCESS_COMMIT_LIMIT)
@project.after_create_default_branch project.after_create_default_branch
end end
def build_push_data def build_push_data
@push_data ||= Gitlab::DataBuilder::Push.build( @push_data ||= Gitlab::DataBuilder::Push.build(
@project, project,
current_user, current_user,
params[:oldrev], params[:oldrev],
params[:newrev], params[:newrev],
params[:ref], params[:ref],
@push_commits, @push_commits,
commits_count: @push_commits_count) commits_count: push_commits_count)
end end
def push_to_existing_branch? def push_to_existing_branch?
# Return if this is not a push to a branch (e.g. new commits) # Return if this is not a push to a branch (e.g. new commits)
Gitlab::Git.branch_ref?(params[:ref]) && !Gitlab::Git.blank_ref?(params[:oldrev]) branch_ref? && !Gitlab::Git.blank_ref?(params[:oldrev])
end end
def push_to_new_branch? def push_to_new_branch?
Gitlab::Git.branch_ref?(params[:ref]) && Gitlab::Git.blank_ref?(params[:oldrev]) strong_memoize(:push_to_new_branch) do
branch_ref? && Gitlab::Git.blank_ref?(params[:oldrev])
end
end end
def push_remove_branch? def push_remove_branch?
Gitlab::Git.branch_ref?(params[:ref]) && Gitlab::Git.blank_ref?(params[:newrev]) strong_memoize(:push_remove_branch) do
end branch_ref? && Gitlab::Git.blank_ref?(params[:newrev])
end
def push_to_branch?
Gitlab::Git.branch_ref?(params[:ref])
end end
def default_branch? def default_branch?
Gitlab::Git.branch_ref?(params[:ref]) && branch_ref? &&
(Gitlab::Git.ref_name(params[:ref]) == project.default_branch || project.default_branch.nil?) (branch_name == project.default_branch || project.default_branch.nil?)
end end
def commit_user(commit) def commit_user(commit)
...@@ -204,6 +201,24 @@ class GitPushService < BaseService ...@@ -204,6 +201,24 @@ class GitPushService < BaseService
end end
def branch_name def branch_name
@branch_name ||= Gitlab::Git.ref_name(params[:ref]) strong_memoize(:branch_name) do
Gitlab::Git.ref_name(params[:ref])
end
end
def branch_ref?
strong_memoize(:branch_ref) do
Gitlab::Git.branch_ref?(params[:ref])
end
end
def push_commits_count
strong_memoize(:push_commits_count) do
project.repository.commit_count_for_ref(params[:ref])
end
end
def last_pushed_commits
@last_pushed_commits ||= @push_commits.last(PROCESS_COMMIT_LIMIT)
end end
end end
...@@ -9,12 +9,12 @@ class GitTagPushService < BaseService ...@@ -9,12 +9,12 @@ class GitTagPushService < BaseService
@push_data = build_push_data @push_data = build_push_data
EventCreateService.new.push(project, current_user, @push_data) EventCreateService.new.push(project, current_user, push_data)
Ci::CreatePipelineService.new(project, current_user, @push_data).execute(:push) Ci::CreatePipelineService.new(project, current_user, push_data).execute(:push)
SystemHooksService.new.execute_hooks(build_system_push_data.dup, :tag_push_hooks) SystemHooksService.new.execute_hooks(build_system_push_data, :tag_push_hooks)
project.execute_hooks(@push_data.dup, :tag_push_hooks) project.execute_hooks(push_data.dup, :tag_push_hooks)
project.execute_services(@push_data.dup, :tag_push_hooks) project.execute_services(push_data.dup, :tag_push_hooks)
ProjectCacheWorker.perform_async(project.id, [], [:commit_count, :repository_size]) ProjectCacheWorker.perform_async(project.id, [], [:commit_count, :repository_size])
......
...@@ -37,6 +37,8 @@ module Issues ...@@ -37,6 +37,8 @@ module Issues
end end
if issue.previous_changes.include?('confidential') if issue.previous_changes.include?('confidential')
# don't enqueue immediately to prevent todos removal in case of a mistake
TodosDestroyer::ConfidentialIssueWorker.perform_in(1.hour, issue.id) if issue.confidential?
create_confidentiality_note(issue) create_confidentiality_note(issue)
end end
......
...@@ -15,6 +15,8 @@ module Members ...@@ -15,6 +15,8 @@ module Members
notification_service.decline_access_request(member) notification_service.decline_access_request(member)
end end
enqeue_delete_todos(member)
after_execute(member: member) after_execute(member: member)
member member
...@@ -22,6 +24,12 @@ module Members ...@@ -22,6 +24,12 @@ module Members
private private
def enqeue_delete_todos(member)
type = member.is_a?(GroupMember) ? 'Group' : 'Project'
# don't enqueue immediately to prevent todos removal in case of a mistake
TodosDestroyer::EntityLeaveWorker.perform_in(1.hour, member.user_id, member.source_id, type)
end
def can_destroy_member?(member) def can_destroy_member?(member)
can?(current_user, destroy_member_permission(member), member) can?(current_user, destroy_member_permission(member), member)
end end
......
...@@ -25,13 +25,7 @@ module Projects ...@@ -25,13 +25,7 @@ module Projects
return validation_failed! if project.errors.any? return validation_failed! if project.errors.any?
if project.update(params.except(:default_branch)) if project.update(params.except(:default_branch))
if project.previous_changes.include?('path') after_update
project.rename_repo
else
system_hook_service.execute_hooks_for(project, :update)
end
update_pages_config if changing_pages_https_only?
success success
else else
...@@ -47,6 +41,30 @@ module Projects ...@@ -47,6 +41,30 @@ module Projects
private private
def after_update
todos_features_changes = %w(
issues_access_level
merge_requests_access_level
repository_access_level
)
project_changed_feature_keys = project.project_feature.previous_changes.keys
if project.previous_changes.include?(:visibility_level) && project.private?
# don't enqueue immediately to prevent todos removal in case of a mistake
TodosDestroyer::ProjectPrivateWorker.perform_in(1.hour, project.id)
elsif (project_changed_feature_keys & todos_features_changes).present?
TodosDestroyer::PrivateFeaturesWorker.perform_in(1.hour, project.id)
end
if project.previous_changes.include?('path')
project.rename_repo
else
system_hook_service.execute_hooks_for(project, :update)
end
update_pages_config if changing_pages_https_only?
end
def validation_failed! def validation_failed!
model_errors = project.errors.full_messages.to_sentence model_errors = project.errors.full_messages.to_sentence
error_message = model_errors.presence || 'Project could not be updated!' error_message = model_errors.presence || 'Project could not be updated!'
......
module Todos
module Destroy
class BaseService
def execute
return unless todos_to_remove?
without_authorized(todos).delete_all
end
private
def without_authorized(items)
items.where('user_id NOT IN (?)', authorized_users)
end
def authorized_users
ProjectAuthorization.select(:user_id).where(project_id: project_ids)
end
def todos
raise NotImplementedError
end
def project_ids
raise NotImplementedError
end
def todos_to_remove?
raise NotImplementedError
end
end
end
end
module Todos
module Destroy
class ConfidentialIssueService < ::Todos::Destroy::BaseService
extend ::Gitlab::Utils::Override
attr_reader :issue
def initialize(issue_id)
@issue = Issue.find_by(id: issue_id)
end
private
override :todos
def todos
Todo.where(target: issue)
.where('user_id != ?', issue.author_id)
.where('user_id NOT IN (?)', issue.assignees.select(:id))
end
override :todos_to_remove?
def todos_to_remove?
issue&.confidential?
end
override :project_ids
def project_ids
issue.project_id
end
override :authorized_users
def authorized_users
ProjectAuthorization.select(:user_id)
.where(project_id: project_ids)
.where('access_level >= ?', Gitlab::Access::REPORTER)
end
end
end
end
module Todos
module Destroy
class EntityLeaveService < ::Todos::Destroy::BaseService
extend ::Gitlab::Utils::Override
attr_reader :user_id, :entity
def initialize(user_id, entity_id, entity_type)
unless %w(Group Project).include?(entity_type)
raise ArgumentError.new("#{entity_type} is not an entity user can leave")
end
@user_id = user_id
@entity = entity_type.constantize.find_by(id: entity_id)
end
private
override :todos
def todos
if entity.private?
Todo.where(project_id: project_ids, user_id: user_id)
else
project_ids.each do |project_id|
TodosDestroyer::PrivateFeaturesWorker.perform_async(project_id, user_id)
end
Todo.where(
target_id: confidential_issues.select(:id), target_type: Issue, user_id: user_id
)
end
end
override :project_ids
def project_ids
case entity
when Project
[entity.id]
when Namespace
Project.select(:id).where(namespace_id: entity.self_and_descendants.select(:id))
end
end
override :todos_to_remove?
def todos_to_remove?
# if an entity is provided we want to check always at least private features
!!entity
end
def confidential_issues
assigned_ids = IssueAssignee.select(:issue_id).where(user_id: user_id)
Issue.where(project_id: project_ids, confidential: true)
.where('author_id != ?', user_id)
.where('id NOT IN (?)', assigned_ids)
end
end
end
end
module Todos
module Destroy
class PrivateFeaturesService < ::Todos::Destroy::BaseService
attr_reader :project_ids, :user_id
def initialize(project_ids, user_id = nil)
@project_ids = project_ids
@user_id = user_id
end
def execute
ProjectFeature.where(project_id: project_ids).each do |project_features|
target_types = []
target_types << Issue if private?(project_features.issues_access_level)
target_types << MergeRequest if private?(project_features.merge_requests_access_level)
target_types << Commit if private?(project_features.repository_access_level)
next if target_types.empty?
remove_todos(project_features.project_id, target_types)
end
end
private
def private?(feature_level)
feature_level == ProjectFeature::PRIVATE
end
def remove_todos(project_id, target_types)
items = Todo.where(project_id: project_id)
items = items.where(user_id: user_id) if user_id
items.where('user_id NOT IN (?)', authorized_users)
.where(target_type: target_types)
.delete_all
end
end
end
end
module Todos
module Destroy
class ProjectPrivateService < ::Todos::Destroy::BaseService
extend ::Gitlab::Utils::Override
attr_reader :project
def initialize(project_id)
@project = Project.find_by(id: project_id)
end
private
override :todos
def todos
Todo.where(project_id: project_ids)
end
override :project_ids
def project_ids
project.id
end
override :todos_to_remove?
def todos_to_remove?
project&.private?
end
end
end
end
...@@ -4,6 +4,6 @@ ...@@ -4,6 +4,6 @@
= s_("Wiki|New page") = s_("Wiki|New page")
= link_to project_wiki_history_path(@project, @page), class: "btn" do = link_to project_wiki_history_path(@project, @page), class: "btn" do
= s_("Wiki|Page history") = s_("Wiki|Page history")
- if can?(current_user, :create_wiki, @project) && @page.latest? - if can?(current_user, :create_wiki, @project) && @page.latest? && @valid_encoding
= link_to project_wiki_edit_path(@project, @page), class: "btn js-wiki-edit" do = link_to project_wiki_edit_path(@project, @page), class: "btn js-wiki-edit" do
= _("Edit") = _("Edit")
...@@ -32,17 +32,21 @@ ...@@ -32,17 +32,21 @@
"v-if" => "!list.preset && list.id" } "v-if" => "!list.preset && list.id" }
%button.board-delete.has-tooltip.float-right{ type: "button", title: _("Delete list"), "aria-label" => _("Delete list"), data: { placement: "bottom" }, "@click.stop" => "deleteBoard" } %button.board-delete.has-tooltip.float-right{ type: "button", title: _("Delete list"), "aria-label" => _("Delete list"), data: { placement: "bottom" }, "@click.stop" => "deleteBoard" }
= icon("trash") = icon("trash")
.issue-count-badge.clearfix{ "v-if" => 'list.type !== "blank" && list.type !== "promotion"' } .issue-count-badge.text-secondary{ "v-if" => 'list.type !== "blank" && list.type !== "promotion"', ":title": "counterTooltip", "v-tooltip": true, data: { placement: "top" } }
%span.issue-count-badge-count.float-left{ ":class" => '{ "has-btn": list.type !== "closed" && !disabled }' } %span.issue-count-badge-count
%icon.mr-1{ name: "issues" }
{{ list.issuesSize }} {{ list.issuesSize }}
- if can?(current_user, :admin_list, current_board_parent) = render_if_exists "shared/boards/components/list_weight"
%button.issue-count-badge-add-button.btn.btn-sm.btn-default.has-tooltip.js-no-trigger-collapse{ type: "button",
"@click" => "showNewIssueForm", - if can?(current_user, :admin_list, current_board_parent)
"v-if" => 'list.type !== "closed"', %button.issue-count-badge-add-button.btn.btn-sm.btn-default.ml-1.has-tooltip.js-no-trigger-collapse{ type: "button",
"aria-label" => _("New issue"), "@click" => "showNewIssueForm",
"title" => _("New issue"), "v-if" => 'list.type !== "closed"',
data: { placement: "top", container: "body" } } "aria-label" => _("New issue"),
= icon("plus", class: "js-no-trigger-collapse") "title" => _("New issue"),
data: { placement: "top", container: "body" } }
= icon("plus", class: "js-no-trigger-collapse")
%board-list{ "v-if" => 'list.type !== "blank" && list.type !== "promotion"', %board-list{ "v-if" => 'list.type !== "blank" && list.type !== "promotion"',
":list" => "list", ":list" => "list",
":issues" => "list.issues", ":issues" => "list.issues",
......
...@@ -73,6 +73,11 @@ ...@@ -73,6 +73,11 @@
- repository_check:repository_check_batch - repository_check:repository_check_batch
- repository_check:repository_check_single_repository - repository_check:repository_check_single_repository
- todos_destroyer:todos_destroyer_confidential_issue
- todos_destroyer:todos_destroyer_entity_leave
- todos_destroyer:todos_destroyer_project_private
- todos_destroyer:todos_destroyer_private_features
- default - default
- mailers # ActionMailer::DeliveryJob.queue_name - mailers # ActionMailer::DeliveryJob.queue_name
......
# frozen_string_literal: true
##
# Concern for setting Sidekiq settings for the various Todos Destroyers.
#
module TodosDestroyerQueue
extend ActiveSupport::Concern
included do
queue_namespace :todos_destroyer
end
end
...@@ -3,15 +3,23 @@ ...@@ -3,15 +3,23 @@
class CreateGpgSignatureWorker class CreateGpgSignatureWorker
include ApplicationWorker include ApplicationWorker
def perform(commit_sha, project_id) def perform(commit_shas, project_id)
return if commit_shas.empty?
project = Project.find_by(id: project_id) project = Project.find_by(id: project_id)
return unless project return unless project
commit = project.commit(commit_sha) commits = project.commits_by(oids: commit_shas)
return unless commit return if commits.empty?
# This calculates and caches the signature in the database # This calculates and caches the signature in the database
Gitlab::Gpg::Commit.new(commit).signature commits.each do |commit|
begin
Gitlab::Gpg::Commit.new(commit).signature
rescue => e
Rails.logger.error("Failed to create signature for commit #{commit.id}. Error: #{e.message}")
end
end
end end
end end
module TodosDestroyer
class ConfidentialIssueWorker
include ApplicationWorker
include TodosDestroyerQueue
def perform(issue_id)
::Todos::Destroy::ConfidentialIssueService.new(issue_id).execute
end
end
end
module TodosDestroyer
class EntityLeaveWorker
include ApplicationWorker
include TodosDestroyerQueue
def perform(user_id, entity_id, entity_type)
::Todos::Destroy::EntityLeaveService.new(user_id, entity_id, entity_type).execute
end
end
end
module TodosDestroyer
class PrivateFeaturesWorker
include ApplicationWorker
include TodosDestroyerQueue
def perform(project_id, user_id = nil)
::Todos::Destroy::PrivateFeaturesService.new(project_id, user_id).execute
end
end
end
module TodosDestroyer
class ProjectPrivateWorker
include ApplicationWorker
include TodosDestroyerQueue
def perform(project_id)
::Todos::Destroy::ProjectPrivateService.new(project_id).execute
end
end
end
---
title: Fix authorization for interactive web terminals
merge_request: 20811
author:
type: fixed
---
title: Improve error message when adding invalid user to a project
merge_request: 20885
author: Jacopo Beschi @jacopo-beschi
type: added
---
title: Resolve Copy diff file path as GFM is broken
merge_request: 20725
author:
type: fixed
---
title: Disables toggle comments button if diff has no discussions
merge_request:
author:
type: other
---
title: Fix sorting by name on milestones page
merge_request: 20881
author:
type: fixed
---
title: Changes poll.js to keep polling on any 2xx http status code
merge_request: 20904
author:
type: other
---
title: Extend gitlab-ci.yml to request junit.xml test reports
merge_request: 20390
author:
type: added
---
title: Add more comprehensive metrics tracking authentication activity
merge_request: 20668
author:
type: added
---
title: Update total storage size when changing size of artifacts
merge_request: 20697
author: Peter Marko
type: fixed
---
title: Performing Commit GPG signature calculation in bulk
merge_request: 20870
author:
type: performance
---
title: Prevent editing and updating wiki pages with non UTF-8 encoding via web interface
merge_request: 20906
author:
type: fixed
---
title: Warn user when reload IDE with staged changes
merge_request: 20857
author:
type: added
---
title: Permit concurrent loads in gpg keychain mutex
merge_request: 20894
author: Jasper Maes
type: fixed
---
title: Fix /admin/jobs failing to load due to statement timeout
merge_request: 20909
author:
type: performance
---
title: Add /-/health basic health check endpoint
merge_request: 20456
author:
type: added
---
title: Add support for searching users by confirmed e-mails
merge_request: 20893
author:
type: other
---
title: Delete todos when user loses access to read the target
merge_request: 20665
author:
type: other
---
title: Remove gitlab:user:check_repos, gitlab:check_repo, gitlab:git:prune, gitlab:git:gc, and gitlab:git:repack
merge_request: 20806
author:
type: removed
...@@ -154,6 +154,10 @@ module Gitlab ...@@ -154,6 +154,10 @@ module Gitlab
config.action_view.sanitized_allowed_protocols = %w(smb) config.action_view.sanitized_allowed_protocols = %w(smb)
# This middleware needs to precede ActiveRecord::QueryCache and other middlewares that
# connect to the database.
config.middleware.insert_after "Rails::Rack::Logger", "Gitlab::Middleware::BasicHealthCheck"
config.middleware.insert_after Warden::Manager, Rack::Attack config.middleware.insert_after Warden::Manager, Rack::Attack
# Allow access to GitLab API from other domains # Allow access to GitLab API from other domains
......
# frozen_string_literal: true
require 'rbtrace' if ENV['ENABLE_RBTRACE']
...@@ -8,6 +8,8 @@ Sidekiq.default_worker_options = { retry: 3 } ...@@ -8,6 +8,8 @@ Sidekiq.default_worker_options = { retry: 3 }
enable_json_logs = Gitlab.config.sidekiq.log_format == 'json' enable_json_logs = Gitlab.config.sidekiq.log_format == 'json'
Sidekiq.configure_server do |config| Sidekiq.configure_server do |config|
require 'rbtrace' if ENV['ENABLE_RBTRACE']
config.redis = queues_config_hash config.redis = queues_config_hash
config.server_middleware do |chain| config.server_middleware do |chain|
......
Rails.application.configure do |config| Rails.application.configure do |config|
Warden::Manager.after_set_user(scope: :user) do |user, auth, opts| Warden::Manager.after_set_user(scope: :user) do |user, auth, opts|
Gitlab::Auth::UniqueIpsLimiter.limit_user!(user) Gitlab::Auth::UniqueIpsLimiter.limit_user!(user)
end
Warden::Manager.before_failure(scope: :user) do |env, opts| activity = Gitlab::Auth::Activity.new(user, opts)
Gitlab::Auth::BlockedUserTracker.log_if_user_blocked(env)
case opts[:event]
when :authentication
activity.user_authenticated!
when :set_user
activity.user_authenticated!
activity.user_session_override!
when :fetch # rubocop:disable Lint/EmptyWhen
# We ignore session fetch events
else
activity.user_session_override!
end
end end
Warden::Manager.after_authentication(scope: :user) do |user, auth, opts| Warden::Manager.after_authentication(scope: :user) do |user, auth, opts|
...@@ -15,7 +25,17 @@ Rails.application.configure do |config| ...@@ -15,7 +25,17 @@ Rails.application.configure do |config|
ActiveSession.set(user, auth.request) ActiveSession.set(user, auth.request)
end end
Warden::Manager.before_logout(scope: :user) do |user, auth, opts| Warden::Manager.before_failure(scope: :user) do |env, opts|
ActiveSession.destroy(user || auth.user, auth.request.session.id) tracker = Gitlab::Auth::BlockedUserTracker.new(env)
tracker.log_blocked_user_activity! if tracker.user_blocked?
Gitlab::Auth::Activity.new(tracker.user, opts).user_authentication_failed!
end
Warden::Manager.before_logout(scope: :user) do |user_warden, auth, opts|
user = user_warden || auth.user
ActiveSession.destroy(user, auth.request.session.id)
Gitlab::Auth::Activity.new(user, opts).user_session_destroyed!
end end
end end
...@@ -46,6 +46,7 @@ Rails.application.routes.draw do ...@@ -46,6 +46,7 @@ Rails.application.routes.draw do
get 'health_check(/:checks)' => 'health_check#index', as: :health_check get 'health_check(/:checks)' => 'health_check#index', as: :health_check
scope path: '-' do scope path: '-' do
# '/-/health' implemented by BasicHealthMiddleware
get 'liveness' => 'health#liveness' get 'liveness' => 'health#liveness'
get 'readiness' => 'health#readiness' get 'readiness' => 'health#readiness'
post 'storage_check' => 'health#storage_check' post 'storage_check' => 'health#storage_check'
......
...@@ -45,6 +45,7 @@ ...@@ -45,6 +45,7 @@
- [github_import_advance_stage, 1] - [github_import_advance_stage, 1]
- [project_service, 1] - [project_service, 1]
- [delete_user, 1] - [delete_user, 1]
- [todos_destroyer, 1]
- [delete_merged_branches, 1] - [delete_merged_branches, 1]
- [authorized_projects, 1] - [authorized_projects, 1]
- [expire_build_instance_artifacts, 1] - [expire_build_instance_artifacts, 1]
......
...@@ -124,6 +124,10 @@ before_fork do |server, worker| ...@@ -124,6 +124,10 @@ before_fork do |server, worker|
end end
after_fork do |server, worker| after_fork do |server, worker|
# Unicorn clears out signals before it forks, so rbtrace won't work
# unless it is enabled after the fork.
require 'rbtrace' if ENV['ENABLE_RBTRACE']
# per-process listener ports for debugging/admin/migrations # per-process listener ports for debugging/admin/migrations
# addr = "127.0.0.1:#{9293 + worker.nr}" # addr = "127.0.0.1:#{9293 + worker.nr}"
# server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true) # server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true)
......
class AddFileFormatToCiJobArtifacts < ActiveRecord::Migration
DOWNTIME = false
def change
add_column :ci_job_artifacts, :file_format, :integer, limit: 2
end
end
...@@ -394,6 +394,7 @@ ActiveRecord::Schema.define(version: 20180722103201) do ...@@ -394,6 +394,7 @@ ActiveRecord::Schema.define(version: 20180722103201) do
t.datetime_with_timezone "expire_at" t.datetime_with_timezone "expire_at"
t.string "file" t.string "file"
t.binary "file_sha256" t.binary "file_sha256"
t.integer "file_format", limit: 2
end end
add_index "ci_job_artifacts", ["expire_at", "job_id"], name: "index_ci_job_artifacts_on_expire_at_and_job_id", using: :btree add_index "ci_job_artifacts", ["expire_at", "job_id"], name: "index_ci_job_artifacts_on_expire_at_and_job_id", using: :btree
......
...@@ -177,7 +177,8 @@ instant how code changes impact your production environment. ...@@ -177,7 +177,8 @@ instant how code changes impact your production environment.
- [Prometheus metrics](user/project/integrations/prometheus_library/metrics.md): Let Prometheus collect metrics from various services, like Kubernetes, NGINX, NGINX ingress controller, HAProxy, and Amazon Cloud Watch. - [Prometheus metrics](user/project/integrations/prometheus_library/metrics.md): Let Prometheus collect metrics from various services, like Kubernetes, NGINX, NGINX ingress controller, HAProxy, and Amazon Cloud Watch.
- [GitLab Performance Monitoring](administration/monitoring/performance/index.md): Use InfluxDB and Grafana to monitor the performance of your GitLab instance (will be eventually replaced by Prometheus). - [GitLab Performance Monitoring](administration/monitoring/performance/index.md): Use InfluxDB and Grafana to monitor the performance of your GitLab instance (will be eventually replaced by Prometheus).
- [Health check](user/admin_area/monitoring/health_check.md): GitLab provides liveness and readiness probes to indicate service health and reachability to required services. - [Health check](user/admin_area/monitoring/health_check.md): GitLab provides liveness and readiness probes to indicate service health and reachability to required services.
- [GitLab Cycle Analytics](user/project/cycle_analytics.md): Cycle Analytics measures the time it takes to go from an [idea to production](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/#from-idea-to-production-with-gitlab) for each project you have. - [GitLab Cycle Analytics](user/project/cycle_analytics.md): Cycle Analytics measures the time it takes to go from an
[idea to production](https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/#from-idea-to-production-with-gitlab) for each project you have.
## Getting started with GitLab ## Getting started with GitLab
......
...@@ -39,23 +39,11 @@ Our support team will not be able to assist on performance issues related to ...@@ -39,23 +39,11 @@ Our support team will not be able to assist on performance issues related to
file system access. file system access.
Customers and users have reported that AWS EFS does not perform well for GitLab's Customers and users have reported that AWS EFS does not perform well for GitLab's
use-case. There are several issues that can cause problems. For these reasons use-case. Workloads where many small files are written in a serialized manner, like `git`,
GitLab does not recommend using EFS with GitLab. are not well-suited for EFS. EBS with an NFS server on top will perform much better.
- EFS bases allowed IOPS on volume size. The larger the volume, the more IOPS If you do choose to use EFS, avoid storing GitLab log files (e.g. those in `/var/log/gitlab`)
are allocated. For smaller volumes, users may experience decent performance there because this will also affect performance. We recommend that the log files be
for a period of time due to 'Burst Credits'. Over a period of weeks to months
credits may run out and performance will bottom out.
- To keep "Burst Credits" available, it may be necessary to provision more space
with 'dummy data'. However, this may get expensive.
- Another option to maintain "Burst Credits" is to use FS Cache on the server so
that AWS doesn't always have to go into EFS to access files.
- For larger volumes, allocated IOPS may not be the problem. Workloads where
many small files are written in a serialized manner are not well-suited for EFS.
EBS with an NFS server on top will perform much better.
In addition, avoid storing GitLab log files (e.g. those in `/var/log/gitlab`)
because this will also affect performance. We recommend that the log files be
stored on a local volume. stored on a local volume.
For more details on another person's experience with EFS, see For more details on another person's experience with EFS, see
......
# Check Rake Tasks # Integrity Check Rake Task
## Repository Integrity ## Repository Integrity
...@@ -28,14 +28,8 @@ exactly which repositories are causing the trouble. ...@@ -28,14 +28,8 @@ exactly which repositories are causing the trouble.
### Check all GitLab repositories ### Check all GitLab repositories
>**Note:**
>
> - `gitlab:repo:check` has been deprecated in favor of `gitlab:git:fsck`
> - [Deprecated][ce-15931] in GitLab 10.4.
> - `gitlab:repo:check` will be removed in the future. [Removal issue][ce-41699]
This task loops through all repositories on the GitLab server and runs the This task loops through all repositories on the GitLab server and runs the
3 integrity checks described previously. integrity check described previously.
**Omnibus Installation** **Omnibus Installation**
...@@ -49,33 +43,6 @@ sudo gitlab-rake gitlab:git:fsck ...@@ -49,33 +43,6 @@ sudo gitlab-rake gitlab:git:fsck
sudo -u git -H bundle exec rake gitlab:git:fsck RAILS_ENV=production sudo -u git -H bundle exec rake gitlab:git:fsck RAILS_ENV=production
``` ```
### Check repositories for a specific user
This task checks all repositories that a specific user has access to. This is important
because sometimes you know which user is experiencing trouble but you don't know
which project might be the cause.
If the rake task is executed without brackets at the end, you will be prompted
to enter a username.
**Omnibus Installation**
```bash
sudo gitlab-rake gitlab:user:check_repos
sudo gitlab-rake gitlab:user:check_repos[<username>]
```
**Source Installation**
```bash
sudo -u git -H bundle exec rake gitlab:user:check_repos RAILS_ENV=production
sudo -u git -H bundle exec rake gitlab:user:check_repos[<username>] RAILS_ENV=production
```
Example output:
![gitlab:user:check_repos output](../img/raketasks/check_repos_output.png)
## Uploaded Files Integrity ## Uploaded Files Integrity
Various types of files can be uploaded to a GitLab installation by users. Various types of files can be uploaded to a GitLab installation by users.
...@@ -167,5 +134,4 @@ The LDAP check Rake task will test the bind_dn and password credentials ...@@ -167,5 +134,4 @@ The LDAP check Rake task will test the bind_dn and password credentials
executed as part of the `gitlab:check` task, but can run independently. executed as part of the `gitlab:check` task, but can run independently.
See [LDAP Rake Tasks - LDAP Check](ldap.md#check) for details. See [LDAP Rake Tasks - LDAP Check](ldap.md#check) for details.
[ce-15931]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15931 [git-fsck]: https://git-scm.com/docs/git-fsck
[ce-41699]: https://gitlab.com/gitlab-org/gitlab-ce/issues/41699
...@@ -77,7 +77,12 @@ and more. However, this is not enabled by default. To enable it, define the ...@@ -77,7 +77,12 @@ and more. However, this is not enabled by default. To enable it, define the
gitlab_rails['env'] = {"ENABLE_RBTRACE" => "1"} gitlab_rails['env'] = {"ENABLE_RBTRACE" => "1"}
``` ```
Then reconfigure the system and restart Unicorn and Sidekiq. Then reconfigure the system and restart Unicorn and Sidekiq. To run this
in Omnibus, run as root:
```ruby
/opt/gitlab/embedded/bin/ruby /opt/gitlab/embedded/bin/rbtrace
```
## Common Problems ## Common Problems
......
...@@ -15,7 +15,7 @@ Use the following rules when creating realtime solutions. ...@@ -15,7 +15,7 @@ Use the following rules when creating realtime solutions.
Use that as your polling interval. This way it is [easy for system administrators to change the Use that as your polling interval. This way it is [easy for system administrators to change the
polling rate](../../administration/polling.md). polling rate](../../administration/polling.md).
A `Poll-Interval: -1` means you should disable polling, and this must be implemented. A `Poll-Interval: -1` means you should disable polling, and this must be implemented.
1. A response with HTTP status `4XX` or `5XX` should disable polling as well. 1. A response with HTTP status different from 2XX should disable polling as well.
1. Use a common library for polling. 1. Use a common library for polling.
1. Poll on active tabs only. Please use [Visibility](https://github.com/ai/visibilityjs). 1. Poll on active tabs only. Please use [Visibility](https://github.com/ai/visibilityjs).
1. Use regular polling intervals, do not use backoff polling, or jitter, as the interval will be 1. Use regular polling intervals, do not use backoff polling, or jitter, as the interval will be
...@@ -25,15 +25,15 @@ controlled by the server. ...@@ -25,15 +25,15 @@ controlled by the server.
### Lazy Loading Images ### Lazy Loading Images
To improve the time to first render we are using lazy loading for images. This works by setting To improve the time to first render we are using lazy loading for images. This works by setting
the actual image source on the `data-src` attribute. After the HTML is rendered and JavaScript is loaded, the actual image source on the `data-src` attribute. After the HTML is rendered and JavaScript is loaded,
the value of `data-src` will be moved to `src` automatically if the image is in the current viewport. the value of `data-src` will be moved to `src` automatically if the image is in the current viewport.
* Prepare images in HTML for lazy loading by renaming the `src` attribute to `data-src` AND adding the class `lazy` * Prepare images in HTML for lazy loading by renaming the `src` attribute to `data-src` AND adding the class `lazy`
* If you are using the Rails `image_tag` helper, all images will be lazy-loaded by default unless `lazy: false` is provided. * If you are using the Rails `image_tag` helper, all images will be lazy-loaded by default unless `lazy: false` is provided.
If you are asynchronously adding content which contains lazy images then you need to call the function If you are asynchronously adding content which contains lazy images then you need to call the function
`gl.lazyLoader.searchLazyImages()` which will search for lazy images and load them if needed. `gl.lazyLoader.searchLazyImages()` which will search for lazy images and load them if needed.
But in general it should be handled automatically through a `MutationObserver` in the lazy loading function. But in general it should be handled automatically through a `MutationObserver` in the lazy loading function.
### Animations ### Animations
...@@ -97,19 +97,19 @@ bundle and included on the page. ...@@ -97,19 +97,19 @@ bundle and included on the page.
```javascript ```javascript
import initMyWidget from './my_widget'; import initMyWidget from './my_widget';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
initMyWidget(); initMyWidget();
}); });
``` ```
- **Supporting Module Placement:** - **Supporting Module Placement:**
- If a class or a module is _specific to a particular route_, try to locate - If a class or a module is _specific to a particular route_, try to locate
it close to the entry point it will be used. For instance, if it close to the entry point it will be used. For instance, if
`my_widget.js` is only imported within `pages/widget/show/index.js`, you `my_widget.js` is only imported within `pages/widget/show/index.js`, you
should place the module at `pages/widget/show/my_widget.js` and import it should place the module at `pages/widget/show/my_widget.js` and import it
with a relative path (e.g. `import initMyWidget from './my_widget';`). with a relative path (e.g. `import initMyWidget from './my_widget';`).
- If a class or module is _used by multiple routes_, place it within a - If a class or module is _used by multiple routes_, place it within a
shared directory at the closest common parent directory for the entry shared directory at the closest common parent directory for the entry
points that import it. For example, if `my_widget.js` is imported within points that import it. For example, if `my_widget.js` is imported within
......
...@@ -2,10 +2,8 @@ ...@@ -2,10 +2,8 @@
comments: false comments: false
--- ---
DANGER: This guide exists for reference of how an AWS deployment could work. > **Note**: We **do not** recommend using the AWS Elastic File System (EFS), as it can result
We are currently seeing very slow EFS access performance which causes GitLab to in [significantly degraded performance](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/doc/administration/high_availability/nfs.md#aws-elastic-file-system).
be 5-10x slower than using NFS or Local disk. We _do not_ recommend follow this
guide at this time.
# High Availability on AWS # High Availability on AWS
......
...@@ -20,14 +20,24 @@ To access monitoring resources, the client IP needs to be included in a whitelis ...@@ -20,14 +20,24 @@ To access monitoring resources, the client IP needs to be included in a whitelis
[Read how to add IPs to a whitelist for the monitoring endpoints][admin]. [Read how to add IPs to a whitelist for the monitoring endpoints][admin].
## Using the endpoint ## Using the endpoints
With default whitelist settings, the probes can be accessed from localhost: With default whitelist settings, the probes can be accessed from localhost:
- `http://localhost/-/health`
- `http://localhost/-/readiness` - `http://localhost/-/readiness`
- `http://localhost/-/liveness` - `http://localhost/-/liveness`
which will then provide a report of system health in JSON format.
The first endpoint, `/-/health/`, only checks whether the application server is running. It does
-not verify the database or other services are running. A successful response will return
a 200 status code with the following message:
```
GitLab OK
```
The readiness and liveness probes will provide a report of system health in JSON format.
Readiness example output: Readiness example output:
...@@ -42,12 +52,6 @@ Readiness example output: ...@@ -42,12 +52,6 @@ Readiness example output:
"shared_state_check" : { "shared_state_check" : {
"status" : "ok" "status" : "ok"
}, },
"fs_shards_check" : {
"labels" : {
"shard" : "default"
},
"status" : "ok"
},
"db_check" : { "db_check" : {
"status" : "ok" "status" : "ok"
}, },
...@@ -61,9 +65,6 @@ Liveness example output: ...@@ -61,9 +65,6 @@ Liveness example output:
``` ```
{ {
"fs_shards_check" : {
"status" : "ok"
},
"cache_check" : { "cache_check" : {
"status" : "ok" "status" : "ok"
}, },
......
...@@ -1236,7 +1236,13 @@ module API ...@@ -1236,7 +1236,13 @@ module API
end end
class Artifacts < Grape::Entity class Artifacts < Grape::Entity
expose :name, :untracked, :paths, :when, :expire_in expose :name
expose :untracked
expose :paths
expose :when
expose :expire_in
expose :artifact_type
expose :artifact_format
end end
class Cache < Grape::Entity class Cache < Grape::Entity
......
...@@ -75,7 +75,10 @@ module API ...@@ -75,7 +75,10 @@ module API
member = source.members.find_by(user_id: params[:user_id]) member = source.members.find_by(user_id: params[:user_id])
conflict!('Member already exists') if member conflict!('Member already exists') if member
member = source.add_user(params[:user_id], params[:access_level], current_user: current_user, expires_at: params[:expires_at]) user = User.find_by_id(params[:user_id])
not_found!('User') unless user
member = source.add_user(user, params[:access_level], current_user: current_user, expires_at: params[:expires_at])
if !member if !member
not_allowed! # This currently can only be reached in EE not_allowed! # This currently can only be reached in EE
......
...@@ -13,6 +13,10 @@ module API ...@@ -13,6 +13,10 @@ module API
# EE::API::Projects would override this helper # EE::API::Projects would override this helper
end end
params :optional_update_params_ee do
# EE::API::Projects would override this helper
end
# EE::API::Projects would override this method # EE::API::Projects would override this method
def apply_filters(projects) def apply_filters(projects)
projects = projects.with_issues_available_for_user(current_user) if params[:with_issues_enabled] projects = projects.with_issues_available_for_user(current_user) if params[:with_issues_enabled]
...@@ -21,6 +25,37 @@ module API ...@@ -21,6 +25,37 @@ module API
projects projects
end end
def verify_update_project_attrs!(project, attrs)
end
end
def self.update_params_at_least_one_of
[
:jobs_enabled,
:resolve_outdated_diff_discussions,
:ci_config_path,
:container_registry_enabled,
:default_branch,
:description,
:issues_enabled,
:lfs_enabled,
:merge_requests_enabled,
:merge_method,
:name,
:only_allow_merge_if_all_discussions_are_resolved,
:only_allow_merge_if_pipeline_succeeds,
:path,
:printing_merge_request_link_enabled,
:public_builds,
:request_access_enabled,
:shared_runners_enabled,
:snippets_enabled,
:tag_list,
:visibility,
:wiki_enabled,
:avatar
]
end end
helpers do helpers do
...@@ -252,39 +287,13 @@ module API ...@@ -252,39 +287,13 @@ module API
success Entities::Project success Entities::Project
end end
params do params do
# CE
at_least_one_of_ce =
[
:jobs_enabled,
:resolve_outdated_diff_discussions,
:ci_config_path,
:container_registry_enabled,
:default_branch,
:description,
:issues_enabled,
:lfs_enabled,
:merge_requests_enabled,
:merge_method,
:name,
:only_allow_merge_if_all_discussions_are_resolved,
:only_allow_merge_if_pipeline_succeeds,
:path,
:printing_merge_request_link_enabled,
:public_builds,
:request_access_enabled,
:shared_runners_enabled,
:snippets_enabled,
:tag_list,
:visibility,
:wiki_enabled,
:avatar
]
optional :name, type: String, desc: 'The name of the project' optional :name, type: String, desc: 'The name of the project'
optional :default_branch, type: String, desc: 'The default branch of the project' optional :default_branch, type: String, desc: 'The default branch of the project'
optional :path, type: String, desc: 'The path of the repository' optional :path, type: String, desc: 'The path of the repository'
use :optional_project_params use :optional_project_params
at_least_one_of(*at_least_one_of_ce)
at_least_one_of(*::API::Projects.update_params_at_least_one_of)
end end
put ':id' do put ':id' do
authorize_admin_project authorize_admin_project
...@@ -294,6 +303,8 @@ module API ...@@ -294,6 +303,8 @@ module API
attrs = translate_params_for_compatibility(attrs) attrs = translate_params_for_compatibility(attrs)
verify_update_project_attrs!(user_project, attrs)
result = ::Projects::UpdateService.new(user_project, current_user, attrs).execute result = ::Projects::UpdateService.new(user_project, current_user, attrs).execute
if result[:status] == :success if result[:status] == :success
......
...@@ -109,7 +109,7 @@ module API ...@@ -109,7 +109,7 @@ module API
if result.valid? if result.valid?
if result.build if result.build
Gitlab::Metrics.add_event(:build_found) Gitlab::Metrics.add_event(:build_found)
present result.build, with: Entities::JobRequest::Response present Ci::BuildRunnerPresenter.new(result.build), with: Entities::JobRequest::Response
else else
Gitlab::Metrics.add_event(:build_not_found) Gitlab::Metrics.add_event(:build_not_found)
header 'X-GitLab-Last-Update', new_update header 'X-GitLab-Last-Update', new_update
...@@ -231,6 +231,10 @@ module API ...@@ -231,6 +231,10 @@ module API
requires :id, type: Integer, desc: %q(Job's ID) requires :id, type: Integer, desc: %q(Job's ID)
optional :token, type: String, desc: %q(Job's authentication token) optional :token, type: String, desc: %q(Job's authentication token)
optional :expire_in, type: String, desc: %q(Specify when artifacts should expire) optional :expire_in, type: String, desc: %q(Specify when artifacts should expire)
optional :artifact_type, type: String, desc: %q(The type of artifact),
default: 'archive', values: Ci::JobArtifact.file_types.keys
optional :artifact_format, type: String, desc: %q(The format of artifact),
default: 'zip', values: Ci::JobArtifact.file_formats.keys
optional 'file.path', type: String, desc: %q(path to locally stored body (generated by Workhorse)) optional 'file.path', type: String, desc: %q(path to locally stored body (generated by Workhorse))
optional 'file.name', type: String, desc: %q(real filename as send in Content-Disposition (generated by Workhorse)) optional 'file.name', type: String, desc: %q(real filename as send in Content-Disposition (generated by Workhorse))
optional 'file.type', type: String, desc: %q(real content type as send in Content-Type (generated by Workhorse)) optional 'file.type', type: String, desc: %q(real content type as send in Content-Type (generated by Workhorse))
...@@ -254,29 +258,29 @@ module API ...@@ -254,29 +258,29 @@ module API
bad_request!('Missing artifacts file!') unless artifacts bad_request!('Missing artifacts file!') unless artifacts
file_to_large! unless artifacts.size < max_artifacts_size file_to_large! unless artifacts.size < max_artifacts_size
bad_request!("Already uploaded") if job.job_artifacts_archive
expire_in = params['expire_in'] || expire_in = params['expire_in'] ||
Gitlab::CurrentSettings.current_application_settings.default_artifacts_expire_in Gitlab::CurrentSettings.current_application_settings.default_artifacts_expire_in
job.build_job_artifacts_archive( job.job_artifacts.build(
project: job.project, project: job.project,
file: artifacts, file: artifacts,
file_type: :archive, file_type: params['artifact_type'],
file_format: params['artifact_format'],
file_sha256: artifacts.sha256, file_sha256: artifacts.sha256,
expire_in: expire_in) expire_in: expire_in)
if metadata if metadata
job.build_job_artifacts_metadata( job.job_artifacts.build(
project: job.project, project: job.project,
file: metadata, file: metadata,
file_type: :metadata, file_type: :metadata,
file_format: :gzip,
file_sha256: metadata.sha256, file_sha256: metadata.sha256,
expire_in: expire_in) expire_in: expire_in)
end end
if job.update(artifacts_expire_in: expire_in) if job.update(artifacts_expire_in: expire_in)
present job, with: Entities::JobRequest::Response present Ci::BuildRunnerPresenter.new(job), with: Entities::JobRequest::Response
else else
render_validation_error!(job) render_validation_error!(job)
end end
......
This diff is collapsed.
module Gitlab
module Auth
##
# Metrics and logging for user authentication activity.
#
class Activity
extend Gitlab::Utils::StrongMemoize
COUNTERS = {
user_authenticated: 'Counter of successful authentication events',
user_unauthenticated: 'Counter of authentication failures',
user_not_found: 'Counter of failed log-ins when user is unknown',
user_password_invalid: 'Counter of failed log-ins with invalid password',
user_session_override: 'Counter of manual log-ins and sessions overrides',
user_session_destroyed: 'Counter of user sessions being destroyed',
user_two_factor_authenticated: 'Counter of two factor authentications',
user_sessionless_authentication: 'Counter of sessionless authentications',
user_blocked: 'Counter of sign in attempts when user is blocked'
}.freeze
def initialize(user, opts)
@user = user
@opts = opts
end
def user_authentication_failed!
self.class.user_unauthenticated_counter_increment!
case @opts[:message]
when :not_found_in_database
self.class.user_not_found_counter_increment!
when :invalid
self.class.user_password_invalid_counter_increment!
end
self.class.user_blocked_counter_increment! if @user&.blocked?
end
def user_authenticated!
self.class.user_authenticated_counter_increment!
end
def user_session_override!
self.class.user_session_override_counter_increment!
case @opts[:message]
when :two_factor_authenticated
self.class.user_two_factor_authenticated_counter_increment!
when :sessionless_sign_in
self.class.user_sessionless_authentication_counter_increment!
end
end
def user_session_destroyed!
self.class.user_session_destroyed_counter_increment!
end
def self.each_counter
COUNTERS.each_pair do |metric, description|
yield "#{metric}_counter", metric, description
end
end
each_counter do |counter, metric, description|
define_singleton_method(counter) do
strong_memoize(counter) do
Gitlab::Metrics.counter("gitlab_auth_#{metric}_total".to_sym, description)
end
end
define_singleton_method("#{counter}_increment!") do
public_send(counter).increment # rubocop:disable GitlabSecurity/PublicSend
end
end
end
end
end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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