Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-03-09 15:08:59 +00:00
parent b90d8b54a4
commit 6f2b1c32f3
123 changed files with 860 additions and 244 deletions

View file

@ -1 +1 @@
059a82773ec2b5afc115442270d663cccc68451c d1f4340a1123d2436c7544d6ba64635c4c8f6104

View file

@ -41,10 +41,17 @@ export default {
} }
}, },
}, },
mounted() {
// If this is true, we need to present the captcha modal to the user.
// When the modal is shown we will also initialize and render the form.
if (this.needsCaptchaResponse) {
this.$refs.modal.show();
}
},
methods: { methods: {
emitReceivedCaptchaResponse(captchaResponse) { emitReceivedCaptchaResponse(captchaResponse) {
this.$emit('receivedCaptchaResponse', captchaResponse);
this.$refs.modal.hide(); this.$refs.modal.hide();
this.$emit('receivedCaptchaResponse', captchaResponse);
}, },
emitNullReceivedCaptchaResponse() { emitNullReceivedCaptchaResponse() {
this.emitReceivedCaptchaResponse(null); this.emitReceivedCaptchaResponse(null);
@ -103,6 +110,7 @@ export default {
:action-cancel="{ text: __('Cancel') }" :action-cancel="{ text: __('Cancel') }"
@shown="shown" @shown="shown"
@hide="hide" @hide="hide"
@hidden="$emit('hidden')"
> >
<div ref="captcha"></div> <div ref="captcha"></div>
<p>{{ __('We want to be sure it is you, please confirm you are not a robot.') }}</p> <p>{{ __('We want to be sure it is you, please confirm you are not a robot.') }}</p>

View file

@ -0,0 +1,37 @@
const supportedMethods = ['patch', 'post', 'put'];
export function registerCaptchaModalInterceptor(axios) {
return axios.interceptors.response.use(
(response) => {
return response;
},
(err) => {
if (
supportedMethods.includes(err?.config?.method) &&
err?.response?.data?.needs_captcha_response
) {
const { data } = err.response;
const captchaSiteKey = data.captcha_site_key;
const spamLogId = data.spam_log_id;
// eslint-disable-next-line promise/no-promise-in-callback
return import('~/captcha/wait_for_captcha_to_be_solved')
.then(({ waitForCaptchaToBeSolved }) => waitForCaptchaToBeSolved(captchaSiteKey))
.then((captchaResponse) => {
const errConfig = err.config;
const originalData = JSON.parse(errConfig.data);
return axios({
method: errConfig.method,
url: errConfig.url,
data: {
...originalData,
captcha_response: captchaResponse,
spam_log_id: spamLogId,
},
});
});
}
return Promise.reject(err);
},
);
}

View file

@ -0,0 +1,10 @@
import { __ } from '~/locale';
class UnsolvedCaptchaError extends Error {
constructor(message) {
super(message || __('You must solve the CAPTCHA in order to submit'));
this.name = 'UnsolvedCaptchaError';
}
}
export default UnsolvedCaptchaError;

View file

@ -0,0 +1,53 @@
import Vue from 'vue';
import CaptchaModal from '~/captcha/captcha_modal.vue';
import UnsolvedCaptchaError from '~/captcha/unsolved_captcha_error';
/**
* Opens a Captcha Modal with provided captchaSiteKey.
*
* Returns a Promise which resolves if the captcha is solved correctly, and rejects
* if the captcha process is aborted.
*
* @param captchaSiteKey
* @returns {Promise}
*/
export function waitForCaptchaToBeSolved(captchaSiteKey) {
return new Promise((resolve, reject) => {
let captchaModalElement = document.createElement('div');
document.body.append(captchaModalElement);
let captchaModalVueInstance = new Vue({
el: captchaModalElement,
render: (createElement) => {
return createElement(CaptchaModal, {
props: {
captchaSiteKey,
needsCaptchaResponse: true,
},
on: {
hidden: () => {
// Cleaning up the modal from the DOM
captchaModalVueInstance.$destroy();
captchaModalVueInstance.$el.remove();
captchaModalElement.remove();
captchaModalElement = null;
captchaModalVueInstance = null;
},
receivedCaptchaResponse: (captchaResponse) => {
if (captchaResponse) {
resolve(captchaResponse);
} else {
// reject the promise with a custom exception, allowing consuming apps to
// adjust their error handling, if appropriate.
const error = new UnsolvedCaptchaError();
reject(error);
}
},
},
});
},
});
});
}

View file

@ -5,7 +5,6 @@ import { deprecatedCreateFlash as createFlash } from '~/flash';
import Poll from '~/lib/utils/poll'; import Poll from '~/lib/utils/poll';
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl } from '~/lib/utils/url_utility';
import { __, s__, sprintf } from '~/locale'; import { __, s__, sprintf } from '~/locale';
import recaptchaModalImplementor from '~/vue_shared/mixins/recaptcha_modal_implementor';
import { IssuableStatus, IssuableStatusText, IssuableType } from '../constants'; import { IssuableStatus, IssuableStatusText, IssuableType } from '../constants';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import Service from '../services/index'; import Service from '../services/index';
@ -25,7 +24,6 @@ export default {
formComponent, formComponent,
PinnedLinks, PinnedLinks,
}, },
mixins: [recaptchaModalImplementor],
props: { props: {
endpoint: { endpoint: {
required: true, required: true,
@ -250,6 +248,7 @@ export default {
}, },
}, },
created() { created() {
this.flashContainer = null;
this.service = new Service(this.endpoint); this.service = new Service(this.endpoint);
this.poll = new Poll({ this.poll = new Poll({
resource: this.service, resource: this.service,
@ -289,7 +288,7 @@ export default {
methods: { methods: {
handleBeforeUnloadEvent(e) { handleBeforeUnloadEvent(e) {
const event = e; const event = e;
if (this.showForm && this.issueChanged && !this.showRecaptcha) { if (this.showForm && this.issueChanged) {
event.returnValue = __('Are you sure you want to lose your issue information?'); event.returnValue = __('Are you sure you want to lose your issue information?');
} }
return undefined; return undefined;
@ -347,10 +346,10 @@ export default {
}, },
updateIssuable() { updateIssuable() {
this.clearFlash();
return this.service return this.service
.updateIssuable(this.store.formState) .updateIssuable(this.store.formState)
.then((res) => res.data) .then((res) => res.data)
.then((data) => this.checkForSpam(data))
.then((data) => { .then((data) => {
if (!window.location.pathname.includes(data.web_url)) { if (!window.location.pathname.includes(data.web_url)) {
visitUrl(data.web_url); visitUrl(data.web_url);
@ -361,30 +360,24 @@ export default {
eventHub.$emit('close.form'); eventHub.$emit('close.form');
}) })
.catch((error = {}) => { .catch((error = {}) => {
const { name, response = {} } = error; const { message, response = {} } = error;
if (name === 'SpamError') { this.store.setFormState({
this.openRecaptcha(); updateLoading: false,
} else { });
let errMsg = this.defaultErrorMessage;
if (response.data && response.data.errors) { let errMsg = this.defaultErrorMessage;
errMsg += `. ${response.data.errors.join(' ')}`;
}
createFlash(errMsg); if (response.data && response.data.errors) {
errMsg += `. ${response.data.errors.join(' ')}`;
} else if (message) {
errMsg += `. ${message}`;
} }
this.flashContainer = createFlash(errMsg);
}); });
}, },
closeRecaptchaModal() {
this.store.setFormState({
updateLoading: false,
});
this.closeRecaptcha();
},
deleteIssuable(payload) { deleteIssuable(payload) {
return this.service return this.service
.deleteIssuable(payload) .deleteIssuable(payload)
@ -409,6 +402,13 @@ export default {
showStickyHeader() { showStickyHeader() {
this.isStickyHeaderShowing = true; this.isStickyHeaderShowing = true;
}, },
clearFlash() {
if (this.flashContainer) {
this.flashContainer.style.display = 'none';
this.flashContainer = null;
}
},
}, },
}; };
</script> </script>
@ -430,13 +430,6 @@ export default {
:enable-autocomplete="enableAutocomplete" :enable-autocomplete="enableAutocomplete"
:issuable-type="issuableType" :issuable-type="issuableType"
/> />
<recaptcha-modal
v-show="showRecaptcha"
ref="recaptchaModal"
:html="recaptchaHTML"
@close="closeRecaptchaModal"
/>
</div> </div>
<div v-else> <div v-else>
<title-component <title-component

View file

@ -1,9 +1,11 @@
import { registerCaptchaModalInterceptor } from '~/captcha/captcha_modal_axios_interceptor';
import axios from '../../lib/utils/axios_utils'; import axios from '../../lib/utils/axios_utils';
export default class Service { export default class Service {
constructor(endpoint) { constructor(endpoint) {
this.endpoint = `${endpoint}.json`; this.endpoint = `${endpoint}.json`;
this.realtimeEndpoint = `${endpoint}/realtime_changes`; this.realtimeEndpoint = `${endpoint}/realtime_changes`;
registerCaptchaModalInterceptor(axios);
} }
getData() { getData() {

View file

@ -19,6 +19,7 @@ const httpStatusCodes = {
UNAUTHORIZED: 401, UNAUTHORIZED: 401,
FORBIDDEN: 403, FORBIDDEN: 403,
NOT_FOUND: 404, NOT_FOUND: 404,
METHOD_NOT_ALLOWED: 405,
CONFLICT: 409, CONFLICT: 409,
GONE: 410, GONE: 410,
UNPROCESSABLE_ENTITY: 422, UNPROCESSABLE_ENTITY: 422,

View file

@ -21,7 +21,7 @@ export default {
GlSprintf, GlSprintf,
}, },
props: { props: {
defaultBranch: { currentBranch: {
type: String, type: String,
required: false, required: false,
default: '', default: '',
@ -40,23 +40,23 @@ export default {
data() { data() {
return { return {
message: this.defaultMessage, message: this.defaultMessage,
branch: this.defaultBranch,
openMergeRequest: false, openMergeRequest: false,
targetBranch: this.currentBranch,
}; };
}, },
computed: { computed: {
isDefaultBranch() { isCurrentBranchTarget() {
return this.branch === this.defaultBranch; return this.targetBranch === this.currentBranch;
}, },
submitDisabled() { submitDisabled() {
return !(this.message && this.branch); return !(this.message && this.targetBranch);
}, },
}, },
methods: { methods: {
onSubmit() { onSubmit() {
this.$emit('submit', { this.$emit('submit', {
message: this.message, message: this.message,
branch: this.branch, targetBranch: this.targetBranch,
openMergeRequest: this.openMergeRequest, openMergeRequest: this.openMergeRequest,
}); });
}, },
@ -100,12 +100,12 @@ export default {
> >
<gl-form-input <gl-form-input
id="target-branch-field" id="target-branch-field"
v-model="branch" v-model="targetBranch"
class="gl-font-monospace!" class="gl-font-monospace!"
required required
/> />
<gl-form-checkbox <gl-form-checkbox
v-if="!isDefaultBranch" v-if="!isCurrentBranchTarget"
v-model="openMergeRequest" v-model="openMergeRequest"
data-testid="new-mr-checkbox" data-testid="new-mr-checkbox"
class="gl-mt-3" class="gl-mt-3"

View file

@ -4,6 +4,7 @@ import { __, s__, sprintf } from '~/locale';
import { COMMIT_FAILURE, COMMIT_SUCCESS } from '../../constants'; import { COMMIT_FAILURE, COMMIT_SUCCESS } from '../../constants';
import commitCIFile from '../../graphql/mutations/commit_ci_file.mutation.graphql'; import commitCIFile from '../../graphql/mutations/commit_ci_file.mutation.graphql';
import getCommitSha from '../../graphql/queries/client/commit_sha.graphql'; import getCommitSha from '../../graphql/queries/client/commit_sha.graphql';
import getCurrentBranch from '../../graphql/queries/client/current_branch.graphql';
import CommitForm from './commit_form.vue'; import CommitForm from './commit_form.vue';
@ -21,7 +22,7 @@ export default {
components: { components: {
CommitForm, CommitForm,
}, },
inject: ['projectFullPath', 'ciConfigPath', 'defaultBranch', 'newMergeRequestPath'], inject: ['projectFullPath', 'ciConfigPath', 'newMergeRequestPath'],
props: { props: {
ciFileContent: { ciFileContent: {
type: String, type: String,
@ -38,6 +39,9 @@ export default {
commitSha: { commitSha: {
query: getCommitSha, query: getCommitSha,
}, },
currentBranch: {
query: getCurrentBranch,
},
}, },
computed: { computed: {
defaultCommitMessage() { defaultCommitMessage() {
@ -49,13 +53,13 @@ export default {
const url = mergeUrlParams( const url = mergeUrlParams(
{ {
[MR_SOURCE_BRANCH]: sourceBranch, [MR_SOURCE_BRANCH]: sourceBranch,
[MR_TARGET_BRANCH]: this.defaultBranch, [MR_TARGET_BRANCH]: this.currentBranch,
}, },
this.newMergeRequestPath, this.newMergeRequestPath,
); );
redirectTo(url); redirectTo(url);
}, },
async onCommitSubmit({ message, branch, openMergeRequest }) { async onCommitSubmit({ message, targetBranch, openMergeRequest }) {
this.isSaving = true; this.isSaving = true;
try { try {
@ -67,8 +71,8 @@ export default {
mutation: commitCIFile, mutation: commitCIFile,
variables: { variables: {
projectPath: this.projectFullPath, projectPath: this.projectFullPath,
branch, branch: targetBranch,
startBranch: this.defaultBranch, startBranch: this.currentBranch,
message, message,
filePath: this.ciConfigPath, filePath: this.ciConfigPath,
content: this.ciFileContent, content: this.ciFileContent,
@ -86,7 +90,7 @@ export default {
if (errors?.length) { if (errors?.length) {
this.$emit('showError', { type: COMMIT_FAILURE, reasons: errors }); this.$emit('showError', { type: COMMIT_FAILURE, reasons: errors });
} else if (openMergeRequest) { } else if (openMergeRequest) {
this.redirectToNewMergeRequest(branch); this.redirectToNewMergeRequest(targetBranch);
} else { } else {
this.$emit('commit', { type: COMMIT_SUCCESS }); this.$emit('commit', { type: COMMIT_SUCCESS });
} }
@ -105,7 +109,7 @@ export default {
<template> <template>
<commit-form <commit-form
:default-branch="defaultBranch" :current-branch="currentBranch"
:default-message="defaultCommitMessage" :default-message="defaultCommitMessage"
:is-saving="isSaving" :is-saving="isSaving"
@cancel="onCommitCancel" @cancel="onCommitCancel"

View file

@ -0,0 +1,3 @@
query getCurrentBranch {
currentBranch @client
}

View file

@ -22,6 +22,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
const { const {
// Add to apollo cache as it can be updated by future queries // Add to apollo cache as it can be updated by future queries
commitSha, commitSha,
initialBranchName,
// Add to provide/inject API for static values // Add to provide/inject API for static values
ciConfigPath, ciConfigPath,
defaultBranch, defaultBranch,
@ -42,6 +43,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
apolloProvider.clients.defaultClient.cache.writeData({ apolloProvider.clients.defaultClient.cache.writeData({
data: { data: {
currentBranch: initialBranchName || defaultBranch,
commitSha, commitSha,
}, },
}); });

View file

@ -9,6 +9,7 @@ import PipelineEditorEmptyState from './components/ui/pipeline_editor_empty_stat
import { COMMIT_FAILURE, COMMIT_SUCCESS, DEFAULT_FAILURE, LOAD_FAILURE_UNKNOWN } from './constants'; import { COMMIT_FAILURE, COMMIT_SUCCESS, DEFAULT_FAILURE, LOAD_FAILURE_UNKNOWN } from './constants';
import getBlobContent from './graphql/queries/blob_content.graphql'; import getBlobContent from './graphql/queries/blob_content.graphql';
import getCiConfigData from './graphql/queries/ci_config.graphql'; import getCiConfigData from './graphql/queries/ci_config.graphql';
import getCurrentBranch from './graphql/queries/client/current_branch.graphql';
import PipelineEditorHome from './pipeline_editor_home.vue'; import PipelineEditorHome from './pipeline_editor_home.vue';
export default { export default {
@ -23,9 +24,6 @@ export default {
ciConfigPath: { ciConfigPath: {
default: '', default: '',
}, },
defaultBranch: {
default: null,
},
projectFullPath: { projectFullPath: {
default: '', default: '',
}, },
@ -58,7 +56,7 @@ export default {
return { return {
projectPath: this.projectFullPath, projectPath: this.projectFullPath,
path: this.ciConfigPath, path: this.ciConfigPath,
ref: this.defaultBranch, ref: this.currentBranch,
}; };
}, },
update(data) { update(data) {
@ -97,6 +95,9 @@ export default {
this.reportFailure(LOAD_FAILURE_UNKNOWN); this.reportFailure(LOAD_FAILURE_UNKNOWN);
}, },
}, },
currentBranch: {
query: getCurrentBranch,
},
}, },
computed: { computed: {
hasUnsavedChanges() { hasUnsavedChanges() {

View file

@ -221,7 +221,10 @@ export default {
this.captchaResponse = captchaResponse; this.captchaResponse = captchaResponse;
if (this.captchaResponse) { if (this.captchaResponse) {
// If the user solved the captcha resubmit the form. // If the user solved the captcha, resubmit the form.
// NOTE: we do not need to clear out the captchaResponse and spamLogId
// data values after submit, because this component always does a full page reload.
// Otherwise, we would need to.
this.handleFormSubmit(); this.handleFormSubmit();
} else { } else {
// If the user didn't solve the captcha (e.g. they just closed the modal), // If the user didn't solve the captcha (e.g. they just closed the modal),

View file

@ -463,7 +463,7 @@ class ApplicationController < ActionController::Base
feature_category: feature_category) do feature_category: feature_category) do
yield yield
ensure ensure
@current_context = Labkit::Context.current.to_h @current_context = Gitlab::ApplicationContext.current
end end
end end

View file

@ -2,6 +2,7 @@
module SpammableActions module SpammableActions
extend ActiveSupport::Concern extend ActiveSupport::Concern
include Spam::Concerns::HasSpamActionResponseFields
included do included do
before_action :authorize_submit_spammable!, only: :mark_as_spam before_action :authorize_submit_spammable!, only: :mark_as_spam
@ -25,14 +26,20 @@ module SpammableActions
respond_to do |format| respond_to do |format|
format.html do format.html do
# NOTE: format.html is still used by issue create, and uses the legacy HAML
# `_recaptcha_form.html.haml` rendered via the `projects/issues/verify` template.
render :verify render :verify
end end
format.json do format.json do
locals = { spammable: spammable, script: false, has_submit: false } # format.json is used by all new Vue-based CAPTCHA implementations, which
recaptcha_html = render_to_string(partial: 'shared/recaptcha_form', formats: :html, locals: locals) # handle all of the CAPTCHA form rendering on the client via the Pajamas-based
# app/assets/javascripts/captcha/captcha_modal.vue
render json: { recaptcha_html: recaptcha_html } # NOTE: "409 - Conflict" seems to be the most appropriate HTTP status code for a response
# which requires a CAPTCHA to be solved in order for the request to be resubmitted.
# See https://stackoverflow.com/q/26547466/25192
render json: spam_action_response_fields(spammable), status: :conflict
end end
end end
else else
@ -58,7 +65,7 @@ module SpammableActions
# After this newer GraphQL/JS API process is fully supported by the backend, we can remove the # After this newer GraphQL/JS API process is fully supported by the backend, we can remove the
# check for the 'g-recaptcha-response' field and other HTML/HAML form-specific support. # check for the 'g-recaptcha-response' field and other HTML/HAML form-specific support.
captcha_response = params['g-recaptcha-response'] captcha_response = params['g-recaptcha-response'] || params[:captcha_response]
{ {
request: request, request: request,

View file

@ -50,6 +50,8 @@ class Projects::CommitController < Projects::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def pipelines def pipelines
set_pipeline_feature_flag
@pipelines = @commit.pipelines.order(id: :desc) @pipelines = @commit.pipelines.order(id: :desc)
@pipelines = @pipelines.where(ref: params[:ref]).page(params[:page]).per(30) if params[:ref] @pipelines = @pipelines.where(ref: params[:ref]).page(params[:page]).per(30) if params[:ref]
@ -124,6 +126,10 @@ class Projects::CommitController < Projects::ApplicationController
private private
def set_pipeline_feature_flag
push_frontend_feature_flag(:new_pipelines_table, @project, default_enabled: :yaml)
end
def create_new_branch? def create_new_branch?
params[:create_merge_request].present? || !can?(current_user, :push_code, @project) params[:create_merge_request].present? || !can?(current_user, :push_code, @project)
end end

View file

@ -8,7 +8,7 @@ module Mutations
ADMIN_MESSAGE = 'You must be an admin to use this mutation' ADMIN_MESSAGE = 'You must be an admin to use this mutation'
Labkit::Context::KNOWN_KEYS.each do |key| Gitlab::ApplicationContext::KNOWN_KEYS.each do |key|
argument key, argument key,
GraphQL::STRING_TYPE, GraphQL::STRING_TYPE,
required: false, required: false,

View file

@ -89,7 +89,7 @@ class NotifyPreview < ActionMailer::Preview
end end
def merge_request_status_email def merge_request_status_email
Notify.merge_request_status_email(user.id, merge_request.id, 'closed', user.id).message Notify.merge_request_status_email(user.id, merge_request.id, 'reopened', user.id).message
end end
def merged_merge_request_email def merged_merge_request_email

View file

@ -6,6 +6,8 @@ class WebHookLog < ApplicationRecord
include DeleteWithLimit include DeleteWithLimit
include CreatedAtFilterable include CreatedAtFilterable
self.primary_key = :id
belongs_to :web_hook belongs_to :web_hook
serialize :request_headers, Hash # rubocop:disable Cop/ActiveRecordSerialize serialize :request_headers, Hash # rubocop:disable Cop/ActiveRecordSerialize

View file

@ -0,0 +1,17 @@
# frozen_string_literal: true
# This model is not yet intended to be used.
# It is in a transitioning phase while we are partitioning
# the web_hook_logs table on the database-side.
# Please refer to https://gitlab.com/groups/gitlab-org/-/epics/5558
# for details.
# rubocop:disable Gitlab/NamespacedClass: This is a temporary class with no relevant namespace
# WebHook, WebHookLog and all hooks are defined outside of a namespace
class WebHookLogPartitioned < ApplicationRecord
include PartitionedTable
self.table_name = 'web_hook_logs_part_0c5294f417'
self.primary_key = :id
partitioned_by :created_at, strategy: :monthly
end

View file

@ -30,7 +30,6 @@ class Note < ApplicationRecord
# Aliases to make application_helper#edited_time_ago_with_tooltip helper work properly with notes. # Aliases to make application_helper#edited_time_ago_with_tooltip helper work properly with notes.
# See https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/10392/diffs#note_28719102 # See https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/10392/diffs#note_28719102
alias_attribute :last_edited_at, :updated_at
alias_attribute :last_edited_by, :updated_by alias_attribute :last_edited_by, :updated_by
# Attribute containing rendered and redacted Markdown as generated by # Attribute containing rendered and redacted Markdown as generated by
@ -349,7 +348,13 @@ class Note < ApplicationRecord
!system? !system?
end end
# Since we're using `updated_at` as `last_edited_at`, it could be touched by transforming / resolving a note. # We used `last_edited_at` as an alias of `updated_at` before.
# This makes it compatible with the previous way without data migration.
def last_edited_at
super || updated_at
end
# Since we used `updated_at` as `last_edited_at`, it could be touched by transforming / resolving a note.
# This makes sure it is only marked as edited when the note body is updated. # This makes sure it is only marked as edited when the note body is updated.
def edited? def edited?
return false if updated_by.blank? return false if updated_by.blank?

View file

@ -7,12 +7,7 @@ module Notes
old_mentioned_users = note.mentioned_users(current_user).to_a old_mentioned_users = note.mentioned_users(current_user).to_a
note.assign_attributes(params.merge(updated_by: current_user)) note.assign_attributes(params)
note.with_transaction_returning_status do
update_confidentiality(note)
note.save
end
track_note_edit_usage_for_issues(note) if note.for_issue? track_note_edit_usage_for_issues(note) if note.for_issue?
track_note_edit_usage_for_merge_requests(note) if note.for_merge_request? track_note_edit_usage_for_merge_requests(note) if note.for_merge_request?
@ -28,6 +23,15 @@ module Notes
note.note = content note.note = content
end end
if note.note_changed?
note.assign_attributes(last_edited_at: Time.current, updated_by: current_user)
end
note.with_transaction_returning_status do
update_confidentiality(note)
note.save
end
unless only_commands || note.for_personal_snippet? unless only_commands || note.for_personal_snippet?
note.create_new_cross_references!(current_user) note.create_new_cross_references!(current_user)

View file

@ -8,4 +8,4 @@
.form-text.text-muted .form-text.text-muted
Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area. Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area.
= f.submit 'Save changes', class: "gl-button btn btn-success" = f.submit 'Save changes', class: "gl-button btn btn-confirm"

View file

@ -65,4 +65,4 @@
= render_if_exists 'admin/application_settings/updating_name_disabled_for_users', form: f = render_if_exists 'admin/application_settings/updating_name_disabled_for_users', form: f
= render_if_exists 'admin/application_settings/availability_on_namespace_setting', form: f = render_if_exists 'admin/application_settings/availability_on_namespace_setting', form: f
= f.submit _('Save changes'), class: 'gl-button btn btn-success qa-save-changes-button' = f.submit _('Save changes'), class: 'gl-button btn btn-confirm qa-save-changes-button'

View file

@ -68,4 +68,4 @@
= _("The default CI configuration path for new projects.").html_safe = _("The default CI configuration path for new projects.").html_safe
= link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'custom-cicd-configuration-path'), target: '_blank' = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'custom-cicd-configuration-path'), target: '_blank'
= f.submit _('Save changes'), class: "gl-button btn btn-success" = f.submit _('Save changes'), class: "gl-button btn btn-confirm"

View file

@ -12,4 +12,4 @@
= link_to sprite_icon('question-o'), = link_to sprite_icon('question-o'),
help_page_path('user/admin_area/diff_limits', help_page_path('user/admin_area/diff_limits',
anchor: 'maximum-diff-patch-size') anchor: 'maximum-diff-patch-size')
= f.submit _('Save changes'), class: 'gl-button btn btn-success' = f.submit _('Save changes'), class: 'gl-button btn btn-confirm'

View file

@ -33,4 +33,4 @@
.form-text.text-muted .form-text.text-muted
= _('AWS Secret Access Key. Only required if not using role instance credentials') = _('AWS Secret Access Key. Only required if not using role instance credentials')
= f.submit 'Save changes', class: "gl-button btn btn-success" = f.submit 'Save changes', class: "gl-button btn btn-confirm"

View file

@ -33,4 +33,4 @@
.form-text.text-muted .form-text.text-muted
= _('By default, GitLab sends emails to help guide users through the onboarding process.') = _('By default, GitLab sends emails to help guide users through the onboarding process.')
= f.submit _('Save changes'), class: "gl-button btn btn-success", data: { qa_selector: 'save_changes_button' } = f.submit _('Save changes'), class: "gl-button btn btn-confirm", data: { qa_selector: 'save_changes_button' }

View file

@ -47,4 +47,4 @@
.form-group .form-group
= f.label :external_authorization_service_default_label, _('Default classification label'), class: 'label-bold' = f.label :external_authorization_service_default_label, _('Default classification label'), class: 'label-bold'
= f.text_field :external_authorization_service_default_label, class: 'form-control gl-form-input' = f.text_field :external_authorization_service_default_label, class: 'form-control gl-form-input'
= f.submit 'Save changes', class: "gl-button btn btn-success" = f.submit 'Save changes', class: "gl-button btn btn-confirm"

View file

@ -24,4 +24,4 @@
.form-text.text-muted .form-text.text-muted
Medium operation timeout (in seconds). This should be a value between the Fast and the Default timeout. Medium operation timeout (in seconds). This should be a value between the Fast and the Default timeout.
= f.submit 'Save changes', class: "gl-button btn btn-success" = f.submit 'Save changes', class: "gl-button btn btn-confirm"

View file

@ -25,4 +25,4 @@
= f.text_field :gitpod_url, class: 'form-control gl-form-input', placeholder: s_('Gitpod|e.g. https://gitpod.example.com') = f.text_field :gitpod_url, class: 'form-control gl-form-input', placeholder: s_('Gitpod|e.g. https://gitpod.example.com')
.form-text.text-muted .form-text.text-muted
= s_('Gitpod|Add the URL to your Gitpod instance configured to read your GitLab projects.') = s_('Gitpod|Add the URL to your Gitpod instance configured to read your GitLab projects.')
= f.submit s_('Save changes'), class: 'gl-button btn btn-success' = f.submit s_('Save changes'), class: 'gl-button btn btn-confirm'

View file

@ -14,4 +14,4 @@
= f.label :grafana_url, _('Grafana URL'), class: 'label-bold' = f.label :grafana_url, _('Grafana URL'), class: 'label-bold'
= f.text_field :grafana_url, class: 'form-control gl-form-input', placeholder: '/-/grafana' = f.text_field :grafana_url, class: 'form-control gl-form-input', placeholder: '/-/grafana'
= f.submit _('Save changes'), class: "gl-button btn btn-success" = f.submit _('Save changes'), class: "gl-button btn btn-confirm"

View file

@ -23,4 +23,4 @@
= f.label :help_page_documentation_base_url, _('Documentation pages URL'), class: 'label-bold' = f.label :help_page_documentation_base_url, _('Documentation pages URL'), class: 'label-bold'
= f.text_field :help_page_documentation_base_url, class: 'form-control gl-form-input', placeholder: 'https://docs.gitlab.com' = f.text_field :help_page_documentation_base_url, class: 'form-control gl-form-input', placeholder: 'https://docs.gitlab.com'
= f.submit _('Save changes'), class: "gl-button btn btn-success" = f.submit _('Save changes'), class: "gl-button btn btn-confirm"

View file

@ -31,4 +31,4 @@
= f.label :group_download_export_limit, _('Max Group Export Download requests per minute per user'), class: 'label-bold' = f.label :group_download_export_limit, _('Max Group Export Download requests per minute per user'), class: 'label-bold'
= f.number_field :group_download_export_limit, class: 'form-control gl-form-input' = f.number_field :group_download_export_limit, class: 'form-control gl-form-input'
= f.submit 'Save changes', class: "gl-button btn btn-success", data: { qa_selector: 'save_changes_button' } = f.submit 'Save changes', class: "gl-button btn btn-confirm", data: { qa_selector: 'save_changes_button' }

View file

@ -10,4 +10,4 @@
%span.form-text.text-muted %span.form-text.text-muted
= (_("Changes affect new repositories only. If not specified, Git's default name %{branch_name_default} will be used.") % { branch_name_default: fallback_branch_name } ).html_safe = (_("Changes affect new repositories only. If not specified, Git's default name %{branch_name_default} will be used.") % { branch_name_default: fallback_branch_name } ).html_safe
= f.submit _('Save changes'), class: 'gl-button btn-success' = f.submit _('Save changes'), class: 'gl-button btn-confirm'

View file

@ -57,4 +57,4 @@
= _('A plain-text response to show to clients that hit the rate limit.') = _('A plain-text response to show to clients that hit the rate limit.')
= f.text_area :rate_limiting_response_text, placeholder: ::Gitlab::Throttle::DEFAULT_RATE_LIMITING_RESPONSE_TEXT, class: 'form-control gl-form-input', rows: 5 = f.text_area :rate_limiting_response_text, placeholder: ::Gitlab::Throttle::DEFAULT_RATE_LIMITING_RESPONSE_TEXT, class: 'form-control gl-form-input', rows: 5
= f.submit 'Save changes', class: "gl-button btn btn-success", data: { qa_selector: 'save_changes_button' } = f.submit 'Save changes', class: "gl-button btn btn-confirm", data: { qa_selector: 'save_changes_button' }

View file

@ -6,4 +6,4 @@
= f.label :issues_create_limit, 'Max requests per minute per user', class: 'label-bold' = f.label :issues_create_limit, 'Max requests per minute per user', class: 'label-bold'
= f.number_field :issues_create_limit, class: 'form-control gl-form-input' = f.number_field :issues_create_limit, class: 'form-control gl-form-input'
= f.submit 'Save changes', class: "gl-button btn btn-success", data: { qa_selector: 'save_changes_button' } = f.submit 'Save changes', class: "gl-button btn btn-confirm", data: { qa_selector: 'save_changes_button' }

View file

@ -30,4 +30,4 @@
= f.check_box format[:name], class: 'form-check-input' = f.check_box format[:name], class: 'form-check-input'
= f.label format[:name], format[:label], class: 'form-check-label' = f.label format[:name], format[:label], class: 'form-check-label'
= f.submit _('Save changes'), class: "btn gl-button btn-success" = f.submit _('Save changes'), class: "btn gl-button btn-confirm"

View file

@ -15,4 +15,4 @@
= f.label :time_tracking_limit_to_hours, class: 'form-check-label' do = f.label :time_tracking_limit_to_hours, class: 'form-check-label' do
= _('Limit display of time tracking units to hours.') = _('Limit display of time tracking units to hours.')
= f.submit _('Save changes'), class: "gl-button btn btn-success" = f.submit _('Save changes'), class: "gl-button btn btn-confirm"

View file

@ -9,4 +9,4 @@
= f.label :notes_create_limit_allowlist, _('List of users to be excluded from the limit'), class: 'label-bold' = f.label :notes_create_limit_allowlist, _('List of users to be excluded from the limit'), class: 'label-bold'
= f.text_area :notes_create_limit_allowlist_raw, placeholder: 'username1, username2', class: 'form-control gl-form-input', rows: 5 = f.text_area :notes_create_limit_allowlist_raw, placeholder: 'username1, username2', class: 'form-control gl-form-input', rows: 5
= f.submit _('Save changes'), class: "gl-button btn btn-success", data: { qa_selector: 'save_changes_button' } = f.submit _('Save changes'), class: "gl-button btn btn-confirm", data: { qa_selector: 'save_changes_button' }

View file

@ -27,4 +27,4 @@
%span.form-text.text-muted %span.form-text.text-muted
= _('Resolves IP addresses once and uses them to submit requests') = _('Resolves IP addresses once and uses them to submit requests')
= f.submit 'Save changes', class: "gl-button btn btn-success", data: { qa_selector: 'save_changes_button' } = f.submit 'Save changes', class: "gl-button btn btn-confirm", data: { qa_selector: 'save_changes_button' }

View file

@ -47,4 +47,4 @@
.form-group .form-group
= f.label :generic_packages_max_file_size, _('Generic package file size in bytes'), class: 'label-bold' = f.label :generic_packages_max_file_size, _('Generic package file size in bytes'), class: 'label-bold'
= f.number_field :generic_packages_max_file_size, class: 'form-control gl-form-input' = f.number_field :generic_packages_max_file_size, class: 'form-control gl-form-input'
= f.submit _('Save %{name} size limits').html_safe % { name: plan.name.capitalize }, class: 'btn gl-button btn-success' = f.submit _('Save %{name} size limits').html_safe % { name: plan.name.capitalize }, class: 'btn gl-button btn-confirm'

View file

@ -41,4 +41,4 @@
- terms_of_service_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: lets_encrypt_terms_of_service_admin_application_settings_path } - terms_of_service_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: lets_encrypt_terms_of_service_admin_application_settings_path }
= _("I have read and agree to the Let's Encrypt %{link_start}Terms of Service%{link_end} (PDF)").html_safe % { link_start: terms_of_service_link_start, link_end: '</a>'.html_safe } = _("I have read and agree to the Let's Encrypt %{link_start}Terms of Service%{link_end} (PDF)").html_safe % { link_start: terms_of_service_link_start, link_end: '</a>'.html_safe }
= f.submit _('Save changes'), class: "gl-button btn btn-success" = f.submit _('Save changes'), class: "gl-button btn btn-confirm"

View file

@ -31,4 +31,4 @@
.form-text.text-muted .form-text.text-muted
= _('Number of changes (branches or tags) in a single push to determine whether individual push events or bulk push event will be created. Bulk push event will be created if it surpasses that value.') = _('Number of changes (branches or tags) in a single push to determine whether individual push events or bulk push event will be created. Bulk push event will be created if it surpasses that value.')
= f.submit 'Save changes', class: "gl-button btn btn-success" = f.submit 'Save changes', class: "gl-button btn btn-confirm"

View file

@ -11,4 +11,4 @@
= f.label :performance_bar_allowed_group_path, 'Allowed group', class: 'label-bold' = f.label :performance_bar_allowed_group_path, 'Allowed group', class: 'label-bold'
= f.text_field :performance_bar_allowed_group_path, class: 'form-control gl-form-input', placeholder: 'my-org/my-group', value: @application_setting.performance_bar_allowed_group&.full_path = f.text_field :performance_bar_allowed_group_path, class: 'form-control gl-form-input', placeholder: 'my-org/my-group', value: @application_setting.performance_bar_allowed_group&.full_path
= f.submit 'Save changes', class: 'gl-button btn btn-success qa-save-changes-button' = f.submit 'Save changes', class: 'gl-button btn btn-confirm qa-save-changes-button'

View file

@ -24,4 +24,4 @@
= link_to "PlantUML", "http://plantuml.com" = link_to "PlantUML", "http://plantuml.com"
diagrams in Asciidoc documents using an external PlantUML service. diagrams in Asciidoc documents using an external PlantUML service.
= f.submit _('Save changes'), class: "gl-button btn btn-success" = f.submit _('Save changes'), class: "gl-button btn btn-confirm"

View file

@ -30,4 +30,4 @@
A method call is only tracked when it takes longer to complete than A method call is only tracked when it takes longer to complete than
the given amount of milliseconds. the given amount of milliseconds.
= f.submit 'Save changes', class: "gl-button btn btn-success" = f.submit 'Save changes', class: "gl-button btn btn-confirm"

View file

@ -28,4 +28,4 @@
= _('All paths are relative to the GitLab URL. Do not include %{relative_url_link_start}relative URL%{relative_url_link_end}.').html_safe % { relative_url_link_start: relative_url_link_start, relative_url_link_end: '</a>'.html_safe } = _('All paths are relative to the GitLab URL. Do not include %{relative_url_link_start}relative URL%{relative_url_link_end}.').html_safe % { relative_url_link_start: relative_url_link_start, relative_url_link_end: '</a>'.html_safe }
= f.text_area :protected_paths_raw, placeholder: '/users/sign_in,/users/password', class: 'form-control gl-form-input', rows: 10 = f.text_area :protected_paths_raw, placeholder: '/users/sign_in,/users/password', class: 'form-control gl-form-input', rows: 10
= f.submit 'Save changes', class: 'gl-button btn btn-success' = f.submit 'Save changes', class: 'gl-button btn btn-confirm'

View file

@ -14,4 +14,4 @@
installations. Set to 0 to completely disable polling. installations. Set to 0 to completely disable polling.
= link_to sprite_icon('question-o'), help_page_path('administration/polling') = link_to sprite_icon('question-o'), help_page_path('administration/polling')
= f.submit 'Save changes', class: "gl-button btn btn-success" = f.submit 'Save changes', class: "gl-button btn btn-confirm"

View file

@ -31,4 +31,4 @@
.form-text.text-muted .form-text.text-muted
= _("The maximum number of tags that a single worker accepts for cleanup. If the number of tags goes above this limit, the list of tags to delete is truncated to this number. To remove this limit, set it to 0.") = _("The maximum number of tags that a single worker accepts for cleanup. If the number of tags goes above this limit, the list of tags to delete is truncated to this number. To remove this limit, set it to 0.")
= f.submit 'Save changes', class: "gl-button btn btn-success" = f.submit 'Save changes', class: "gl-button btn btn-confirm"

View file

@ -55,4 +55,4 @@
.form-text.text-muted .form-text.text-muted
Number of Git pushes after which 'git gc' is run. Number of Git pushes after which 'git gc' is run.
= f.submit _('Save changes'), class: "gl-button btn btn-success" = f.submit _('Save changes'), class: "gl-button btn btn-confirm"

View file

@ -14,4 +14,4 @@
= render_if_exists 'admin/application_settings/mirror_settings', form: f = render_if_exists 'admin/application_settings/mirror_settings', form: f
= f.submit _('Save changes'), class: "gl-button btn btn-success" = f.submit _('Save changes'), class: "gl-button btn btn-confirm"

View file

@ -15,4 +15,4 @@
%span.form-text.text-muted#static_objects_external_storage_auth_token_help_block %span.form-text.text-muted#static_objects_external_storage_auth_token_help_block
= _('A secure token that identifies an external storage request.') = _('A secure token that identifies an external storage request.')
= f.submit _('Save changes'), class: "gl-button btn btn-success" = f.submit _('Save changes'), class: "gl-button btn btn-confirm"

View file

@ -22,4 +22,4 @@
= f.text_field attribute[:name], class: 'form-text-input', value: attribute[:value] = f.text_field attribute[:name], class: 'form-text-input', value: attribute[:value]
= f.label attribute[:label], attribute[:label], class: 'label-bold form-check-label' = f.label attribute[:label], attribute[:label], class: 'label-bold form-check-label'
%br %br
= f.submit _('Save changes'), class: "gl-button btn btn-success qa-save-changes-button" = f.submit _('Save changes'), class: "gl-button btn btn-confirm"

View file

@ -57,4 +57,4 @@
= f.label :sign_in_text, _('Sign-in text'), class: 'label-bold' = f.label :sign_in_text, _('Sign-in text'), class: 'label-bold'
= f.text_area :sign_in_text, class: 'form-control gl-form-input', rows: 4 = f.text_area :sign_in_text, class: 'form-control gl-form-input', rows: 4
.form-text.text-muted Markdown enabled .form-text.text-muted Markdown enabled
= f.submit 'Save changes', class: "gl-button btn btn-success" = f.submit 'Save changes', class: "gl-button btn btn-confirm"

View file

@ -77,4 +77,4 @@
= f.label :after_sign_up_text, class: 'label-bold' = f.label :after_sign_up_text, class: 'label-bold'
= f.text_area :after_sign_up_text, class: 'form-control gl-form-input', rows: 4 = f.text_area :after_sign_up_text, class: 'form-control gl-form-input', rows: 4
.form-text.text-muted Markdown enabled .form-text.text-muted Markdown enabled
= f.submit 'Save changes', class: "gl-button btn btn-success", data: { qa_selector: 'save_changes_button' } = f.submit 'Save changes', class: "gl-button btn btn-confirm", data: { qa_selector: 'save_changes_button' }

View file

@ -26,4 +26,4 @@
= f.label :snowplow_cookie_domain, _('Cookie domain'), class: 'label-light' = f.label :snowplow_cookie_domain, _('Cookie domain'), class: 'label-light'
= f.text_field :snowplow_cookie_domain, class: 'form-control gl-form-input' = f.text_field :snowplow_cookie_domain, class: 'form-control gl-form-input'
= f.submit _('Save changes'), class: 'gl-button btn btn-success' = f.submit _('Save changes'), class: 'gl-button btn btn-confirm'

View file

@ -35,4 +35,4 @@
= f.text_field :sourcegraph_url, class: 'form-control gl-form-input', placeholder: s_('SourcegraphAdmin|e.g. https://sourcegraph.example.com') = f.text_field :sourcegraph_url, class: 'form-control gl-form-input', placeholder: s_('SourcegraphAdmin|e.g. https://sourcegraph.example.com')
.form-text.text-muted .form-text.text-muted
= s_('SourcegraphAdmin|Configure the URL to a Sourcegraph instance which can read your GitLab projects.') = s_('SourcegraphAdmin|Configure the URL to a Sourcegraph instance which can read your GitLab projects.')
= f.submit s_('SourcegraphAdmin|Save changes'), class: 'gl-button btn btn-success' = f.submit s_('SourcegraphAdmin|Save changes'), class: 'gl-button btn btn-confirm'

View file

@ -79,4 +79,4 @@
= f.label :spam_check_endpoint_url, _('URL of the external Spam Check endpoint'), class: 'label-bold' = f.label :spam_check_endpoint_url, _('URL of the external Spam Check endpoint'), class: 'label-bold'
= f.text_field :spam_check_endpoint_url, class: 'form-control gl-form-input' = f.text_field :spam_check_endpoint_url, class: 'form-control gl-form-input'
= f.submit 'Save changes', class: "gl-button btn btn-success" = f.submit 'Save changes', class: "gl-button btn btn-confirm"

View file

@ -8,4 +8,4 @@
.form-text.text-muted .form-text.text-muted
Maximum time for web terminal websocket connection (in seconds). Maximum time for web terminal websocket connection (in seconds).
0 for unlimited. 0 for unlimited.
= f.submit 'Save changes', class: "gl-button btn btn-success" = f.submit 'Save changes', class: "gl-button btn btn-confirm"

View file

@ -15,4 +15,4 @@
= f.text_area :terms, class: 'form-control gl-form-input', rows: 8 = f.text_area :terms, class: 'form-control gl-form-input', rows: 8
.form-text.text-muted .form-text.text-muted
= _("Markdown enabled") = _("Markdown enabled")
= f.submit _("Save changes"), class: "gl-button btn btn-success" = f.submit _("Save changes"), class: "gl-button btn btn-confirm"

View file

@ -17,4 +17,4 @@
= f.check_box :hide_third_party_offers, class: 'form-check-input' = f.check_box :hide_third_party_offers, class: 'form-check-input'
= f.label :hide_third_party_offers, _('Do not display offers from third parties within GitLab'), class: 'form-check-label' = f.label :hide_third_party_offers, _('Do not display offers from third parties within GitLab'), class: 'form-check-label'
= f.submit _('Save changes'), class: "gl-button btn btn-success" = f.submit _('Save changes'), class: "gl-button btn btn-confirm"

View file

@ -37,4 +37,4 @@
- deactivating_usage_ping_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: deactivating_usage_ping_path } - deactivating_usage_ping_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: deactivating_usage_ping_path }
= s_('For more information, see the documentation on %{deactivating_usage_ping_link_start}deactivating the usage ping%{deactivating_usage_ping_link_end}.').html_safe % { deactivating_usage_ping_link_start: deactivating_usage_ping_link_start, deactivating_usage_ping_link_end: '</a>'.html_safe } = s_('For more information, see the documentation on %{deactivating_usage_ping_link_start}deactivating the usage ping%{deactivating_usage_ping_link_end}.').html_safe % { deactivating_usage_ping_link_start: deactivating_usage_ping_link_start, deactivating_usage_ping_link_end: '</a>'.html_safe }
= f.submit 'Save changes', class: "gl-button btn btn-success" = f.submit 'Save changes', class: "gl-button btn btn-confirm"

View file

@ -74,4 +74,4 @@
= f.label :disable_feed_token, class: 'form-check-label' do = f.label :disable_feed_token, class: 'form-check-label' do
= s_('AdminSettings|Disable feed token') = s_('AdminSettings|Disable feed token')
= f.submit _('Save changes'), class: "gl-button btn btn-success" = f.submit _('Save changes'), class: "gl-button btn btn-confirm"

View file

@ -101,7 +101,7 @@
= s_('IDE|Live Preview') = s_('IDE|Live Preview')
%span.form-text.text-muted %span.form-text.text-muted
= s_('IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview.') = s_('IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview.')
= f.submit _('Save changes'), class: "gl-button btn btn-success" = f.submit _('Save changes'), class: "gl-button btn btn-confirm"
= render_if_exists 'admin/application_settings/maintenance_mode_settings_form' = render_if_exists 'admin/application_settings/maintenance_mode_settings_form'
= render 'admin/application_settings/gitpod' = render 'admin/application_settings/gitpod'

View file

@ -6,3 +6,4 @@ Merge Request URL: #{project_merge_request_url(@merge_request.target_project, @m
Author: #{sanitize_name(@merge_request.author_name)} Author: #{sanitize_name(@merge_request.author_name)}
= assignees_label(@merge_request) = assignees_label(@merge_request)
= reviewers_label(@merge_request)

View file

@ -4,6 +4,7 @@
"commit-sha" => @project.commit ? @project.commit.sha : '', "commit-sha" => @project.commit ? @project.commit.sha : '',
"default-branch" => @project.default_branch, "default-branch" => @project.default_branch,
"empty-state-illustration-path" => image_path('illustrations/empty-state/empty-dag-md.svg'), "empty-state-illustration-path" => image_path('illustrations/empty-state/empty-dag-md.svg'),
"initial-branch-name": params[:branch_name],
"lint-help-page-path" => help_page_path('ci/lint', anchor: 'validate-basic-logic-and-syntax'), "lint-help-page-path" => help_page_path('ci/lint', anchor: 'validate-basic-logic-and-syntax'),
"new-merge-request-path" => namespace_project_new_merge_request_path, "new-merge-request-path" => namespace_project_new_merge_request_path,
"project-path" => @project.path, "project-path" => @project.path,

View file

@ -5,7 +5,7 @@
%section.settings.project-mirror-settings.no-animate#js-push-remote-settings{ class: mirror_settings_class, data: { qa_selector: 'mirroring_repositories_settings_content' } } %section.settings.project-mirror-settings.no-animate#js-push-remote-settings{ class: mirror_settings_class, data: { qa_selector: 'mirroring_repositories_settings_content' } }
.settings-header .settings-header
%h4= _('Mirroring repositories') %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Mirroring repositories')
%button.btn.gl-button.btn-default.js-settings-toggle %button.btn.gl-button.btn-default.js-settings-toggle
= expanded ? _('Collapse') : _('Expand') = expanded ? _('Collapse') : _('Expand')
%p %p

View file

@ -18,7 +18,7 @@ module ApplicationWorker
set_queue set_queue
def structured_payload(payload = {}) def structured_payload(payload = {})
context = Labkit::Context.current.to_h.merge( context = Gitlab::ApplicationContext.current.merge(
'class' => self.class.name, 'class' => self.class.name,
'job_status' => 'running', 'job_status' => 'running',
'queue' => self.class.queue, 'queue' => self.class.queue,

View file

@ -15,7 +15,7 @@ module CronjobQueue
# Cronjobs never get scheduled with arguments, so this is safe to # Cronjobs never get scheduled with arguments, so this is safe to
# override # override
def context_for_arguments(_args) def context_for_arguments(_args)
return if Gitlab::ApplicationContext.current_context_include?('meta.caller_id') return if Gitlab::ApplicationContext.current_context_include?(:caller_id)
Gitlab::ApplicationContext.new(caller_id: "Cronjob") Gitlab::ApplicationContext.new(caller_id: "Cronjob")
end end

View file

@ -0,0 +1,5 @@
---
title: Project Settings Repository Mirroring repositories header expands/collapses on click / tap
merge_request: 55229
author: Daniel Schömer
type: changed

View file

@ -0,0 +1,5 @@
---
title: Add reviewers detail to merge request status email
merge_request: 55584
author:
type: added

View file

@ -0,0 +1,5 @@
---
title: 'Fixes: No such file or directory lib/pager_duty/validator/schemas/message.json'
merge_request: 55725
author:
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Add web_hook_logs partitioning migration
merge_request: 55938
author:
type: other

View file

@ -0,0 +1,5 @@
---
title: Move to btn-confirm from btn-success in admin application_settings directory
merge_request: 55263
author: Yogi (@yo)
type: changed

View file

@ -0,0 +1,5 @@
---
title: Fix edited timestamp updated when transforming / resolving comments
merge_request: 55671
author: Mycroft Kang @TaehyeokKang
type: fixed

View file

@ -0,0 +1,5 @@
---
title: Allow users to work on non-default branch in pipeline editor
merge_request: 55413
author:
type: added

View file

@ -4,6 +4,7 @@
# (even with eager loading disabled). # (even with eager loading disabled).
Gitlab::Database::Partitioning::PartitionCreator.register(AuditEvent) Gitlab::Database::Partitioning::PartitionCreator.register(AuditEvent)
Gitlab::Database::Partitioning::PartitionCreator.register(WebHookLogPartitioned)
begin begin
Gitlab::Database::Partitioning::PartitionCreator.new.create_partitions unless ENV['DISABLE_POSTGRES_PARTITION_CREATION_ON_STARTUP'] Gitlab::Database::Partitioning::PartitionCreator.new.create_partitions unless ENV['DISABLE_POSTGRES_PARTITION_CREATION_ON_STARTUP']

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
class AddLastEditedAtAndLastEditedByIdToNotes < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
with_lock_retries do
add_column :notes, :last_edited_at, :datetime_with_timezone
end
end
def down
with_lock_retries do
remove_column :notes, :last_edited_at
end
end
end

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
class PartitionWebHookLogs < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
include Gitlab::Database::PartitioningMigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
disable_ddl_transaction!
def up
partition_table_by_date :web_hook_logs, :created_at
end
def down
drop_partitioned_table_for :web_hook_logs
end
end

View file

@ -0,0 +1,17 @@
# frozen_string_literal: true
class BackfillPartitionedWebHookLogs < ActiveRecord::Migration[6.0]
include Gitlab::Database::PartitioningMigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
enqueue_partitioning_data_migration :web_hook_logs
end
def down
cleanup_partitioning_data_migration :web_hook_logs
end
end

View file

@ -0,0 +1 @@
3bd7e839c4f93716a7e893bf9184306a1fcfd401e5b54f4393e5138e2776f5e0

View file

@ -0,0 +1 @@
44d53ac15c5e54c2f1c825286155dec643b82573184026caaf08288512168aef

View file

@ -0,0 +1 @@
90072e3dee4517061ff9e08decda7fecb9cc9b38a56345c09685e3cce48a8b66

View file

@ -41,6 +41,62 @@ RETURN NULL;
END END
$$; $$;
CREATE FUNCTION table_sync_function_29bc99d6db() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
IF (TG_OP = 'DELETE') THEN
DELETE FROM web_hook_logs_part_0c5294f417 where id = OLD.id;
ELSIF (TG_OP = 'UPDATE') THEN
UPDATE web_hook_logs_part_0c5294f417
SET web_hook_id = NEW.web_hook_id,
trigger = NEW.trigger,
url = NEW.url,
request_headers = NEW.request_headers,
request_data = NEW.request_data,
response_headers = NEW.response_headers,
response_body = NEW.response_body,
response_status = NEW.response_status,
execution_duration = NEW.execution_duration,
internal_error_message = NEW.internal_error_message,
updated_at = NEW.updated_at,
created_at = NEW.created_at
WHERE web_hook_logs_part_0c5294f417.id = NEW.id;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO web_hook_logs_part_0c5294f417 (id,
web_hook_id,
trigger,
url,
request_headers,
request_data,
response_headers,
response_body,
response_status,
execution_duration,
internal_error_message,
updated_at,
created_at)
VALUES (NEW.id,
NEW.web_hook_id,
NEW.trigger,
NEW.url,
NEW.request_headers,
NEW.request_data,
NEW.response_headers,
NEW.response_body,
NEW.response_status,
NEW.execution_duration,
NEW.internal_error_message,
NEW.updated_at,
NEW.created_at);
END IF;
RETURN NULL;
END
$$;
COMMENT ON FUNCTION table_sync_function_29bc99d6db() IS 'Partitioning migration: table sync for web_hook_logs table';
CREATE FUNCTION table_sync_function_2be879775d() RETURNS trigger CREATE FUNCTION table_sync_function_2be879775d() RETURNS trigger
LANGUAGE plpgsql LANGUAGE plpgsql
AS $$ AS $$
@ -114,6 +170,23 @@ CREATE TABLE audit_events (
) )
PARTITION BY RANGE (created_at); PARTITION BY RANGE (created_at);
CREATE TABLE web_hook_logs_part_0c5294f417 (
id bigint NOT NULL,
web_hook_id integer NOT NULL,
trigger character varying,
url character varying,
request_headers text,
request_data text,
response_headers text,
response_body text,
response_status character varying,
execution_duration double precision,
internal_error_message character varying,
updated_at timestamp without time zone NOT NULL,
created_at timestamp without time zone NOT NULL
)
PARTITION BY RANGE (created_at);
CREATE TABLE product_analytics_events_experimental ( CREATE TABLE product_analytics_events_experimental (
id bigint NOT NULL, id bigint NOT NULL,
project_id integer NOT NULL, project_id integer NOT NULL,
@ -14613,7 +14686,8 @@ CREATE TABLE notes (
change_position text, change_position text,
resolved_by_push boolean, resolved_by_push boolean,
review_id bigint, review_id bigint,
confidential boolean confidential boolean,
last_edited_at timestamp with time zone
); );
CREATE SEQUENCE notes_id_seq CREATE SEQUENCE notes_id_seq
@ -21254,6 +21328,9 @@ ALTER TABLE ONLY vulnerability_statistics
ALTER TABLE ONLY vulnerability_user_mentions ALTER TABLE ONLY vulnerability_user_mentions
ADD CONSTRAINT vulnerability_user_mentions_pkey PRIMARY KEY (id); ADD CONSTRAINT vulnerability_user_mentions_pkey PRIMARY KEY (id);
ALTER TABLE ONLY web_hook_logs_part_0c5294f417
ADD CONSTRAINT web_hook_logs_part_0c5294f417_pkey PRIMARY KEY (id, created_at);
ALTER TABLE ONLY web_hook_logs ALTER TABLE ONLY web_hook_logs
ADD CONSTRAINT web_hook_logs_pkey PRIMARY KEY (id); ADD CONSTRAINT web_hook_logs_pkey PRIMARY KEY (id);
@ -24319,6 +24396,8 @@ ALTER INDEX product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_p
ALTER INDEX product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_63_pkey; ALTER INDEX product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_63_pkey;
CREATE TRIGGER table_sync_trigger_b99eb6998c AFTER INSERT OR DELETE OR UPDATE ON web_hook_logs FOR EACH ROW EXECUTE PROCEDURE table_sync_function_29bc99d6db();
CREATE TRIGGER table_sync_trigger_ee39a25f9d AFTER INSERT OR DELETE OR UPDATE ON audit_events FOR EACH ROW EXECUTE PROCEDURE table_sync_function_2be879775d(); CREATE TRIGGER table_sync_trigger_ee39a25f9d AFTER INSERT OR DELETE OR UPDATE ON audit_events FOR EACH ROW EXECUTE PROCEDURE table_sync_function_2be879775d();
CREATE TRIGGER trigger_has_external_issue_tracker_on_delete AFTER DELETE ON services FOR EACH ROW WHEN ((((old.category)::text = 'issue_tracker'::text) AND (old.active = true) AND (old.project_id IS NOT NULL))) EXECUTE PROCEDURE set_has_external_issue_tracker(); CREATE TRIGGER trigger_has_external_issue_tracker_on_delete AFTER DELETE ON services FOR EACH ROW WHEN ((((old.category)::text = 'issue_tracker'::text) AND (old.active = true) AND (old.project_id IS NOT NULL))) EXECUTE PROCEDURE set_has_external_issue_tracker();

View file

@ -14,10 +14,12 @@ Find more about them [in Audit Events documentation](audit_events.md).
System log files are typically plain text in a standard log file format. System log files are typically plain text in a standard log file format.
This guide talks about how to read and use these system log files. This guide talks about how to read and use these system log files.
Read more about how to Read more about the log system and using the logs:
[customize logging on Omnibus GitLab installations](https://docs.gitlab.com/omnibus/settings/logs.html)
- [Customize logging on Omnibus GitLab installations](https://docs.gitlab.com/omnibus/settings/logs.html)
including adjusting log retention, log forwarding, including adjusting log retention, log forwarding,
switching logs from JSON to plain text logging, and more. switching logs from JSON to plain text logging, and more.
- [How to parse and analyze JSON logs](troubleshooting/log_parsing.md).
## `production_json.log` ## `production_json.log`

View file

@ -148,3 +148,23 @@ Traceback (most recent call last):
[traceback removed] [traceback removed]
/opt/gitlab/..../runner_command.rb:42:in `load': cannot load such file -- /tmp/helloworld.rb (LoadError) /opt/gitlab/..../runner_command.rb:42:in `load': cannot load such file -- /tmp/helloworld.rb (LoadError)
``` ```
In case you encouter a similar error to this:
```plaintext
[root ~]# sudo gitlab-rails runner helloworld.rb
Please specify a valid ruby command or the path of a script to run.
Run 'rails runner -h' for help.
undefined local variable or method `helloworld' for main:Object
```
You can either move the file to the `/tmp` directory or create a new directory onwed by the user `git` and save the script in that directory as illustrated below:
```shell
sudo mkdir /scripts
sudo mv /script_path/helloworld.rb /scripts
sudo chown -R git:git /scripts
sudo chmod 700 /scripts
sudo gitlab-rails runner /scripts/helloworld.rb
```

View file

@ -41,6 +41,20 @@ jq -cR 'fromjson?' file.json | jq <COMMAND>
By default `jq` will error out when it encounters a line that is not valid JSON. By default `jq` will error out when it encounters a line that is not valid JSON.
This skips over all invalid lines and parses the rest. This skips over all invalid lines and parses the rest.
#### Print a JSON log's time range
```shell
cat log.json | (head -1; tail -1) | jq '.time'
```
Use `zcat` if the file has been rotated and compressed:
```shell
zcat @400000006026b71d1a7af804.s | (head -1; tail -1) | jq '.time'
zcat some_json.log.25.gz | (head -1; tail -1) | jq '.time'
```
### Parsing `production_json.log` and `api_json.log` ### Parsing `production_json.log` and `api_json.log`
#### Find all requests with a 5XX status code #### Find all requests with a 5XX status code

View file

@ -310,6 +310,27 @@ Example response:
] ]
``` ```
## Remove a billable member from a group
Removes a billable member from a group and its subgroups and projects.
The user does not need to be a group member to qualify for removal.
For example, if the user was added directly to a project within the group, you can
still use this API to remove them.
```plaintext
DELETE /groups/:id/billable_members/:user_id
```
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `user_id` | integer | yes | The user ID of the member |
```shell
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/billable_members/:user_id"
```
## Add a member to a group or project ## Add a member to a group or project
Adds a member to a group or project. Adds a member to a group or project.

View file

@ -46,9 +46,9 @@ To create a GitLab Pages website:
| Document | Description | | Document | Description |
| -------- | ----------- | | -------- | ----------- |
| [Fork a sample project](getting_started/pages_forked_sample_project.md) | Create a new project with Pages already configured by forking a sample project. |
| [Use a new project template](getting_started/pages_new_project_template.md) | Create a new project with Pages already configured by using a new project template. | | [Use a new project template](getting_started/pages_new_project_template.md) | Create a new project with Pages already configured by using a new project template. |
| [Use a `.gitlab-ci.yml` template](getting_started/pages_ci_cd_template.md) | Add a Pages site to an existing project. Use a pre-populated CI template file. | | [Use a `.gitlab-ci.yml` template](getting_started/pages_ci_cd_template.md) | Add a Pages site to an existing project. Use a pre-populated CI template file. |
| [Fork a sample project](getting_started/pages_forked_sample_project.md) | Create a new project with Pages already configured by forking a sample project. |
| [Create a `gitlab-ci.yml` file from scratch](getting_started/pages_from_scratch.md) | Add a Pages site to an existing project. Learn how to create and configure your own CI file. | | [Create a `gitlab-ci.yml` file from scratch](getting_started/pages_from_scratch.md) | Add a Pages site to an existing project. Learn how to create and configure your own CI file. |
To update a GitLab Pages website: To update a GitLab Pages website:

View file

@ -12,11 +12,11 @@ module API
namespace 'queues' do namespace 'queues' do
desc 'Drop jobs matching the given metadata from the Sidekiq queue' desc 'Drop jobs matching the given metadata from the Sidekiq queue'
params do params do
Labkit::Context::KNOWN_KEYS.each do |key| Gitlab::ApplicationContext::KNOWN_KEYS.each do |key|
optional key, type: String, allow_blank: false optional key, type: String, allow_blank: false
end end
at_least_one_of(*Labkit::Context::KNOWN_KEYS) at_least_one_of(*Gitlab::ApplicationContext::KNOWN_KEYS)
end end
delete ':queue_name' do delete ':queue_name' do
result = result =

View file

@ -7,6 +7,9 @@ module Gitlab
Attribute = Struct.new(:name, :type) Attribute = Struct.new(:name, :type)
LOG_KEY = Labkit::Context::LOG_KEY
KNOWN_KEYS = Labkit::Context::KNOWN_KEYS
APPLICATION_ATTRIBUTES = [ APPLICATION_ATTRIBUTES = [
Attribute.new(:project, Project), Attribute.new(:project, Project),
Attribute.new(:namespace, Namespace), Attribute.new(:namespace, Namespace),
@ -22,6 +25,10 @@ module Gitlab
application_context.use(&block) application_context.use(&block)
end end
def self.with_raw_context(attributes = {}, &block)
Labkit::Context.with_context(attributes, &block)
end
def self.push(args) def self.push(args)
application_context = new(**args) application_context = new(**args)
Labkit::Context.push(application_context.to_lazy_hash) Labkit::Context.push(application_context.to_lazy_hash)

View file

@ -9,7 +9,7 @@ module Gitlab
include ::Gitlab::Database::MigrationHelpers include ::Gitlab::Database::MigrationHelpers
include ::Gitlab::Database::Migrations::BackgroundMigrationHelpers include ::Gitlab::Database::Migrations::BackgroundMigrationHelpers
ALLOWED_TABLES = %w[audit_events].freeze ALLOWED_TABLES = %w[audit_events web_hook_logs].freeze
ERROR_SCOPE = 'table partitioning' ERROR_SCOPE = 'table partitioning'
MIGRATION_CLASS_NAME = "::#{module_parent_name}::BackfillPartitionedTable" MIGRATION_CLASS_NAME = "::#{module_parent_name}::BackfillPartitionedTable"

View file

@ -215,7 +215,7 @@ module Gitlab
'client_name' => CLIENT_NAME 'client_name' => CLIENT_NAME
} }
context_data = Labkit::Context.current&.to_h context_data = Gitlab::ApplicationContext.current
feature_stack = Thread.current[:gitaly_feature_stack] feature_stack = Thread.current[:gitaly_feature_stack]
feature = feature_stack && feature_stack[0] feature = feature_stack && feature_stack[0]

View file

@ -6,7 +6,7 @@ module Gitlab
module Loggers module Loggers
class ContextLogger < ::GrapeLogging::Loggers::Base class ContextLogger < ::GrapeLogging::Loggers::Base
def parameters(_, _) def parameters(_, _)
Labkit::Context.current.to_h Gitlab::ApplicationContext.current
end end
end end
end end

View file

@ -21,7 +21,7 @@ module Gitlab
job_search_metadata = job_search_metadata =
search_metadata search_metadata
.stringify_keys .stringify_keys
.slice(*Labkit::Context::KNOWN_KEYS) .slice(*Gitlab::ApplicationContext::KNOWN_KEYS)
.transform_keys { |key| "meta.#{key}" } .transform_keys { |key| "meta.#{key}" }
.compact .compact

View file

@ -2,7 +2,7 @@
module PagerDuty module PagerDuty
class WebhookPayloadParser class WebhookPayloadParser
SCHEMA_PATH = File.join('lib', 'pager_duty', 'validator', 'schemas', 'message.json') SCHEMA_PATH = Rails.root.join('lib', 'pager_duty', 'validator', 'schemas', 'message.json')
def initialize(payload) def initialize(payload)
@payload = payload @payload = payload
@ -66,7 +66,7 @@ module PagerDuty
end end
def valid_message?(message) def valid_message?(message)
::JSONSchemer.schema(Pathname.new(SCHEMA_PATH)).valid?(message) ::JSONSchemer.schema(SCHEMA_PATH).valid?(message)
end end
end end
end end

View file

@ -34456,6 +34456,9 @@ msgstr ""
msgid "You must set up incoming email before it becomes active." msgid "You must set up incoming email before it becomes active."
msgstr "" msgstr ""
msgid "You must solve the CAPTCHA in order to submit"
msgstr ""
msgid "You must upload a file with the same file name when dropping onto an existing design." msgid "You must upload a file with the same file name when dropping onto an existing design."
msgstr "" msgstr ""

View file

@ -900,7 +900,7 @@ RSpec.describe ApplicationController do
feature_category :issue_tracking feature_category :issue_tracking
def index def index
Labkit::Context.with_context do |context| Gitlab::ApplicationContext.with_raw_context do |context|
render json: context.to_h render json: context.to_h
end end
end end

Some files were not shown because too many files have changed in this diff Show more