diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-10 00:09:19 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-10 00:09:19 +0300 |
commit | b5944525b015e4efb4cd2c1d09ec37566d7691a0 (patch) | |
tree | 23134355a45b69298483e6c08b65ef6b23b8bd26 /app | |
parent | 16cfd85bcf0046ae97d7ea84dae7eea3eafafe99 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
15 files changed, 123 insertions, 5 deletions
diff --git a/app/assets/javascripts/diffs/store/getters.js b/app/assets/javascripts/diffs/store/getters.js index 149afc01056..1fc2a684e95 100644 --- a/app/assets/javascripts/diffs/store/getters.js +++ b/app/assets/javascripts/diffs/store/getters.js @@ -4,6 +4,7 @@ import { INLINE_DIFF_VIEW_TYPE, INLINE_DIFF_LINES_KEY, } from '../constants'; +import { computeSuggestionCommitMessage } from '../utils/suggestions'; import { parallelizeDiffLines } from './utils'; export * from './getters_versions_dropdowns'; @@ -154,3 +155,18 @@ export const diffLines = (state) => (file, unifiedDiffComponents) => { state.diffViewType === INLINE_DIFF_VIEW_TYPE, ); }; + +export function suggestionCommitMessage(state) { + return (values = {}) => + computeSuggestionCommitMessage({ + message: state.defaultSuggestionCommitMessage, + values: { + branch_name: state.branchName, + project_path: state.projectPath, + project_name: state.projectName, + username: state.username, + user_full_name: state.userFullName, + ...values, + }, + }); +} diff --git a/app/assets/javascripts/diffs/utils/suggestions.js b/app/assets/javascripts/diffs/utils/suggestions.js new file mode 100644 index 00000000000..a272f7f3257 --- /dev/null +++ b/app/assets/javascripts/diffs/utils/suggestions.js @@ -0,0 +1,28 @@ +function removeEmptyProperties(dict) { + const noBlanks = Object.entries(dict).reduce((final, [key, value]) => { + const upd = { ...final }; + + // The number 0 shouldn't be falsey when we're printing variables + if (value || value === 0) { + upd[key] = value; + } + + return upd; + }, {}); + + return noBlanks; +} + +export function computeSuggestionCommitMessage({ message, values = {} } = {}) { + const noEmpties = removeEmptyProperties(values); + const matchPhrases = Object.keys(noEmpties) + .map((key) => `%{${key}}`) + .join('|'); + const replacementExpression = new RegExp(`(${matchPhrases})`, 'gm'); + + return message.replace(replacementExpression, (match) => { + const key = match.replace(/(^%{|}$)/gm, ''); + + return noEmpties[key]; + }); +} diff --git a/app/assets/javascripts/notes/components/note_body.vue b/app/assets/javascripts/notes/components/note_body.vue index 03a8a8f9376..df56e6fd8b0 100644 --- a/app/assets/javascripts/notes/components/note_body.vue +++ b/app/assets/javascripts/notes/components/note_body.vue @@ -2,6 +2,8 @@ /* eslint-disable vue/no-v-html */ import { mapActions, mapGetters, mapState } from 'vuex'; import $ from 'jquery'; +import { escape } from 'lodash'; + import '~/behaviors/markdown/render_gfm'; import Suggestions from '~/vue_shared/components/markdown/suggestions.vue'; import autosave from '../mixins/autosave'; @@ -29,6 +31,11 @@ export default { required: false, default: null, }, + file: { + type: Object, + required: false, + default: null, + }, canEdit: { type: Boolean, required: true, @@ -46,6 +53,7 @@ export default { }, computed: { ...mapGetters(['getDiscussion', 'suggestionsCount']), + ...mapGetters('diffs', ['suggestionCommitMessage']), discussion() { if (!this.note.isDraft) return {}; @@ -54,7 +62,6 @@ export default { ...mapState({ batchSuggestionsInfo: (state) => state.notes.batchSuggestionsInfo, }), - ...mapState('diffs', ['defaultSuggestionCommitMessage']), noteBody() { return this.note.note; }, @@ -64,6 +71,21 @@ export default { lineType() { return this.line ? this.line.type : null; }, + commitMessage() { + // Please see this issue comment for why these + // are hard-coded to 1: + // https://gitlab.com/gitlab-org/gitlab/-/issues/291027#note_468308022 + const suggestionsCount = 1; + const filesCount = 1; + const filePaths = this.file ? [this.file.file_path] : []; + const suggestion = this.suggestionCommitMessage({ + file_paths: filePaths.join(', '), + suggestions_count: suggestionsCount, + files_count: filesCount, + }); + + return escape(suggestion); + }, }, mounted() { this.renderGFM(); @@ -135,7 +157,7 @@ export default { :note-html="note.note_html" :line-type="lineType" :help-page-path="helpPagePath" - :default-commit-message="defaultSuggestionCommitMessage" + :default-commit-message="commitMessage" @apply="applySuggestion" @applyBatch="applySuggestionBatch" @addToBatch="addSuggestionToBatch" diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue index a1738b993d7..22941857f93 100644 --- a/app/assets/javascripts/notes/components/noteable_note.vue +++ b/app/assets/javascripts/notes/components/noteable_note.vue @@ -431,6 +431,7 @@ export default { ref="noteBody" :note="note" :line="line" + :file="diffFile" :can-edit="note.current_user.can_edit" :is-editing="isEditing" :help-page-path="helpPagePath" diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 179e6ef60fb..eb3de936fad 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -243,6 +243,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :domain_denylist_file, :raw_blob_request_limit, :issues_create_limit, + :notes_create_limit, :default_branch_name, disabled_oauth_sign_in_sources: [], import_sources: [], diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 77fd7688caf..0b1d7d24d21 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -10,6 +10,7 @@ class Projects::NotesController < Projects::ApplicationController before_action :authorize_read_note! before_action :authorize_create_note!, only: [:create] before_action :authorize_resolve_note!, only: [:resolve, :unresolve] + before_action :create_rate_limit, only: [:create] feature_category :issue_tracking @@ -90,4 +91,17 @@ class Projects::NotesController < Projects::ApplicationController def whitelist_query_limiting Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42383') end + + def create_rate_limit + key = :notes_create + + return unless rate_limiter.throttled?(key, scope: [current_user]) + + rate_limiter.log_request(request, "#{key}_request_limit".to_sym, current_user) + render plain: _('This endpoint has been requested too many times. Try again later.'), status: :too_many_requests + end + + def rate_limiter + ::Gitlab::ApplicationRateLimiter + end end diff --git a/app/graphql/mutations/notes/create/base.rb b/app/graphql/mutations/notes/create/base.rb index 2351af01813..ad90e6598c1 100644 --- a/app/graphql/mutations/notes/create/base.rb +++ b/app/graphql/mutations/notes/create/base.rb @@ -25,6 +25,7 @@ module Mutations def resolve(args) noteable = authorized_find!(id: args[:noteable_id]) + verify_rate_limit!(current_user) note = ::Notes::CreateService.new( noteable.project, @@ -54,6 +55,14 @@ module Mutations confidential: args[:confidential] } end + + def verify_rate_limit!(current_user) + rate_limiter, key = ::Gitlab::ApplicationRateLimiter, :notes_create + return unless rate_limiter.throttled?(key, scope: [current_user]) + + raise Gitlab::Graphql::Errors::ResourceNotAvailable, + 'This endpoint has been requested too many times. Try again later.' + end end end end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 185c86bd3ca..f92011958dc 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -328,6 +328,7 @@ module ApplicationSettingsHelper :email_restrictions_enabled, :email_restrictions, :issues_create_limit, + :notes_create_limit, :raw_blob_request_limit, :project_import_limit, :project_export_limit, diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 027cc372ecb..db286005ff4 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -444,6 +444,9 @@ class ApplicationSetting < ApplicationRecord presence: true, numericality: { only_integer: true, greater_than: 0 } + validates :notes_create_limit, + numericality: { only_integer: true, greater_than_or_equal_to: 0 } + attr_encrypted :asset_proxy_secret_key, mode: :per_attribute_iv, key: Settings.attr_encrypted_db_key_base_truncated, diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index 4fca087cf20..9d99b638af6 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -93,6 +93,7 @@ module ApplicationSettingImplementation import_sources: Settings.gitlab['import_sources'], invisible_captcha_enabled: false, issues_create_limit: 300, + notes_create_limit: 300, local_markdown_version: 0, login_recaptcha_protection_enabled: false, max_artifacts_size: Settings.artifacts['max_size'], diff --git a/app/services/concerns/alert_management/alert_processing.rb b/app/services/concerns/alert_management/alert_processing.rb index cc782bfe7a9..3d64758b11a 100644 --- a/app/services/concerns/alert_management/alert_processing.rb +++ b/app/services/concerns/alert_management/alert_processing.rb @@ -125,3 +125,5 @@ module AlertManagement end end end + +AlertManagement::AlertProcessing.prepend_ee_mod diff --git a/app/views/admin/application_settings/_note_limits.html.haml b/app/views/admin/application_settings/_note_limits.html.haml new file mode 100644 index 00000000000..3045c967b00 --- /dev/null +++ b/app/views/admin/application_settings/_note_limits.html.haml @@ -0,0 +1,9 @@ += form_for @application_setting, url: network_admin_application_settings_path(anchor: 'js-note-limits-settings'), html: { class: 'fieldset-form' } do |f| + = form_errors(@application_setting) + + %fieldset + .form-group + = f.label :notes_create_limit, _('Max requests per minute per user'), class: 'label-bold' + = f.number_field :notes_create_limit, class: 'form-control gl-form-input' + + = f.submit _('Save changes'), class: "gl-button btn btn-success", data: { qa_selector: 'save_changes_button' } diff --git a/app/views/admin/application_settings/network.html.haml b/app/views/admin/application_settings/network.html.haml index f977a8c93fa..72716e76013 100644 --- a/app/views/admin/application_settings/network.html.haml +++ b/app/views/admin/application_settings/network.html.haml @@ -61,6 +61,17 @@ .settings-content = render 'issue_limits' +%section.settings.as-note-limits.no-animate#js-note-limits-settings{ class: ('expanded' if expanded_by_default?) } + .settings-header + %h4 + = _('Notes Rate Limits') + %button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' } + = expanded_by_default? ? _('Collapse') : _('Expand') + %p + = _('Configure limit for notes created per minute by web and API requests.') + .settings-content + = render 'note_limits' + %section.settings.as-import-export-limits.no-animate#js-import-export-limits-settings{ class: ('expanded' if expanded_by_default?) } .settings-header %h4 diff --git a/app/views/doorkeeper/applications/_delete_form.html.haml b/app/views/doorkeeper/applications/_delete_form.html.haml index 3d6361a90ca..13ae18af2c5 100644 --- a/app/views/doorkeeper/applications/_delete_form.html.haml +++ b/app/views/doorkeeper/applications/_delete_form.html.haml @@ -2,7 +2,7 @@ = form_tag oauth_application_path(application) do %input{ :name => "_method", :type => "hidden", :value => "delete" }/ - if defined? small - = button_tag type: "submit", class: "gl-button btn btn-transparent", data: { confirm: _("Are you sure?") } do + = button_tag type: "submit", class: "gl-button btn btn-default", data: { confirm: _("Are you sure?") } do %span.sr-only = _('Destroy') = sprite_icon('remove') diff --git a/app/views/doorkeeper/applications/index.html.haml b/app/views/doorkeeper/applications/index.html.haml index 2daba4586e1..827a839234f 100644 --- a/app/views/doorkeeper/applications/index.html.haml +++ b/app/views/doorkeeper/applications/index.html.haml @@ -40,8 +40,8 @@ - application.redirect_uri.split.each do |uri| %div= uri %td= application.access_tokens.count - %td - = link_to edit_oauth_application_path(application), class: "gl-button btn btn-transparent gl-mr-2" do + %td.gl-display-flex + = link_to edit_oauth_application_path(application), class: "gl-button btn btn-default gl-mr-2" do %span.sr-only = _('Edit') = sprite_icon('pencil') |