diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index e823b7e3501..10eeb3d68c6 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -059a82773ec2b5afc115442270d663cccc68451c +d1f4340a1123d2436c7544d6ba64635c4c8f6104 diff --git a/app/assets/javascripts/captcha/captcha_modal.vue b/app/assets/javascripts/captcha/captcha_modal.vue index e6c73bc9643..a98a52a3130 100644 --- a/app/assets/javascripts/captcha/captcha_modal.vue +++ b/app/assets/javascripts/captcha/captcha_modal.vue @@ -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: { emitReceivedCaptchaResponse(captchaResponse) { - this.$emit('receivedCaptchaResponse', captchaResponse); this.$refs.modal.hide(); + this.$emit('receivedCaptchaResponse', captchaResponse); }, emitNullReceivedCaptchaResponse() { this.emitReceivedCaptchaResponse(null); @@ -103,6 +110,7 @@ export default { :action-cancel="{ text: __('Cancel') }" @shown="shown" @hide="hide" + @hidden="$emit('hidden')" >

{{ __('We want to be sure it is you, please confirm you are not a robot.') }}

diff --git a/app/assets/javascripts/captcha/captcha_modal_axios_interceptor.js b/app/assets/javascripts/captcha/captcha_modal_axios_interceptor.js new file mode 100644 index 00000000000..c9eac44eb28 --- /dev/null +++ b/app/assets/javascripts/captcha/captcha_modal_axios_interceptor.js @@ -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); + }, + ); +} diff --git a/app/assets/javascripts/captcha/unsolved_captcha_error.js b/app/assets/javascripts/captcha/unsolved_captcha_error.js new file mode 100644 index 00000000000..1e5c2a4d852 --- /dev/null +++ b/app/assets/javascripts/captcha/unsolved_captcha_error.js @@ -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; diff --git a/app/assets/javascripts/captcha/wait_for_captcha_to_be_solved.js b/app/assets/javascripts/captcha/wait_for_captcha_to_be_solved.js new file mode 100644 index 00000000000..0fd0f571d3b --- /dev/null +++ b/app/assets/javascripts/captcha/wait_for_captcha_to_be_solved.js @@ -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); + } + }, + }, + }); + }, + }); + }); +} diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue index d1a8d334796..9b978483cc6 100644 --- a/app/assets/javascripts/issue_show/components/app.vue +++ b/app/assets/javascripts/issue_show/components/app.vue @@ -5,7 +5,6 @@ import { deprecatedCreateFlash as createFlash } from '~/flash'; import Poll from '~/lib/utils/poll'; import { visitUrl } from '~/lib/utils/url_utility'; import { __, s__, sprintf } from '~/locale'; -import recaptchaModalImplementor from '~/vue_shared/mixins/recaptcha_modal_implementor'; import { IssuableStatus, IssuableStatusText, IssuableType } from '../constants'; import eventHub from '../event_hub'; import Service from '../services/index'; @@ -25,7 +24,6 @@ export default { formComponent, PinnedLinks, }, - mixins: [recaptchaModalImplementor], props: { endpoint: { required: true, @@ -250,6 +248,7 @@ export default { }, }, created() { + this.flashContainer = null; this.service = new Service(this.endpoint); this.poll = new Poll({ resource: this.service, @@ -289,7 +288,7 @@ export default { methods: { handleBeforeUnloadEvent(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?'); } return undefined; @@ -347,10 +346,10 @@ export default { }, updateIssuable() { + this.clearFlash(); return this.service .updateIssuable(this.store.formState) .then((res) => res.data) - .then((data) => this.checkForSpam(data)) .then((data) => { if (!window.location.pathname.includes(data.web_url)) { visitUrl(data.web_url); @@ -361,30 +360,24 @@ export default { eventHub.$emit('close.form'); }) .catch((error = {}) => { - const { name, response = {} } = error; + const { message, response = {} } = error; - if (name === 'SpamError') { - this.openRecaptcha(); - } else { - let errMsg = this.defaultErrorMessage; + this.store.setFormState({ + updateLoading: false, + }); - if (response.data && response.data.errors) { - errMsg += `. ${response.data.errors.join(' ')}`; - } + let errMsg = this.defaultErrorMessage; - 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) { return this.service .deleteIssuable(payload) @@ -409,6 +402,13 @@ export default { showStickyHeader() { this.isStickyHeaderShowing = true; }, + + clearFlash() { + if (this.flashContainer) { + this.flashContainer.style.display = 'none'; + this.flashContainer = null; + } + }, }, }; @@ -430,13 +430,6 @@ export default { :enable-autocomplete="enableAutocomplete" :issuable-type="issuableType" /> - -
{ const { // Add to apollo cache as it can be updated by future queries commitSha, + initialBranchName, // Add to provide/inject API for static values ciConfigPath, defaultBranch, @@ -42,6 +43,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => { apolloProvider.clients.defaultClient.cache.writeData({ data: { + currentBranch: initialBranchName || defaultBranch, commitSha, }, }); diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue index 0145a377598..a402f0011e5 100644 --- a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue +++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue @@ -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 getBlobContent from './graphql/queries/blob_content.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'; export default { @@ -23,9 +24,6 @@ export default { ciConfigPath: { default: '', }, - defaultBranch: { - default: null, - }, projectFullPath: { default: '', }, @@ -58,7 +56,7 @@ export default { return { projectPath: this.projectFullPath, path: this.ciConfigPath, - ref: this.defaultBranch, + ref: this.currentBranch, }; }, update(data) { @@ -97,6 +95,9 @@ export default { this.reportFailure(LOAD_FAILURE_UNKNOWN); }, }, + currentBranch: { + query: getCurrentBranch, + }, }, computed: { hasUnsavedChanges() { diff --git a/app/assets/javascripts/snippets/components/edit.vue b/app/assets/javascripts/snippets/components/edit.vue index 9f43ac36df7..bee9d7b8c2a 100644 --- a/app/assets/javascripts/snippets/components/edit.vue +++ b/app/assets/javascripts/snippets/components/edit.vue @@ -221,7 +221,10 @@ export default { this.captchaResponse = 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(); } else { // If the user didn't solve the captcha (e.g. they just closed the modal), diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 379da90827a..99fab68d24c 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -463,7 +463,7 @@ class ApplicationController < ActionController::Base feature_category: feature_category) do yield ensure - @current_context = Labkit::Context.current.to_h + @current_context = Gitlab::ApplicationContext.current end end diff --git a/app/controllers/concerns/spammable_actions.rb b/app/controllers/concerns/spammable_actions.rb index b285faee9bc..9e861d2859d 100644 --- a/app/controllers/concerns/spammable_actions.rb +++ b/app/controllers/concerns/spammable_actions.rb @@ -2,6 +2,7 @@ module SpammableActions extend ActiveSupport::Concern + include Spam::Concerns::HasSpamActionResponseFields included do before_action :authorize_submit_spammable!, only: :mark_as_spam @@ -25,14 +26,20 @@ module SpammableActions respond_to do |format| 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 end format.json do - locals = { spammable: spammable, script: false, has_submit: false } - recaptcha_html = render_to_string(partial: 'shared/recaptcha_form', formats: :html, locals: locals) + # format.json is used by all new Vue-based CAPTCHA implementations, which + # 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 else @@ -58,7 +65,7 @@ module SpammableActions # 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. - captcha_response = params['g-recaptcha-response'] + captcha_response = params['g-recaptcha-response'] || params[:captcha_response] { request: request, diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 133490164df..cbb2a35d047 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -50,6 +50,8 @@ class Projects::CommitController < Projects::ApplicationController # rubocop: disable CodeReuse/ActiveRecord def pipelines + set_pipeline_feature_flag + @pipelines = @commit.pipelines.order(id: :desc) @pipelines = @pipelines.where(ref: params[:ref]).page(params[:page]).per(30) if params[:ref] @@ -124,6 +126,10 @@ class Projects::CommitController < Projects::ApplicationController private + def set_pipeline_feature_flag + push_frontend_feature_flag(:new_pipelines_table, @project, default_enabled: :yaml) + end + def create_new_branch? params[:create_merge_request].present? || !can?(current_user, :push_code, @project) end diff --git a/app/graphql/mutations/admin/sidekiq_queues/delete_jobs.rb b/app/graphql/mutations/admin/sidekiq_queues/delete_jobs.rb index 32ca6de9b96..ea1502d4b62 100644 --- a/app/graphql/mutations/admin/sidekiq_queues/delete_jobs.rb +++ b/app/graphql/mutations/admin/sidekiq_queues/delete_jobs.rb @@ -8,7 +8,7 @@ module Mutations 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, GraphQL::STRING_TYPE, required: false, diff --git a/app/mailers/previews/notify_preview.rb b/app/mailers/previews/notify_preview.rb index c70ac1428cd..f29d0d990b6 100644 --- a/app/mailers/previews/notify_preview.rb +++ b/app/mailers/previews/notify_preview.rb @@ -89,7 +89,7 @@ class NotifyPreview < ActionMailer::Preview end 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 def merged_merge_request_email diff --git a/app/models/hooks/web_hook_log.rb b/app/models/hooks/web_hook_log.rb index 03f1797f4f4..e2230a2d644 100644 --- a/app/models/hooks/web_hook_log.rb +++ b/app/models/hooks/web_hook_log.rb @@ -6,6 +6,8 @@ class WebHookLog < ApplicationRecord include DeleteWithLimit include CreatedAtFilterable + self.primary_key = :id + belongs_to :web_hook serialize :request_headers, Hash # rubocop:disable Cop/ActiveRecordSerialize diff --git a/app/models/hooks/web_hook_log_partitioned.rb b/app/models/hooks/web_hook_log_partitioned.rb new file mode 100644 index 00000000000..b4b150afb6a --- /dev/null +++ b/app/models/hooks/web_hook_log_partitioned.rb @@ -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 diff --git a/app/models/note.rb b/app/models/note.rb index 0bd1e8a08a2..5cdf1d2736e 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -30,7 +30,6 @@ class Note < ApplicationRecord # 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 - alias_attribute :last_edited_at, :updated_at alias_attribute :last_edited_by, :updated_by # Attribute containing rendered and redacted Markdown as generated by @@ -349,7 +348,13 @@ class Note < ApplicationRecord !system? 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. def edited? return false if updated_by.blank? diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb index 857ffbb6965..76f9b6369b3 100644 --- a/app/services/notes/update_service.rb +++ b/app/services/notes/update_service.rb @@ -7,12 +7,7 @@ module Notes old_mentioned_users = note.mentioned_users(current_user).to_a - note.assign_attributes(params.merge(updated_by: current_user)) - - note.with_transaction_returning_status do - update_confidentiality(note) - note.save - end + note.assign_attributes(params) track_note_edit_usage_for_issues(note) if note.for_issue? track_note_edit_usage_for_merge_requests(note) if note.for_merge_request? @@ -28,6 +23,15 @@ module Notes note.note = content 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? note.create_new_cross_references!(current_user) diff --git a/app/views/admin/application_settings/_abuse.html.haml b/app/views/admin/application_settings/_abuse.html.haml index ea9bdbed9ae..f050c0816b1 100644 --- a/app/views/admin/application_settings/_abuse.html.haml +++ b/app/views/admin/application_settings/_abuse.html.haml @@ -8,4 +8,4 @@ .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. - = f.submit 'Save changes', class: "gl-button btn btn-success" + = f.submit 'Save changes', class: "gl-button btn btn-confirm" diff --git a/app/views/admin/application_settings/_account_and_limit.html.haml b/app/views/admin/application_settings/_account_and_limit.html.haml index 009e0732911..2e5cf156a65 100644 --- a/app/views/admin/application_settings/_account_and_limit.html.haml +++ b/app/views/admin/application_settings/_account_and_limit.html.haml @@ -65,4 +65,4 @@ = 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 - = 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' diff --git a/app/views/admin/application_settings/_ci_cd.html.haml b/app/views/admin/application_settings/_ci_cd.html.haml index f11770b397e..0af244d54f3 100644 --- a/app/views/admin/application_settings/_ci_cd.html.haml +++ b/app/views/admin/application_settings/_ci_cd.html.haml @@ -68,4 +68,4 @@ = _("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' - = f.submit _('Save changes'), class: "gl-button btn btn-success" + = f.submit _('Save changes'), class: "gl-button btn btn-confirm" diff --git a/app/views/admin/application_settings/_diff_limits.html.haml b/app/views/admin/application_settings/_diff_limits.html.haml index 494558a6c2d..c5ae5c579ad 100644 --- a/app/views/admin/application_settings/_diff_limits.html.haml +++ b/app/views/admin/application_settings/_diff_limits.html.haml @@ -12,4 +12,4 @@ = link_to sprite_icon('question-o'), help_page_path('user/admin_area/diff_limits', 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' diff --git a/app/views/admin/application_settings/_eks.html.haml b/app/views/admin/application_settings/_eks.html.haml index c4bdb4ad7ca..1ddf927ed13 100644 --- a/app/views/admin/application_settings/_eks.html.haml +++ b/app/views/admin/application_settings/_eks.html.haml @@ -33,4 +33,4 @@ .form-text.text-muted = _('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" diff --git a/app/views/admin/application_settings/_email.html.haml b/app/views/admin/application_settings/_email.html.haml index 9eb00b5a61b..b22aaabe41a 100644 --- a/app/views/admin/application_settings/_email.html.haml +++ b/app/views/admin/application_settings/_email.html.haml @@ -33,4 +33,4 @@ .form-text.text-muted = _('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' } diff --git a/app/views/admin/application_settings/_external_authorization_service_form.html.haml b/app/views/admin/application_settings/_external_authorization_service_form.html.haml index 07256c9f4fe..97e09476e78 100644 --- a/app/views/admin/application_settings/_external_authorization_service_form.html.haml +++ b/app/views/admin/application_settings/_external_authorization_service_form.html.haml @@ -47,4 +47,4 @@ .form-group = 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.submit 'Save changes', class: "gl-button btn btn-success" + = f.submit 'Save changes', class: "gl-button btn btn-confirm" diff --git a/app/views/admin/application_settings/_gitaly.html.haml b/app/views/admin/application_settings/_gitaly.html.haml index 56ec35d9329..72e7cb0b437 100644 --- a/app/views/admin/application_settings/_gitaly.html.haml +++ b/app/views/admin/application_settings/_gitaly.html.haml @@ -24,4 +24,4 @@ .form-text.text-muted 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" diff --git a/app/views/admin/application_settings/_gitpod.html.haml b/app/views/admin/application_settings/_gitpod.html.haml index cca81136bb9..531ddcdb86f 100644 --- a/app/views/admin/application_settings/_gitpod.html.haml +++ b/app/views/admin/application_settings/_gitpod.html.haml @@ -25,4 +25,4 @@ = f.text_field :gitpod_url, class: 'form-control gl-form-input', placeholder: s_('Gitpod|e.g. https://gitpod.example.com') .form-text.text-muted = 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' diff --git a/app/views/admin/application_settings/_grafana.html.haml b/app/views/admin/application_settings/_grafana.html.haml index 368b4db4549..fd9e7ee50c4 100644 --- a/app/views/admin/application_settings/_grafana.html.haml +++ b/app/views/admin/application_settings/_grafana.html.haml @@ -14,4 +14,4 @@ = f.label :grafana_url, _('Grafana URL'), class: 'label-bold' = 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" diff --git a/app/views/admin/application_settings/_help_page.html.haml b/app/views/admin/application_settings/_help_page.html.haml index 858df44bd98..e7816f5a1c0 100644 --- a/app/views/admin/application_settings/_help_page.html.haml +++ b/app/views/admin/application_settings/_help_page.html.haml @@ -23,4 +23,4 @@ = 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.submit _('Save changes'), class: "gl-button btn btn-success" + = f.submit _('Save changes'), class: "gl-button btn btn-confirm" diff --git a/app/views/admin/application_settings/_import_export_limits.html.haml b/app/views/admin/application_settings/_import_export_limits.html.haml index e1a58c888a5..820c11279d5 100644 --- a/app/views/admin/application_settings/_import_export_limits.html.haml +++ b/app/views/admin/application_settings/_import_export_limits.html.haml @@ -31,4 +31,4 @@ = 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.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' } diff --git a/app/views/admin/application_settings/_initial_branch_name.html.haml b/app/views/admin/application_settings/_initial_branch_name.html.haml index e7718f94b90..b5c178641df 100644 --- a/app/views/admin/application_settings/_initial_branch_name.html.haml +++ b/app/views/admin/application_settings/_initial_branch_name.html.haml @@ -10,4 +10,4 @@ %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 - = f.submit _('Save changes'), class: 'gl-button btn-success' + = f.submit _('Save changes'), class: 'gl-button btn-confirm' diff --git a/app/views/admin/application_settings/_ip_limits.html.haml b/app/views/admin/application_settings/_ip_limits.html.haml index a603eaec913..18d71a90e34 100644 --- a/app/views/admin/application_settings/_ip_limits.html.haml +++ b/app/views/admin/application_settings/_ip_limits.html.haml @@ -57,4 +57,4 @@ = _('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.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' } diff --git a/app/views/admin/application_settings/_issue_limits.html.haml b/app/views/admin/application_settings/_issue_limits.html.haml index e16561b4489..0e1ba8c9c88 100644 --- a/app/views/admin/application_settings/_issue_limits.html.haml +++ b/app/views/admin/application_settings/_issue_limits.html.haml @@ -6,4 +6,4 @@ = 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.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' } diff --git a/app/views/admin/application_settings/_kroki.html.haml b/app/views/admin/application_settings/_kroki.html.haml index cd57d4cca65..fc51942ed1f 100644 --- a/app/views/admin/application_settings/_kroki.html.haml +++ b/app/views/admin/application_settings/_kroki.html.haml @@ -30,4 +30,4 @@ = f.check_box format[:name], class: 'form-check-input' = 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" diff --git a/app/views/admin/application_settings/_localization.html.haml b/app/views/admin/application_settings/_localization.html.haml index 5ad7080b22b..fdb91937ec3 100644 --- a/app/views/admin/application_settings/_localization.html.haml +++ b/app/views/admin/application_settings/_localization.html.haml @@ -15,4 +15,4 @@ = f.label :time_tracking_limit_to_hours, class: 'form-check-label' do = _('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" diff --git a/app/views/admin/application_settings/_note_limits.html.haml b/app/views/admin/application_settings/_note_limits.html.haml index 9578da90170..d50b3395d8f 100644 --- a/app/views/admin/application_settings/_note_limits.html.haml +++ b/app/views/admin/application_settings/_note_limits.html.haml @@ -9,4 +9,4 @@ = 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.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' } diff --git a/app/views/admin/application_settings/_outbound.html.haml b/app/views/admin/application_settings/_outbound.html.haml index 694cc9deab6..d8d105293a1 100644 --- a/app/views/admin/application_settings/_outbound.html.haml +++ b/app/views/admin/application_settings/_outbound.html.haml @@ -27,4 +27,4 @@ %span.form-text.text-muted = _('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' } diff --git a/app/views/admin/application_settings/_package_registry.html.haml b/app/views/admin/application_settings/_package_registry.html.haml index 19801a3e01c..0ca8493c596 100644 --- a/app/views/admin/application_settings/_package_registry.html.haml +++ b/app/views/admin/application_settings/_package_registry.html.haml @@ -47,4 +47,4 @@ .form-group = 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.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' diff --git a/app/views/admin/application_settings/_pages.html.haml b/app/views/admin/application_settings/_pages.html.haml index 503aae861d0..8f52e8b8461 100644 --- a/app/views/admin/application_settings/_pages.html.haml +++ b/app/views/admin/application_settings/_pages.html.haml @@ -41,4 +41,4 @@ - terms_of_service_link_start = ''.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: ''.html_safe } - = f.submit _('Save changes'), class: "gl-button btn btn-success" + = f.submit _('Save changes'), class: "gl-button btn btn-confirm" diff --git a/app/views/admin/application_settings/_performance.html.haml b/app/views/admin/application_settings/_performance.html.haml index 3efe163de7b..5ee68e8fd16 100644 --- a/app/views/admin/application_settings/_performance.html.haml +++ b/app/views/admin/application_settings/_performance.html.haml @@ -31,4 +31,4 @@ .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.') - = f.submit 'Save changes', class: "gl-button btn btn-success" + = f.submit 'Save changes', class: "gl-button btn btn-confirm" diff --git a/app/views/admin/application_settings/_performance_bar.html.haml b/app/views/admin/application_settings/_performance_bar.html.haml index 2db22552596..21345e4d80e 100644 --- a/app/views/admin/application_settings/_performance_bar.html.haml +++ b/app/views/admin/application_settings/_performance_bar.html.haml @@ -11,4 +11,4 @@ = 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.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' diff --git a/app/views/admin/application_settings/_plantuml.html.haml b/app/views/admin/application_settings/_plantuml.html.haml index 93fcc90f044..e6e9bbf3ee0 100644 --- a/app/views/admin/application_settings/_plantuml.html.haml +++ b/app/views/admin/application_settings/_plantuml.html.haml @@ -24,4 +24,4 @@ = link_to "PlantUML", "http://plantuml.com" 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" diff --git a/app/views/admin/application_settings/_prometheus.html.haml b/app/views/admin/application_settings/_prometheus.html.haml index c394bc65046..468c1786d6f 100644 --- a/app/views/admin/application_settings/_prometheus.html.haml +++ b/app/views/admin/application_settings/_prometheus.html.haml @@ -30,4 +30,4 @@ A method call is only tracked when it takes longer to complete than 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" diff --git a/app/views/admin/application_settings/_protected_paths.html.haml b/app/views/admin/application_settings/_protected_paths.html.haml index 57bba4f970a..faa675f211d 100644 --- a/app/views/admin/application_settings/_protected_paths.html.haml +++ b/app/views/admin/application_settings/_protected_paths.html.haml @@ -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: ''.html_safe } = 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' diff --git a/app/views/admin/application_settings/_realtime.html.haml b/app/views/admin/application_settings/_realtime.html.haml index 2b54a15d615..bee120d2f78 100644 --- a/app/views/admin/application_settings/_realtime.html.haml +++ b/app/views/admin/application_settings/_realtime.html.haml @@ -14,4 +14,4 @@ installations. Set to 0 to completely disable 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" diff --git a/app/views/admin/application_settings/_registry.html.haml b/app/views/admin/application_settings/_registry.html.haml index 5fb5effaa55..fc03a6dd10c 100644 --- a/app/views/admin/application_settings/_registry.html.haml +++ b/app/views/admin/application_settings/_registry.html.haml @@ -31,4 +31,4 @@ .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.") - = f.submit 'Save changes', class: "gl-button btn btn-success" + = f.submit 'Save changes', class: "gl-button btn btn-confirm" diff --git a/app/views/admin/application_settings/_repository_check.html.haml b/app/views/admin/application_settings/_repository_check.html.haml index 24e74dd0f1b..ee0281b6e33 100644 --- a/app/views/admin/application_settings/_repository_check.html.haml +++ b/app/views/admin/application_settings/_repository_check.html.haml @@ -55,4 +55,4 @@ .form-text.text-muted 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" diff --git a/app/views/admin/application_settings/_repository_mirrors_form.html.haml b/app/views/admin/application_settings/_repository_mirrors_form.html.haml index 125fa48bbc3..a0076a2f75d 100644 --- a/app/views/admin/application_settings/_repository_mirrors_form.html.haml +++ b/app/views/admin/application_settings/_repository_mirrors_form.html.haml @@ -14,4 +14,4 @@ = 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" diff --git a/app/views/admin/application_settings/_repository_static_objects.html.haml b/app/views/admin/application_settings/_repository_static_objects.html.haml index 42fe2b24bb2..f8ec04003fa 100644 --- a/app/views/admin/application_settings/_repository_static_objects.html.haml +++ b/app/views/admin/application_settings/_repository_static_objects.html.haml @@ -15,4 +15,4 @@ %span.form-text.text-muted#static_objects_external_storage_auth_token_help_block = _('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" diff --git a/app/views/admin/application_settings/_repository_storage.html.haml b/app/views/admin/application_settings/_repository_storage.html.haml index 0862d1bf0b6..68dfe35300b 100644 --- a/app/views/admin/application_settings/_repository_storage.html.haml +++ b/app/views/admin/application_settings/_repository_storage.html.haml @@ -22,4 +22,4 @@ = f.text_field attribute[:name], class: 'form-text-input', value: attribute[:value] = f.label attribute[:label], attribute[:label], class: 'label-bold form-check-label' %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" diff --git a/app/views/admin/application_settings/_signin.html.haml b/app/views/admin/application_settings/_signin.html.haml index 23a7856e483..c6f7266defe 100644 --- a/app/views/admin/application_settings/_signin.html.haml +++ b/app/views/admin/application_settings/_signin.html.haml @@ -57,4 +57,4 @@ = 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 .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" diff --git a/app/views/admin/application_settings/_signup.html.haml b/app/views/admin/application_settings/_signup.html.haml index 82824f1d436..272eba67b1b 100644 --- a/app/views/admin/application_settings/_signup.html.haml +++ b/app/views/admin/application_settings/_signup.html.haml @@ -77,4 +77,4 @@ = f.label :after_sign_up_text, class: 'label-bold' = f.text_area :after_sign_up_text, class: 'form-control gl-form-input', rows: 4 .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' } diff --git a/app/views/admin/application_settings/_snowplow.html.haml b/app/views/admin/application_settings/_snowplow.html.haml index 5f5a3a6992c..e6ac2a4db34 100644 --- a/app/views/admin/application_settings/_snowplow.html.haml +++ b/app/views/admin/application_settings/_snowplow.html.haml @@ -26,4 +26,4 @@ = f.label :snowplow_cookie_domain, _('Cookie domain'), class: 'label-light' = 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' diff --git a/app/views/admin/application_settings/_sourcegraph.html.haml b/app/views/admin/application_settings/_sourcegraph.html.haml index 0ad34e6ad7e..af25577f058 100644 --- a/app/views/admin/application_settings/_sourcegraph.html.haml +++ b/app/views/admin/application_settings/_sourcegraph.html.haml @@ -35,4 +35,4 @@ = f.text_field :sourcegraph_url, class: 'form-control gl-form-input', placeholder: s_('SourcegraphAdmin|e.g. https://sourcegraph.example.com') .form-text.text-muted = 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' diff --git a/app/views/admin/application_settings/_spam.html.haml b/app/views/admin/application_settings/_spam.html.haml index 6085cea4f5d..2086fbc9d32 100644 --- a/app/views/admin/application_settings/_spam.html.haml +++ b/app/views/admin/application_settings/_spam.html.haml @@ -79,4 +79,4 @@ = 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.submit 'Save changes', class: "gl-button btn btn-success" + = f.submit 'Save changes', class: "gl-button btn btn-confirm" diff --git a/app/views/admin/application_settings/_terminal.html.haml b/app/views/admin/application_settings/_terminal.html.haml index 8f89cf27291..487ce25a4da 100644 --- a/app/views/admin/application_settings/_terminal.html.haml +++ b/app/views/admin/application_settings/_terminal.html.haml @@ -8,4 +8,4 @@ .form-text.text-muted Maximum time for web terminal websocket connection (in seconds). 0 for unlimited. - = f.submit 'Save changes', class: "gl-button btn btn-success" + = f.submit 'Save changes', class: "gl-button btn btn-confirm" diff --git a/app/views/admin/application_settings/_terms.html.haml b/app/views/admin/application_settings/_terms.html.haml index 717b2220336..8cc4169b383 100644 --- a/app/views/admin/application_settings/_terms.html.haml +++ b/app/views/admin/application_settings/_terms.html.haml @@ -15,4 +15,4 @@ = f.text_area :terms, class: 'form-control gl-form-input', rows: 8 .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" diff --git a/app/views/admin/application_settings/_third_party_offers.html.haml b/app/views/admin/application_settings/_third_party_offers.html.haml index 32023b11993..970c9c6b003 100644 --- a/app/views/admin/application_settings/_third_party_offers.html.haml +++ b/app/views/admin/application_settings/_third_party_offers.html.haml @@ -17,4 +17,4 @@ = 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.submit _('Save changes'), class: "gl-button btn btn-success" + = f.submit _('Save changes'), class: "gl-button btn btn-confirm" diff --git a/app/views/admin/application_settings/_usage.html.haml b/app/views/admin/application_settings/_usage.html.haml index 6abb7abf4a9..00306e1ba06 100644 --- a/app/views/admin/application_settings/_usage.html.haml +++ b/app/views/admin/application_settings/_usage.html.haml @@ -37,4 +37,4 @@ - deactivating_usage_ping_link_start = ''.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: ''.html_safe } - = f.submit 'Save changes', class: "gl-button btn btn-success" + = f.submit 'Save changes', class: "gl-button btn btn-confirm" diff --git a/app/views/admin/application_settings/_visibility_and_access.html.haml b/app/views/admin/application_settings/_visibility_and_access.html.haml index 0931ba50aa7..e51a41d5254 100644 --- a/app/views/admin/application_settings/_visibility_and_access.html.haml +++ b/app/views/admin/application_settings/_visibility_and_access.html.haml @@ -74,4 +74,4 @@ = f.label :disable_feed_token, class: 'form-check-label' do = 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" diff --git a/app/views/admin/application_settings/general.html.haml b/app/views/admin/application_settings/general.html.haml index ee045f1c904..3a14b4fbc7b 100644 --- a/app/views/admin/application_settings/general.html.haml +++ b/app/views/admin/application_settings/general.html.haml @@ -101,7 +101,7 @@ = s_('IDE|Live Preview') %span.form-text.text-muted = 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 'admin/application_settings/gitpod' diff --git a/app/views/notify/merge_request_status_email.text.haml b/app/views/notify/merge_request_status_email.text.haml index 3d7115856d4..ab663b65199 100644 --- a/app/views/notify/merge_request_status_email.text.haml +++ b/app/views/notify/merge_request_status_email.text.haml @@ -6,3 +6,4 @@ Merge Request URL: #{project_merge_request_url(@merge_request.target_project, @m Author: #{sanitize_name(@merge_request.author_name)} = assignees_label(@merge_request) += reviewers_label(@merge_request) diff --git a/app/views/projects/ci/pipeline_editor/show.html.haml b/app/views/projects/ci/pipeline_editor/show.html.haml index a6c031fd80b..3e10cf49b66 100644 --- a/app/views/projects/ci/pipeline_editor/show.html.haml +++ b/app/views/projects/ci/pipeline_editor/show.html.haml @@ -4,6 +4,7 @@ "commit-sha" => @project.commit ? @project.commit.sha : '', "default-branch" => @project.default_branch, "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'), "new-merge-request-path" => namespace_project_new_merge_request_path, "project-path" => @project.path, diff --git a/app/views/projects/mirrors/_mirror_repos.html.haml b/app/views/projects/mirrors/_mirror_repos.html.haml index a0cea365162..bcdc2988e49 100644 --- a/app/views/projects/mirrors/_mirror_repos.html.haml +++ b/app/views/projects/mirrors/_mirror_repos.html.haml @@ -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' } } .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 = expanded ? _('Collapse') : _('Expand') %p diff --git a/app/workers/concerns/application_worker.rb b/app/workers/concerns/application_worker.rb index d101ef100d8..0de26e27631 100644 --- a/app/workers/concerns/application_worker.rb +++ b/app/workers/concerns/application_worker.rb @@ -18,7 +18,7 @@ module ApplicationWorker set_queue def structured_payload(payload = {}) - context = Labkit::Context.current.to_h.merge( + context = Gitlab::ApplicationContext.current.merge( 'class' => self.class.name, 'job_status' => 'running', 'queue' => self.class.queue, diff --git a/app/workers/concerns/cronjob_queue.rb b/app/workers/concerns/cronjob_queue.rb index 955387b5ad4..b89d6bba72c 100644 --- a/app/workers/concerns/cronjob_queue.rb +++ b/app/workers/concerns/cronjob_queue.rb @@ -15,7 +15,7 @@ module CronjobQueue # Cronjobs never get scheduled with arguments, so this is safe to # override 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") end diff --git a/changelogs/unreleased/205484-05-project-settings-headers-repository-mirroring-repositories.yml b/changelogs/unreleased/205484-05-project-settings-headers-repository-mirroring-repositories.yml new file mode 100644 index 00000000000..e471108aa3b --- /dev/null +++ b/changelogs/unreleased/205484-05-project-settings-headers-repository-mirroring-repositories.yml @@ -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 diff --git a/changelogs/unreleased/300750-add-missing-reviewers-information-to-merge_request_status_email.yml b/changelogs/unreleased/300750-add-missing-reviewers-information-to-merge_request_status_email.yml new file mode 100644 index 00000000000..83712f4b799 --- /dev/null +++ b/changelogs/unreleased/300750-add-missing-reviewers-information-to-merge_request_status_email.yml @@ -0,0 +1,5 @@ +--- +title: Add reviewers detail to merge request status email +merge_request: 55584 +author: +type: added diff --git a/changelogs/unreleased/322901-fix-pagerduty-webhook.yml b/changelogs/unreleased/322901-fix-pagerduty-webhook.yml new file mode 100644 index 00000000000..96063a0f937 --- /dev/null +++ b/changelogs/unreleased/322901-fix-pagerduty-webhook.yml @@ -0,0 +1,5 @@ +--- +title: 'Fixes: No such file or directory lib/pager_duty/validator/schemas/message.json' +merge_request: 55725 +author: +type: fixed diff --git a/changelogs/unreleased/323674-initiate-web-hook-logs-partitioning.yml b/changelogs/unreleased/323674-initiate-web-hook-logs-partitioning.yml new file mode 100644 index 00000000000..7b36ed31e4d --- /dev/null +++ b/changelogs/unreleased/323674-initiate-web-hook-logs-partitioning.yml @@ -0,0 +1,5 @@ +--- +title: Add web_hook_logs partitioning migration +merge_request: 55938 +author: +type: other diff --git a/changelogs/unreleased/btn-confirm-admin-application-settings.yml b/changelogs/unreleased/btn-confirm-admin-application-settings.yml new file mode 100644 index 00000000000..d76e20b410a --- /dev/null +++ b/changelogs/unreleased/btn-confirm-admin-application-settings.yml @@ -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 diff --git a/changelogs/unreleased/fix-edited-timestamp.yml b/changelogs/unreleased/fix-edited-timestamp.yml new file mode 100644 index 00000000000..83b8e4fcf21 --- /dev/null +++ b/changelogs/unreleased/fix-edited-timestamp.yml @@ -0,0 +1,5 @@ +--- +title: Fix edited timestamp updated when transforming / resolving comments +merge_request: 55671 +author: Mycroft Kang @TaehyeokKang +type: fixed diff --git a/changelogs/unreleased/pipeline-editor-branch-param.yml b/changelogs/unreleased/pipeline-editor-branch-param.yml new file mode 100644 index 00000000000..4a58c4a4676 --- /dev/null +++ b/changelogs/unreleased/pipeline-editor-branch-param.yml @@ -0,0 +1,5 @@ +--- +title: Allow users to work on non-default branch in pipeline editor +merge_request: 55413 +author: +type: added diff --git a/config/initializers/postgres_partitioning.rb b/config/initializers/postgres_partitioning.rb index 3cea8575cc7..6b64ea3f83d 100644 --- a/config/initializers/postgres_partitioning.rb +++ b/config/initializers/postgres_partitioning.rb @@ -4,6 +4,7 @@ # (even with eager loading disabled). Gitlab::Database::Partitioning::PartitionCreator.register(AuditEvent) +Gitlab::Database::Partitioning::PartitionCreator.register(WebHookLogPartitioned) begin Gitlab::Database::Partitioning::PartitionCreator.new.create_partitions unless ENV['DISABLE_POSTGRES_PARTITION_CREATION_ON_STARTUP'] diff --git a/db/migrate/20210303053341_add_last_edited_at_and_last_edited_by_id_to_notes.rb b/db/migrate/20210303053341_add_last_edited_at_and_last_edited_by_id_to_notes.rb new file mode 100644 index 00000000000..b6f4da202fa --- /dev/null +++ b/db/migrate/20210303053341_add_last_edited_at_and_last_edited_by_id_to_notes.rb @@ -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 diff --git a/db/migrate/20210306121300_partition_web_hook_logs.rb b/db/migrate/20210306121300_partition_web_hook_logs.rb new file mode 100644 index 00000000000..ff35a19648e --- /dev/null +++ b/db/migrate/20210306121300_partition_web_hook_logs.rb @@ -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 diff --git a/db/post_migrate/20210306121310_backfill_partitioned_web_hook_logs.rb b/db/post_migrate/20210306121310_backfill_partitioned_web_hook_logs.rb new file mode 100644 index 00000000000..3a37d8a8510 --- /dev/null +++ b/db/post_migrate/20210306121310_backfill_partitioned_web_hook_logs.rb @@ -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 diff --git a/db/schema_migrations/20210303053341 b/db/schema_migrations/20210303053341 new file mode 100644 index 00000000000..115b5655f4f --- /dev/null +++ b/db/schema_migrations/20210303053341 @@ -0,0 +1 @@ +3bd7e839c4f93716a7e893bf9184306a1fcfd401e5b54f4393e5138e2776f5e0 \ No newline at end of file diff --git a/db/schema_migrations/20210306121300 b/db/schema_migrations/20210306121300 new file mode 100644 index 00000000000..f46097b1126 --- /dev/null +++ b/db/schema_migrations/20210306121300 @@ -0,0 +1 @@ +44d53ac15c5e54c2f1c825286155dec643b82573184026caaf08288512168aef \ No newline at end of file diff --git a/db/schema_migrations/20210306121310 b/db/schema_migrations/20210306121310 new file mode 100644 index 00000000000..8713032f2d4 --- /dev/null +++ b/db/schema_migrations/20210306121310 @@ -0,0 +1 @@ +90072e3dee4517061ff9e08decda7fecb9cc9b38a56345c09685e3cce48a8b66 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 8a7ddfdfa02..e42eee4a99e 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -41,6 +41,62 @@ RETURN NULL; 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 LANGUAGE plpgsql AS $$ @@ -114,6 +170,23 @@ CREATE TABLE audit_events ( ) 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 ( id bigint NOT NULL, project_id integer NOT NULL, @@ -14613,7 +14686,8 @@ CREATE TABLE notes ( change_position text, resolved_by_push boolean, review_id bigint, - confidential boolean + confidential boolean, + last_edited_at timestamp with time zone ); CREATE SEQUENCE notes_id_seq @@ -21254,6 +21328,9 @@ ALTER TABLE ONLY vulnerability_statistics ALTER TABLE ONLY vulnerability_user_mentions 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 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; +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 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(); diff --git a/doc/administration/logs.md b/doc/administration/logs.md index 8b6617210a4..1950403ce35 100644 --- a/doc/administration/logs.md +++ b/doc/administration/logs.md @@ -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. This guide talks about how to read and use these system log files. -Read more about how to -[customize logging on Omnibus GitLab installations](https://docs.gitlab.com/omnibus/settings/logs.html) +Read more about the log system and using the logs: + +- [Customize logging on Omnibus GitLab installations](https://docs.gitlab.com/omnibus/settings/logs.html) including adjusting log retention, log forwarding, switching logs from JSON to plain text logging, and more. +- [How to parse and analyze JSON logs](troubleshooting/log_parsing.md). ## `production_json.log` diff --git a/doc/administration/operations/rails_console.md b/doc/administration/operations/rails_console.md index b40560bf6e5..5add52e1a51 100644 --- a/doc/administration/operations/rails_console.md +++ b/doc/administration/operations/rails_console.md @@ -148,3 +148,23 @@ Traceback (most recent call last): [traceback removed] /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 +``` diff --git a/doc/administration/troubleshooting/log_parsing.md b/doc/administration/troubleshooting/log_parsing.md index 25300d036ed..2900ce58940 100644 --- a/doc/administration/troubleshooting/log_parsing.md +++ b/doc/administration/troubleshooting/log_parsing.md @@ -41,6 +41,20 @@ jq -cR 'fromjson?' file.json | jq 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. +#### 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` #### Find all requests with a 5XX status code diff --git a/doc/api/members.md b/doc/api/members.md index 0948c82b05f..286be10dd6e 100644 --- a/doc/api/members.md +++ b/doc/api/members.md @@ -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: " "https://gitlab.example.com/api/v4/groups/:id/billable_members/:user_id" +``` + ## Add a member to a group or project Adds a member to a group or project. diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md index 1b04561c3c2..2a0e1207bf5 100644 --- a/doc/user/project/pages/index.md +++ b/doc/user/project/pages/index.md @@ -46,9 +46,9 @@ To create a GitLab Pages website: | 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 `.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. | To update a GitLab Pages website: diff --git a/lib/api/admin/sidekiq.rb b/lib/api/admin/sidekiq.rb index 7e561783685..d91d4a0d4d5 100644 --- a/lib/api/admin/sidekiq.rb +++ b/lib/api/admin/sidekiq.rb @@ -12,11 +12,11 @@ module API namespace 'queues' do desc 'Drop jobs matching the given metadata from the Sidekiq queue' params do - Labkit::Context::KNOWN_KEYS.each do |key| + Gitlab::ApplicationContext::KNOWN_KEYS.each do |key| optional key, type: String, allow_blank: false end - at_least_one_of(*Labkit::Context::KNOWN_KEYS) + at_least_one_of(*Gitlab::ApplicationContext::KNOWN_KEYS) end delete ':queue_name' do result = diff --git a/lib/gitlab/application_context.rb b/lib/gitlab/application_context.rb index 5d07610feb5..5675019ce83 100644 --- a/lib/gitlab/application_context.rb +++ b/lib/gitlab/application_context.rb @@ -7,6 +7,9 @@ module Gitlab Attribute = Struct.new(:name, :type) + LOG_KEY = Labkit::Context::LOG_KEY + KNOWN_KEYS = Labkit::Context::KNOWN_KEYS + APPLICATION_ATTRIBUTES = [ Attribute.new(:project, Project), Attribute.new(:namespace, Namespace), @@ -22,6 +25,10 @@ module Gitlab application_context.use(&block) end + def self.with_raw_context(attributes = {}, &block) + Labkit::Context.with_context(attributes, &block) + end + def self.push(args) application_context = new(**args) Labkit::Context.push(application_context.to_lazy_hash) diff --git a/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers.rb b/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers.rb index f4cf576dda7..1c289391e21 100644 --- a/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers.rb +++ b/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers.rb @@ -9,7 +9,7 @@ module Gitlab include ::Gitlab::Database::MigrationHelpers include ::Gitlab::Database::Migrations::BackgroundMigrationHelpers - ALLOWED_TABLES = %w[audit_events].freeze + ALLOWED_TABLES = %w[audit_events web_hook_logs].freeze ERROR_SCOPE = 'table partitioning' MIGRATION_CLASS_NAME = "::#{module_parent_name}::BackfillPartitionedTable" diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb index e3788814dd5..f4a89edecd1 100644 --- a/lib/gitlab/gitaly_client.rb +++ b/lib/gitlab/gitaly_client.rb @@ -215,7 +215,7 @@ module Gitlab 'client_name' => CLIENT_NAME } - context_data = Labkit::Context.current&.to_h + context_data = Gitlab::ApplicationContext.current feature_stack = Thread.current[:gitaly_feature_stack] feature = feature_stack && feature_stack[0] diff --git a/lib/gitlab/grape_logging/loggers/context_logger.rb b/lib/gitlab/grape_logging/loggers/context_logger.rb index 0a8f0872fbe..468a296886e 100644 --- a/lib/gitlab/grape_logging/loggers/context_logger.rb +++ b/lib/gitlab/grape_logging/loggers/context_logger.rb @@ -6,7 +6,7 @@ module Gitlab module Loggers class ContextLogger < ::GrapeLogging::Loggers::Base def parameters(_, _) - Labkit::Context.current.to_h + Gitlab::ApplicationContext.current end end end diff --git a/lib/gitlab/sidekiq_queue.rb b/lib/gitlab/sidekiq_queue.rb index 807c27a71ff..4b71dfc0c1b 100644 --- a/lib/gitlab/sidekiq_queue.rb +++ b/lib/gitlab/sidekiq_queue.rb @@ -21,7 +21,7 @@ module Gitlab job_search_metadata = search_metadata .stringify_keys - .slice(*Labkit::Context::KNOWN_KEYS) + .slice(*Gitlab::ApplicationContext::KNOWN_KEYS) .transform_keys { |key| "meta.#{key}" } .compact diff --git a/lib/pager_duty/webhook_payload_parser.rb b/lib/pager_duty/webhook_payload_parser.rb index 11071926cf2..c17e3df1a72 100644 --- a/lib/pager_duty/webhook_payload_parser.rb +++ b/lib/pager_duty/webhook_payload_parser.rb @@ -2,7 +2,7 @@ module PagerDuty 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) @payload = payload @@ -66,7 +66,7 @@ module PagerDuty end def valid_message?(message) - ::JSONSchemer.schema(Pathname.new(SCHEMA_PATH)).valid?(message) + ::JSONSchemer.schema(SCHEMA_PATH).valid?(message) end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 7f2e2d7da27..3b1c61bb243 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -34456,6 +34456,9 @@ msgstr "" msgid "You must set up incoming email before it becomes active." 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." msgstr "" diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index 4a729008e67..6641a3ed914 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -900,7 +900,7 @@ RSpec.describe ApplicationController do feature_category :issue_tracking def index - Labkit::Context.with_context do |context| + Gitlab::ApplicationContext.with_raw_context do |context| render json: context.to_h end end diff --git a/spec/controllers/concerns/spammable_actions_spec.rb b/spec/controllers/concerns/spammable_actions_spec.rb index 25d5398c9da..7bd5a76e60c 100644 --- a/spec/controllers/concerns/spammable_actions_spec.rb +++ b/spec/controllers/concerns/spammable_actions_spec.rb @@ -69,8 +69,11 @@ RSpec.describe SpammableActions do end context 'when spammable.render_recaptcha? is true' do + let(:spam_log) { instance_double(SpamLog, id: 123) } + let(:captcha_site_key) { 'abc123' } + before do - expect(spammable).to receive(:render_recaptcha?) { true } + expect(spammable).to receive(:render_recaptcha?).at_least(:once) { true } end context 'when format is :html' do @@ -83,24 +86,24 @@ RSpec.describe SpammableActions do context 'when format is :json' do let(:format) { :json } - let(:recaptcha_html) { '' } - it 'renders json with recaptcha_html' do - expect(controller).to receive(:render_to_string).with( - { - partial: 'shared/recaptcha_form', - formats: :html, - locals: { - spammable: spammable, - script: false, - has_submit: false - } - } - ) { recaptcha_html } + before do + expect(spammable).to receive(:spam?) { false } + expect(spammable).to receive(:spam_log) { spam_log } + expect(Gitlab::CurrentSettings).to receive(:recaptcha_site_key) { captcha_site_key } + end + it 'renders json with spam_action_response_fields' do subject - expect(json_response).to eq({ 'recaptcha_html' => recaptcha_html }) + expected_json_response = HashWithIndifferentAccess.new( + { + spam: false, + needs_captcha_response: true, + spam_log_id: spam_log.id, + captcha_site_key: captcha_site_key + }) + expect(json_response).to eq(expected_json_response) end end end diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 81ffd2c4512..74062038248 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -9,6 +9,7 @@ RSpec.describe Projects::IssuesController do let_it_be(:project, reload: true) { create(:project) } let_it_be(:user, reload: true) { create(:user) } let(:issue) { create(:issue, project: project) } + let(:spam_action_response_fields) { { 'stub_spam_action_response_fields' => true } } describe "GET #index" do context 'external issue tracker' do @@ -613,12 +614,15 @@ RSpec.describe Projects::IssuesController do context 'when allow_possible_spam feature flag is false' do before do stub_feature_flags(allow_possible_spam: false) + expect(controller).to(receive(:spam_action_response_fields).with(issue)) do + spam_action_response_fields + end end - it 'renders json with recaptcha_html' do + it 'renders json with spam_action_response_fields' do subject - expect(json_response).to have_key('recaptcha_html') + expect(json_response).to eq(spam_action_response_fields) end end @@ -948,12 +952,17 @@ RSpec.describe Projects::IssuesController do context 'renders properly' do render_views - it 'renders recaptcha_html json response' do + before do + expect(controller).to(receive(:spam_action_response_fields).with(issue)) do + spam_action_response_fields + end + end + + it 'renders spam_action_response_fields json response' do update_issue - expect(response).to have_gitlab_http_status(:ok) - expect(json_response).to have_key('recaptcha_html') - expect(json_response['recaptcha_html']).not_to be_empty + expect(response).to have_gitlab_http_status(:conflict) + expect(json_response).to eq(spam_action_response_fields) end end end diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb index c31ba6fe156..5bee7698c3f 100644 --- a/spec/controllers/sessions_controller_spec.rb +++ b/spec/controllers/sessions_controller_spec.rb @@ -524,7 +524,7 @@ RSpec.describe SessionsController do it 'sets the username and caller_id in the context' do expect(controller).to receive(:destroy).and_wrap_original do |m, *args| - expect(Labkit::Context.current.to_h) + expect(Gitlab::ApplicationContext.current) .to include('meta.user' => user.username, 'meta.caller_id' => 'SessionsController#destroy') @@ -538,9 +538,9 @@ RSpec.describe SessionsController do context 'when not signed in' do it 'sets the caller_id in the context' do expect(controller).to receive(:new).and_wrap_original do |m, *args| - expect(Labkit::Context.current.to_h) + expect(Gitlab::ApplicationContext.current) .to include('meta.caller_id' => 'SessionsController#new') - expect(Labkit::Context.current.to_h) + expect(Gitlab::ApplicationContext.current) .not_to include('meta.user') m.call(*args) @@ -557,9 +557,9 @@ RSpec.describe SessionsController do it 'sets the caller_id in the context' do allow_any_instance_of(User).to receive(:lock_access!).and_wrap_original do |m, *args| - expect(Labkit::Context.current.to_h) + expect(Gitlab::ApplicationContext.current) .to include('meta.caller_id' => 'SessionsController#create') - expect(Labkit::Context.current.to_h) + expect(Gitlab::ApplicationContext.current) .not_to include('meta.user') m.call(*args) diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb index 2f0f7376206..d292ba60a12 100644 --- a/spec/db/schema_spec.rb +++ b/spec/db/schema_spec.rb @@ -87,7 +87,8 @@ RSpec.describe 'Database schema' do users_star_projects: %w[user_id], vulnerability_identifiers: %w[external_id], vulnerability_scanners: %w[external_id], - web_hooks: %w[group_id] + web_hooks: %w[group_id], + web_hook_logs_part_0c5294f417: %w[web_hook_id] }.with_indifferent_access.freeze context 'for table' do diff --git a/spec/frontend/captcha/captcha_modal_axios_interceptor_spec.js b/spec/frontend/captcha/captcha_modal_axios_interceptor_spec.js new file mode 100644 index 00000000000..df81b78d010 --- /dev/null +++ b/spec/frontend/captcha/captcha_modal_axios_interceptor_spec.js @@ -0,0 +1,119 @@ +import MockAdapter from 'axios-mock-adapter'; + +import { registerCaptchaModalInterceptor } from '~/captcha/captcha_modal_axios_interceptor'; +import { waitForCaptchaToBeSolved } from '~/captcha/wait_for_captcha_to_be_solved'; +import axios from '~/lib/utils/axios_utils'; +import httpStatusCodes from '~/lib/utils/http_status'; + +jest.mock('~/captcha/wait_for_captcha_to_be_solved'); + +describe('registerCaptchaModalInterceptor', () => { + const SPAM_LOG_ID = 'SPAM_LOG_ID'; + const CAPTCHA_SITE_KEY = 'CAPTCHA_SITE_KEY'; + const CAPTCHA_SUCCESS = 'CAPTCHA_SUCCESS'; + const CAPTCHA_RESPONSE = 'CAPTCHA_RESPONSE'; + const AXIOS_RESPONSE = { text: 'AXIOS_RESPONSE' }; + const NEEDS_CAPTCHA_RESPONSE = { + needs_captcha_response: true, + captcha_site_key: CAPTCHA_SITE_KEY, + spam_log_id: SPAM_LOG_ID, + }; + + const unsupportedMethods = ['delete', 'get', 'head', 'options']; + const supportedMethods = ['patch', 'post', 'put']; + + let mock; + + beforeEach(() => { + mock = new MockAdapter(axios); + mock.onAny('/no-captcha').reply(200, AXIOS_RESPONSE); + mock.onAny('/error').reply(404, AXIOS_RESPONSE); + mock.onAny('/captcha').reply((config) => { + if (!supportedMethods.includes(config.method)) { + return [httpStatusCodes.METHOD_NOT_ALLOWED, { method: config.method }]; + } + + try { + const { captcha_response, spam_log_id, ...rest } = JSON.parse(config.data); + // eslint-disable-next-line babel/camelcase + if (captcha_response === CAPTCHA_RESPONSE && spam_log_id === SPAM_LOG_ID) { + return [httpStatusCodes.OK, { ...rest, method: config.method, CAPTCHA_SUCCESS }]; + } + } catch (e) { + return [httpStatusCodes.BAD_REQUEST, { method: config.method }]; + } + + return [httpStatusCodes.CONFLICT, NEEDS_CAPTCHA_RESPONSE]; + }); + + axios.interceptors.response.handlers = []; + registerCaptchaModalInterceptor(axios); + }); + + afterEach(() => { + mock.restore(); + }); + + describe.each([...supportedMethods, ...unsupportedMethods])('For HTTP method %s', (method) => { + it('successful requests are passed through', async () => { + const { data, status } = await axios[method]('/no-captcha'); + + expect(status).toEqual(httpStatusCodes.OK); + expect(data).toEqual(AXIOS_RESPONSE); + expect(mock.history[method]).toHaveLength(1); + }); + + it('error requests without needs_captcha_response_errors are passed through', async () => { + await expect(() => axios[method]('/error')).rejects.toThrow( + expect.objectContaining({ + response: expect.objectContaining({ + status: httpStatusCodes.NOT_FOUND, + data: AXIOS_RESPONSE, + }), + }), + ); + expect(mock.history[method]).toHaveLength(1); + }); + }); + + describe.each(supportedMethods)('For HTTP method %s', (method) => { + describe('error requests with needs_captcha_response_errors', () => { + const submittedData = { ID: 12345 }; + + it('re-submits request if captcha was solved correctly', async () => { + waitForCaptchaToBeSolved.mockResolvedValue(CAPTCHA_RESPONSE); + const { data: returnedData } = await axios[method]('/captcha', submittedData); + + expect(waitForCaptchaToBeSolved).toHaveBeenCalledWith(CAPTCHA_SITE_KEY); + + expect(returnedData).toEqual({ ...submittedData, CAPTCHA_SUCCESS, method }); + expect(mock.history[method]).toHaveLength(2); + }); + + it('does not re-submit request if captcha was not solved', async () => { + const error = new Error('Captcha not solved'); + waitForCaptchaToBeSolved.mockRejectedValue(error); + await expect(() => axios[method]('/captcha', submittedData)).rejects.toThrow(error); + + expect(waitForCaptchaToBeSolved).toHaveBeenCalledWith(CAPTCHA_SITE_KEY); + expect(mock.history[method]).toHaveLength(1); + }); + }); + }); + + describe.each(unsupportedMethods)('For HTTP method %s', (method) => { + it('ignores captcha response', async () => { + await expect(() => axios[method]('/captcha')).rejects.toThrow( + expect.objectContaining({ + response: expect.objectContaining({ + status: httpStatusCodes.METHOD_NOT_ALLOWED, + data: { method }, + }), + }), + ); + + expect(waitForCaptchaToBeSolved).not.toHaveBeenCalled(); + expect(mock.history[method]).toHaveLength(1); + }); + }); +}); diff --git a/spec/frontend/captcha/wait_for_captcha_to_be_solved_spec.js b/spec/frontend/captcha/wait_for_captcha_to_be_solved_spec.js new file mode 100644 index 00000000000..08d031a4fa7 --- /dev/null +++ b/spec/frontend/captcha/wait_for_captcha_to_be_solved_spec.js @@ -0,0 +1,56 @@ +import CaptchaModal from '~/captcha/captcha_modal.vue'; +import { waitForCaptchaToBeSolved } from '~/captcha/wait_for_captcha_to_be_solved'; + +jest.mock('~/captcha/captcha_modal.vue', () => ({ + mounted: jest.fn(), + render(h) { + return h('div', { attrs: { id: 'mock-modal' } }); + }, +})); + +describe('waitForCaptchaToBeSolved', () => { + const response = 'CAPTCHA_RESPONSE'; + + const findModal = () => document.querySelector('#mock-modal'); + + it('opens a modal, resolves with captcha response on success', async () => { + CaptchaModal.mounted.mockImplementationOnce(function mounted() { + requestAnimationFrame(() => { + this.$emit('receivedCaptchaResponse', response); + this.$emit('hidden'); + }); + }); + + expect(findModal()).toBeNull(); + + const promise = waitForCaptchaToBeSolved('FOO'); + + expect(findModal()).not.toBeNull(); + + const result = await promise; + expect(result).toEqual(response); + + expect(findModal()).toBeNull(); + expect(document.body.innerHTML).toEqual(''); + }); + + it("opens a modal, rejects with error in case the captcha isn't solved", async () => { + CaptchaModal.mounted.mockImplementationOnce(function mounted() { + requestAnimationFrame(() => { + this.$emit('receivedCaptchaResponse', null); + this.$emit('hidden'); + }); + }); + + expect(findModal()).toBeNull(); + + const promise = waitForCaptchaToBeSolved('FOO'); + + expect(findModal()).not.toBeNull(); + + await expect(promise).rejects.toThrow(/You must solve the CAPTCHA in order to submit/); + + expect(findModal()).toBeNull(); + expect(document.body.innerHTML).toEqual(''); + }); +}); diff --git a/spec/frontend/issue_show/components/app_spec.js b/spec/frontend/issue_show/components/app_spec.js index d5d1e1d9cf8..b8860e93a22 100644 --- a/spec/frontend/issue_show/components/app_spec.js +++ b/spec/frontend/issue_show/components/app_spec.js @@ -166,40 +166,6 @@ describe('Issuable output', () => { }); }); - it('opens reCAPTCHA modal if update rejected as spam', () => { - let modal; - - jest.spyOn(wrapper.vm.service, 'updateIssuable').mockResolvedValue({ - data: { - recaptcha_html: '
recaptcha_html
', - }, - }); - - wrapper.vm.canUpdate = true; - wrapper.vm.showForm = true; - - return wrapper.vm - .$nextTick() - .then(() => { - wrapper.vm.$refs.recaptchaModal.scriptSrc = '//scriptsrc'; - return wrapper.vm.updateIssuable(); - }) - .then(() => { - modal = wrapper.find('.js-recaptcha-modal'); - expect(modal.isVisible()).toBe(true); - expect(modal.find('.g-recaptcha').text()).toEqual('recaptcha_html'); - expect(document.body.querySelector('.js-recaptcha-script').src).toMatch('//scriptsrc'); - }) - .then(() => { - modal.find('.close').trigger('click'); - return wrapper.vm.$nextTick(); - }) - .then(() => { - expect(modal.isVisible()).toBe(false); - expect(document.body.querySelector('.js-recaptcha-script')).toBeNull(); - }); - }); - describe('Pinned links propagated', () => { it.each` prop | value diff --git a/spec/frontend/pipeline_editor/components/commit/commit_form_spec.js b/spec/frontend/pipeline_editor/components/commit/commit_form_spec.js index 5dae77a4626..8040c9d701c 100644 --- a/spec/frontend/pipeline_editor/components/commit/commit_form_spec.js +++ b/spec/frontend/pipeline_editor/components/commit/commit_form_spec.js @@ -12,7 +12,7 @@ describe('Pipeline Editor | Commit Form', () => { wrapper = mountFn(CommitForm, { propsData: { defaultMessage: mockCommitMessage, - defaultBranch: mockDefaultBranch, + currentBranch: mockDefaultBranch, ...props, }, @@ -41,7 +41,7 @@ describe('Pipeline Editor | Commit Form', () => { expect(findCommitTextarea().attributes('value')).toBe(mockCommitMessage); }); - it('shows a default branch', () => { + it('shows current branch', () => { expect(findBranchInput().attributes('value')).toBe(mockDefaultBranch); }); @@ -66,7 +66,7 @@ describe('Pipeline Editor | Commit Form', () => { expect(wrapper.emitted('submit')[0]).toEqual([ { message: mockCommitMessage, - branch: mockDefaultBranch, + targetBranch: mockDefaultBranch, openMergeRequest: false, }, ]); @@ -101,7 +101,7 @@ describe('Pipeline Editor | Commit Form', () => { expect(wrapper.emitted('submit')[0]).toEqual([ { message: anotherMessage, - branch: anotherBranch, + targetBranch: anotherBranch, openMergeRequest: true, }, ]); diff --git a/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js b/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js index b87ff6ec0de..aa62144736c 100644 --- a/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js +++ b/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js @@ -35,7 +35,6 @@ const mockVariables = { const mockProvide = { ciConfigPath: mockCiConfigPath, - defaultBranch: mockDefaultBranch, projectFullPath: mockProjectFullPath, newMergeRequestPath: mockNewMergeRequestPath, }; @@ -64,6 +63,7 @@ describe('Pipeline Editor | Commit section', () => { data() { return { commitSha: mockCommitSha, + currentBranch: mockDefaultBranch, }; }, mocks: { @@ -116,7 +116,7 @@ describe('Pipeline Editor | Commit section', () => { await submitCommit(); }); - it('calls the mutation with the default branch', () => { + it('calls the mutation with the current branch', () => { expect(mockMutate).toHaveBeenCalledTimes(1); expect(mockMutate).toHaveBeenCalledWith({ mutation: commitCreate, diff --git a/spec/lib/gitlab/application_context_spec.rb b/spec/lib/gitlab/application_context_spec.rb index 88f865adea7..44e90994975 100644 --- a/spec/lib/gitlab/application_context_spec.rb +++ b/spec/lib/gitlab/application_context_spec.rb @@ -27,6 +27,20 @@ RSpec.describe Gitlab::ApplicationContext do end end + describe '.with_raw_context' do + it 'yields the block' do + expect { |b| described_class.with_raw_context({}, &b) }.to yield_control + end + + it 'passes the attributes unaltered on to labkit' do + attrs = { foo: :bar } + + expect(Labkit::Context).to receive(:with_context).with(attrs) + + described_class.with_raw_context(attrs) {} + end + end + describe '.push' do it 'passes the expected context on to labkit' do fake_proc = duck_type(:call) @@ -110,7 +124,7 @@ RSpec.describe Gitlab::ApplicationContext do it 'does not cause queries' do context = described_class.new(project: create(:project), namespace: create(:group, :nested), user: create(:user)) - expect { context.use { Labkit::Context.current.to_h } }.not_to exceed_query_limit(0) + expect { context.use { Gitlab::ApplicationContext.current } }.not_to exceed_query_limit(0) end end end diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index f360ee81834..3b4451fd2bb 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -84,6 +84,7 @@ Note: - discussion_id - original_discussion_id - confidential +- last_edited_at LabelLink: - id - target_type diff --git a/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb b/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb index ca473462d2e..f736a7db774 100644 --- a/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware/worker_context/server_spec.rb @@ -18,7 +18,7 @@ RSpec.describe Gitlab::SidekiqMiddleware::WorkerContext::Server do worker_context user: nil def perform(identifier, *args) - self.class.contexts.merge!(identifier => Labkit::Context.current.to_h) + self.class.contexts.merge!(identifier => Gitlab::ApplicationContext.current) end end end diff --git a/spec/lib/pager_duty/webhook_payload_parser_spec.rb b/spec/lib/pager_duty/webhook_payload_parser_spec.rb index 54c61b9121c..647f19e3d3a 100644 --- a/spec/lib/pager_duty/webhook_payload_parser_spec.rb +++ b/spec/lib/pager_duty/webhook_payload_parser_spec.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true -require 'fast_spec_helper' -require 'json_schemer' +require 'spec_helper' RSpec.describe PagerDuty::WebhookPayloadParser do describe '.call' do diff --git a/spec/mailers/emails/merge_requests_spec.rb b/spec/mailers/emails/merge_requests_spec.rb index d127867aa82..99a4730e22b 100644 --- a/spec/mailers/emails/merge_requests_spec.rb +++ b/spec/mailers/emails/merge_requests_spec.rb @@ -90,6 +90,40 @@ RSpec.describe Emails::MergeRequests do end end + describe '#merge_request_status_email' do + let(:status) { 'reopened' } + + subject { Notify.merge_request_status_email(recipient.id, merge_request.id, status, current_user.id) } + + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { merge_request } + end + + it_behaves_like 'it should show Gmail Actions View Merge request link' + it_behaves_like 'an unsubscribeable thread' + it_behaves_like 'appearance header and footer enabled' + it_behaves_like 'appearance header and footer not enabled' + + it 'is sent as the author' do + sender = subject.header[:from].addrs[0] + expect(sender.display_name).to eq(current_user.name) + expect(sender.address).to eq(gitlab_sender) + end + + it 'has the correct subject and body' do + aggregate_failures do + is_expected.to have_referable_subject(merge_request, reply: true) + is_expected.to have_body_text(status) + is_expected.to have_body_text(current_user_sanitized) + is_expected.to have_body_text(project_merge_request_path(project, merge_request)) + is_expected.to have_link(merge_request.to_reference, href: project_merge_request_url(merge_request.target_project, merge_request)) + + expect(subject.text_part).to have_content(assignee.name) + expect(subject.text_part).to have_content(reviewer.name) + end + end + end + describe "#merge_when_pipeline_succeeds_email" do let(:title) { "Merge request #{merge_request.to_reference} was scheduled to merge after pipeline succeeds by #{current_user.name}" } diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 762c57615b8..17ed3e967a1 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -464,37 +464,6 @@ RSpec.describe Notify do end end - describe 'status changed' do - let(:status) { 'reopened' } - - subject { described_class.merge_request_status_email(recipient.id, merge_request.id, status, current_user.id) } - - it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do - let(:model) { merge_request } - end - - it_behaves_like 'it should show Gmail Actions View Merge request link' - it_behaves_like 'an unsubscribeable thread' - it_behaves_like 'appearance header and footer enabled' - it_behaves_like 'appearance header and footer not enabled' - - it 'is sent as the author' do - sender = subject.header[:from].addrs[0] - expect(sender.display_name).to eq(current_user.name) - expect(sender.address).to eq(gitlab_sender) - end - - it 'has the correct subject and body' do - aggregate_failures do - is_expected.to have_referable_subject(merge_request, reply: true) - is_expected.to have_body_text(status) - is_expected.to have_body_text(current_user_sanitized) - is_expected.to have_body_text(project_merge_request_path(project, merge_request)) - is_expected.to have_link(merge_request.to_reference, href: project_merge_request_url(merge_request.target_project, merge_request)) - end - end - end - describe 'that are unmergeable' do let_it_be(:merge_request) do create(:merge_request, :conflict, diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 364b80e8601..590acfc0ac1 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -336,6 +336,25 @@ RSpec.describe Note do end end + describe "last_edited_at" do + let(:timestamp) { Time.current } + let(:note) { build(:note, last_edited_at: nil, created_at: timestamp, updated_at: timestamp + 5.hours) } + + context "with last_edited_at" do + it "returns last_edited_at" do + note.last_edited_at = timestamp + + expect(note.last_edited_at).to eq(timestamp) + end + end + + context "without last_edited_at" do + it "returns updated_at" do + expect(note.last_edited_at).to eq(timestamp + 5.hours) + end + end + end + describe "edited?" do let(:note) { build(:note, updated_by_id: nil, created_at: Time.current, updated_at: Time.current + 5.hours) } diff --git a/spec/requests/api/api_spec.rb b/spec/requests/api/api_spec.rb index e1050d0b5f7..4eae5678184 100644 --- a/spec/requests/api/api_spec.rb +++ b/spec/requests/api/api_spec.rb @@ -105,7 +105,7 @@ RSpec.describe API::API do it 'logs all application context fields' do allow_any_instance_of(Gitlab::GrapeLogging::Loggers::ContextLogger).to receive(:parameters) do - Labkit::Context.current.to_h.tap do |log_context| + Gitlab::ApplicationContext.current.tap do |log_context| expect(log_context).to match('correlation_id' => an_instance_of(String), 'meta.caller_id' => '/api/:version/projects/:id/issues', 'meta.remote_ip' => an_instance_of(String), @@ -121,7 +121,7 @@ RSpec.describe API::API do it 'skips fields that do not apply' do allow_any_instance_of(Gitlab::GrapeLogging::Loggers::ContextLogger).to receive(:parameters) do - Labkit::Context.current.to_h.tap do |log_context| + Gitlab::ApplicationContext.current.tap do |log_context| expect(log_context).to match('correlation_id' => an_instance_of(String), 'meta.caller_id' => '/api/:version/users', 'meta.remote_ip' => an_instance_of(String), diff --git a/spec/services/notes/update_service_spec.rb b/spec/services/notes/update_service_spec.rb index 902fd9958f8..000f3d26efa 100644 --- a/spec/services/notes/update_service_spec.rb +++ b/spec/services/notes/update_service_spec.rb @@ -64,6 +64,40 @@ RSpec.describe Notes::UpdateService do end.to change { counter.unique_events(event_names: event, start_date: 1.day.ago, end_date: 1.day.from_now) }.by(1) end + context 'when note text was changed' do + let!(:note) { create(:note, project: project, noteable: issue, author: user2, note: "Old note #{user3.to_reference}") } + let(:edit_note_text) { update_note({ note: 'new text' }) } + + it 'update last_edited_at' do + travel_to(1.day.from_now) do + expect { edit_note_text }.to change { note.reload.last_edited_at } + end + end + + it 'update updated_by' do + travel_to(1.day.from_now) do + expect { edit_note_text }.to change { note.reload.updated_by } + end + end + end + + context 'when note text was not changed' do + let!(:note) { create(:note, project: project, noteable: issue, author: user2, note: "Old note #{user3.to_reference}") } + let(:does_not_edit_note_text) { update_note({}) } + + it 'does not update last_edited_at' do + travel_to(1.day.from_now) do + expect { does_not_edit_note_text }.not_to change { note.reload.last_edited_at } + end + end + + it 'does not update updated_by' do + travel_to(1.day.from_now) do + expect { does_not_edit_note_text }.not_to change { note.reload.updated_by } + end + end + end + context 'when the notable is a merge request' do let(:merge_request) { create(:merge_request, source_project: project) } let(:note) { create(:note, project: project, noteable: merge_request, author: user, note: "Old note #{user2.to_reference}") } diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ddda3ede083..9a49c57e48c 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -304,10 +304,10 @@ RSpec.configure do |config| RequestStore.clear! end - config.around do |example| + config.around(:example, :context_aware) do |example| # Wrap each example in it's own context to make sure the contexts don't # leak - Labkit::Context.with_context { example.run } + Gitlab::ApplicationContext.with_raw_context { example.run } end config.around do |example| diff --git a/spec/support/shared_examples/lib/api/ci/runner_shared_examples.rb b/spec/support/shared_examples/lib/api/ci/runner_shared_examples.rb index bdb0316bf5a..c775ca182e6 100644 --- a/spec/support/shared_examples/lib/api/ci/runner_shared_examples.rb +++ b/spec/support/shared_examples/lib/api/ci/runner_shared_examples.rb @@ -1,14 +1,14 @@ # frozen_string_literal: true RSpec.shared_examples 'API::CI::Runner application context metadata' do |api_route| - it 'contains correct context metadata' do + it 'contains correct context metadata', :context_aware do # Avoids popping the context from the thread so we can # check its content after the request. allow(Labkit::Context).to receive(:pop) send_request - Labkit::Context.with_context do |context| + Gitlab::ApplicationContext.with_raw_context do |context| expected_context = { 'meta.caller_id' => api_route, 'meta.user' => job.user.username, diff --git a/spec/support/shared_examples/requests/api/logging_application_context_shared_examples.rb b/spec/support/shared_examples/requests/api/logging_application_context_shared_examples.rb index 4a71b696d57..57e28e6df57 100644 --- a/spec/support/shared_examples/requests/api/logging_application_context_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/logging_application_context_shared_examples.rb @@ -1,21 +1,13 @@ # frozen_string_literal: true RSpec.shared_examples 'storing arguments in the application context' do - around do |example| - Labkit::Context.with_context { example.run } - end - - it 'places the expected params in the application context' do + it 'places the expected params in the application context', :context_aware do # Stub the clearing of the context so we can validate it later - # The `around` block above makes sure we do clean it up later allow(Labkit::Context).to receive(:pop) subject - Labkit::Context.with_context do |context| - expect(context.to_h) - .to include(log_hash(expected_params)) - end + expect(Gitlab::ApplicationContext.current).to include(log_hash(expected_params)) end def log_hash(hash) diff --git a/spec/workers/background_migration_worker_spec.rb b/spec/workers/background_migration_worker_spec.rb index 8094efcaf04..4575c270042 100644 --- a/spec/workers/background_migration_worker_spec.rb +++ b/spec/workers/background_migration_worker_spec.rb @@ -101,7 +101,7 @@ RSpec.describe BackgroundMigrationWorker, :clean_gitlab_redis_shared_state do it 'sets the class that will be executed as the caller_id' do expect(Gitlab::BackgroundMigration).to receive(:perform) do - expect(Labkit::Context.current.to_h).to include('meta.caller_id' => 'Foo') + expect(Gitlab::ApplicationContext.current).to include('meta.caller_id' => 'Foo') end worker.perform('Foo', [10, 20]) diff --git a/spec/workers/concerns/worker_context_spec.rb b/spec/workers/concerns/worker_context_spec.rb index 3de37b99aba..ebdb752d900 100644 --- a/spec/workers/concerns/worker_context_spec.rb +++ b/spec/workers/concerns/worker_context_spec.rb @@ -103,7 +103,7 @@ RSpec.describe WorkerContext do describe '#with_context' do it 'allows modifying context when the job is running' do worker.new.with_context(user: build_stubbed(:user, username: 'jane-doe')) do - expect(Labkit::Context.current.to_h).to include('meta.user' => 'jane-doe') + expect(Gitlab::ApplicationContext.current).to include('meta.user' => 'jane-doe') end end