Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-06-15 03:07:23 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-06-15 03:07:23 +0300
commitafd8f58f2d0d42d21496fe4652c1664add9b68b7 (patch)
tree38741f62cac6fafb42d30632596db951ec955850 /app
parentbeabc7d164276a8bb35c2b497a0c4dc0dc824e3c (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/deprecated_notes.js27
-rw-r--r--app/assets/javascripts/issuable/components/related_issuable_item.vue2
-rw-r--r--app/assets/javascripts/issues/show/components/header_actions.vue8
-rw-r--r--app/assets/javascripts/issues/show/components/title.vue1
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue42
-rw-r--r--app/assets/javascripts/notes/i18n.js5
-rw-r--r--app/assets/javascripts/related_issues/components/add_issuable_form.vue2
-rw-r--r--app/assets/javascripts/related_issues/components/related_issuable_input.vue2
-rw-r--r--app/assets/javascripts/related_issues/components/related_issues_block.vue1
-rw-r--r--app/assets/javascripts/related_issues/components/related_issues_list.vue4
-rw-r--r--app/assets/javascripts/sidebar/components/sidebar_editable_item.vue1
-rw-r--r--app/components/diffs/base_component.rb2
-rw-r--r--app/components/layouts/horizontal_section_component.rb2
-rw-r--r--app/components/pajamas/component.rb2
-rw-r--r--app/controllers/concerns/notes_actions.rb12
-rw-r--r--app/finders/groups/environment_scopes_finder.rb50
-rw-r--r--app/finders/groups_finder.rb2
-rw-r--r--app/graphql/resolvers/group_environment_scopes_resolver.rb23
-rw-r--r--app/graphql/types/ci/group_environment_scope_connection_type.rb10
-rw-r--r--app/graphql/types/ci/group_environment_scope_type.rb18
-rw-r--r--app/graphql/types/group_type.rb6
-rw-r--r--app/models/ci/group_variable.rb13
-rw-r--r--app/services/notes/update_service.rb15
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml2
-rw-r--r--app/views/shared/notes/_edit_form.html.haml1
25 files changed, 191 insertions, 62 deletions
diff --git a/app/assets/javascripts/deprecated_notes.js b/app/assets/javascripts/deprecated_notes.js
index e5b3b8a397c..08177cd0eac 100644
--- a/app/assets/javascripts/deprecated_notes.js
+++ b/app/assets/javascripts/deprecated_notes.js
@@ -25,7 +25,7 @@ import syntaxHighlight from '~/syntax_highlight';
import CommentTypeDropdown from '~/notes/components/comment_type_dropdown.vue';
import * as constants from '~/notes/constants';
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
-import { COMMENT_FORM } from '~/notes/i18n';
+import { COMMENT_FORM, UPDATE_COMMENT_FORM } from '~/notes/i18n';
import Autosave from './autosave';
import loadAwardsHandler from './awards_handler';
import { defaultAutocompleteConfig } from './gfm_auto_complete';
@@ -708,11 +708,16 @@ export default class Notes {
});
}
- updateNoteError() {
- createAlert({
- message: __(
- 'Your comment could not be updated! Please check your network connection and try again.',
- ),
+ updateNoteError(error, $editingNote) {
+ const serverErrorMessage = error?.response?.data?.errors;
+
+ const alertMessage = serverErrorMessage
+ ? sprintf(UPDATE_COMMENT_FORM.error, { reason: serverErrorMessage }, false)
+ : UPDATE_COMMENT_FORM.defaultError;
+
+ return this.addAlert({
+ message: alertMessage,
+ parent: $editingNote.get(0),
});
}
@@ -794,6 +799,8 @@ export default class Notes {
const $note = $target.closest('.note');
const $currentlyEditing = $('.note.is-editing:visible');
+ this.clearAlertWrapper();
+
if ($currentlyEditing.length) {
const isEditAllowed = this.checkContentToAllowEditing($currentlyEditing);
@@ -1860,14 +1867,14 @@ export default class Notes {
// Submission successful! render final note element
this.updateNote(data, $editingNote);
})
- .catch(() => {
+ .catch((error) => {
+ $editingNote.addClass('is-editing fade-in-full').removeClass('being-posted fade-in-half');
// Submission failed, revert back to original note
- $noteBodyText.html(escape(cachedNoteBodyText));
- $editingNote.removeClass('being-posted fade-in');
+ $noteBodyText.html(cachedNoteBodyText);
$editingNote.find('.gl-spinner').remove();
// Show Flash message about failure
- this.updateNoteError();
+ this.updateNoteError(error, $editingNote);
});
return $closeBtn.text($closeBtn.data('originalText'));
diff --git a/app/assets/javascripts/issuable/components/related_issuable_item.vue b/app/assets/javascripts/issuable/components/related_issuable_item.vue
index df50a30abb7..ff48bfceb29 100644
--- a/app/assets/javascripts/issuable/components/related_issuable_item.vue
+++ b/app/assets/javascripts/issuable/components/related_issuable_item.vue
@@ -248,7 +248,7 @@ export default {
size="small"
:disabled="removeDisabled"
class="js-issue-item-remove-button gl-mr-2"
- data-qa-selector="remove_related_issue_button"
+ data-testid="remove_related_issue_button"
:title="__('Remove')"
:aria-label="__('Remove')"
@click="onRemoveRequest"
diff --git a/app/assets/javascripts/issues/show/components/header_actions.vue b/app/assets/javascripts/issues/show/components/header_actions.vue
index 229bc473db7..a36b0c46927 100644
--- a/app/assets/javascripts/issues/show/components/header_actions.vue
+++ b/app/assets/javascripts/issues/show/components/header_actions.vue
@@ -340,7 +340,6 @@ export default {
class="gl-sm-display-none! w-100"
block
:text="dropdownText"
- data-qa-selector="issue_actions_dropdown"
data-testid="mobile-dropdown"
:loading="isToggleStateButtonLoading"
>
@@ -364,7 +363,7 @@ export default {
</gl-dropdown-item>
<gl-dropdown-item
v-if="showToggleIssueStateButton"
- :data-qa-selector="`mobile_${qaSelector}`"
+ :data-testid="`mobile_${qaSelector}`"
@click="toggleIssueState"
>
{{ buttonText }}
@@ -434,7 +433,7 @@ export default {
class="gl-display-none gl-sm-display-inline-flex!"
:data-qa-selector="qaSelector"
:loading="isToggleStateButtonLoading"
- data-testid="toggle-button"
+ data-testid="toggle-issue-state-button"
@click="toggleIssueState"
>
{{ buttonText }}
@@ -447,7 +446,6 @@ export default {
class="gl-display-none gl-sm-display-inline-flex!"
icon="ellipsis_v"
category="tertiary"
- data-qa-selector="issue_actions_ellipsis_dropdown"
:text="dropdownText"
:text-sr-only="true"
:title="dropdownText"
@@ -510,7 +508,7 @@ export default {
<gl-dropdown-item
v-gl-modal="$options.deleteModalId"
variant="danger"
- data-qa-selector="delete_issue_button"
+ data-testid="delete_issue_button"
@click="track('click_dropdown')"
>
{{ deleteButtonText }}
diff --git a/app/assets/javascripts/issues/show/components/title.vue b/app/assets/javascripts/issues/show/components/title.vue
index 2d2ef327018..c464f48d574 100644
--- a/app/assets/javascripts/issues/show/components/title.vue
+++ b/app/assets/javascripts/issues/show/components/title.vue
@@ -60,7 +60,6 @@ export default {
'issue-realtime-trigger-pulse': pulseAnimation,
}"
class="title gl-font-size-h-display"
- data-qa-selector="title_content"
data-testid="issue-title"
dir="auto"
></h1>
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index 5929e419247..dd135eaee3b 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -18,6 +18,7 @@ import eventHub from '../event_hub';
import noteable from '../mixins/noteable';
import resolvable from '../mixins/resolvable';
import { renderMarkdown } from '../utils';
+import { UPDATE_COMMENT_FORM } from '../i18n';
import {
getStartLineNumber,
getEndLineNumber,
@@ -113,6 +114,7 @@ export default {
isResolving: false,
commentLineStart: {},
resolveAsThread: true,
+ oldContent: this.note.note_html,
};
},
computed: {
@@ -293,7 +295,7 @@ export default {
updateSuccess() {
this.isEditing = false;
this.isRequesting = false;
- this.oldContent = null;
+ this.oldContent = this.note.note_html;
renderGFM(this.$refs.noteBody.$el);
this.$emit('updateSuccess');
},
@@ -341,7 +343,6 @@ export default {
// https://gitlab.com/gitlab-org/gitlab/-/issues/298827
if (!isEmpty(position)) data.note.note.position = JSON.stringify(position);
this.isRequesting = true;
- this.oldContent = this.note.note_html;
// eslint-disable-next-line vue/no-mutating-props
this.note.note_html = renderMarkdown(noteText);
@@ -350,8 +351,8 @@ export default {
this.updateSuccess();
callback();
})
- .catch((response) => {
- if (response.status === HTTP_STATUS_GONE) {
+ .catch((e) => {
+ if (e.status === HTTP_STATUS_GONE) {
this.removeNote(this.note);
this.updateSuccess();
callback();
@@ -360,17 +361,22 @@ export default {
this.isEditing = true;
this.setSelectedCommentPositionHover();
this.$nextTick(() => {
- this.handleUpdateError(response); // The 'response' parameter is being used in JH, don't remove it
- this.recoverNoteContent(noteText);
+ this.handleUpdateError(e); // The 'e' parameter is being used in JH, don't remove it
+ this.recoverNoteContent();
callback();
});
}
});
},
- handleUpdateError() {
- const msg = __('Something went wrong while editing your comment. Please try again.');
+ handleUpdateError(e) {
+ const serverErrorMessage = e?.response?.data?.errors;
+
+ const alertMessage = serverErrorMessage
+ ? sprintf(UPDATE_COMMENT_FORM.error, { reason: serverErrorMessage.toLowerCase() }, false)
+ : UPDATE_COMMENT_FORM.defaultError;
+
createAlert({
- message: msg,
+ message: alertMessage,
parent: this.$el,
});
},
@@ -391,22 +397,14 @@ export default {
});
if (!confirmed) return;
}
- if (this.oldContent) {
- // eslint-disable-next-line vue/no-mutating-props
- this.note.note_html = this.oldContent;
- this.oldContent = null;
- }
+ this.recoverNoteContent();
this.isEditing = false;
this.$emit('cancelForm');
}),
- recoverNoteContent(noteText) {
- // we need to do this to prevent noteForm inconsistent content warning
- // this is something we intentionally do so we need to recover the content
- // eslint-disable-next-line vue/no-mutating-props
- this.note.note = noteText;
- const { noteBody } = this.$refs;
- if (noteBody) {
- noteBody.note.note = noteText;
+ recoverNoteContent() {
+ if (this.oldContent) {
+ // eslint-disable-next-line vue/no-mutating-props
+ this.note.note_html = this.oldContent;
}
},
getLineClasses(lineNumber) {
diff --git a/app/assets/javascripts/notes/i18n.js b/app/assets/javascripts/notes/i18n.js
index 633466a5fd3..c25ca6b586d 100644
--- a/app/assets/javascripts/notes/i18n.js
+++ b/app/assets/javascripts/notes/i18n.js
@@ -55,3 +55,8 @@ export const EDITED_TEXT = {
actionWithAuthor: __('%{actionText} %{actionDetail} %{timeago} by %{author}'),
actionWithoutAuthor: __('%{actionText} %{actionDetail}'),
};
+
+export const UPDATE_COMMENT_FORM = {
+ error: __('Your comment could not be updated because %{reason}.'),
+ defaultError: __('Something went wrong while editing your comment. Please try again.'),
+};
diff --git a/app/assets/javascripts/related_issues/components/add_issuable_form.vue b/app/assets/javascripts/related_issues/components/add_issuable_form.vue
index 7ecc39a56e7..b3033ddf3b6 100644
--- a/app/assets/javascripts/related_issues/components/add_issuable_form.vue
+++ b/app/assets/javascripts/related_issues/components/add_issuable_form.vue
@@ -218,7 +218,7 @@ export default {
type="submit"
size="small"
class="gl-mr-2"
- data-qa-selector="add_issue_button"
+ data-testid="add_issue_button"
>
{{ __('Add') }}
</gl-button>
diff --git a/app/assets/javascripts/related_issues/components/related_issuable_input.vue b/app/assets/javascripts/related_issues/components/related_issuable_input.vue
index 1846b9cf8f4..f92c81a7eb2 100644
--- a/app/assets/javascripts/related_issues/components/related_issuable_input.vue
+++ b/app/assets/javascripts/related_issues/components/related_issuable_input.vue
@@ -217,7 +217,7 @@ export default {
:aria-label="inputPlaceholder"
type="text"
class="gl-w-full gl-border-none gl-outline-0"
- data-qa-selector="add_issue_field"
+ data-testid="add_issue_field"
autocomplete="off"
@input="onInput"
@focus="onFocus"
diff --git a/app/assets/javascripts/related_issues/components/related_issues_block.vue b/app/assets/javascripts/related_issues/components/related_issues_block.vue
index 24b350c7f18..f672acda062 100644
--- a/app/assets/javascripts/related_issues/components/related_issues_block.vue
+++ b/app/assets/javascripts/related_issues/components/related_issues_block.vue
@@ -220,7 +220,6 @@ export default {
<gl-button
v-if="canAdmin"
size="small"
- data-qa-selector="related_issues_plus_button"
data-testid="related-issues-plus-button"
:aria-label="addIssuableButtonText"
class="gl-ml-3"
diff --git a/app/assets/javascripts/related_issues/components/related_issues_list.vue b/app/assets/javascripts/related_issues/components/related_issues_list.vue
index 63452f3eace..8d26917f749 100644
--- a/app/assets/javascripts/related_issues/components/related_issues_list.vue
+++ b/app/assets/javascripts/related_issues/components/related_issues_list.vue
@@ -104,7 +104,7 @@ export default {
{{ heading }}
</h4>
<div class="related-issues-token-body" :class="{ 'sortable-container': canReorder }">
- <div v-if="isFetching" class="gl-mb-2" data-qa-selector="related_issues_loading_placeholder">
+ <div v-if="isFetching" class="gl-mb-2" data-testid="related_issues_loading_placeholder">
<gl-loading-icon
ref="loadingIcon"
size="sm"
@@ -146,7 +146,7 @@ export default {
:locked-message="issue.lockedMessage"
:work-item-type="issue.type"
event-namespace="relatedIssue"
- data-qa-selector="related_issuable_content"
+ data-testid="related_issuable_content"
class="gl-mx-n2"
@relatedIssueRemoveRequest="$emit('relatedIssueRemoveRequest', $event)"
/>
diff --git a/app/assets/javascripts/sidebar/components/sidebar_editable_item.vue b/app/assets/javascripts/sidebar/components/sidebar_editable_item.vue
index 1680e42e5e4..2653748861b 100644
--- a/app/assets/javascripts/sidebar/components/sidebar_editable_item.vue
+++ b/app/assets/javascripts/sidebar/components/sidebar_editable_item.vue
@@ -157,7 +157,6 @@ export default {
:data-track-action="tracking.event"
:data-track-label="tracking.label"
:data-track-property="tracking.property"
- data-qa-selector="edit_link"
@keyup.esc="toggle"
@click="toggle"
>
diff --git a/app/components/diffs/base_component.rb b/app/components/diffs/base_component.rb
index f5bc59cb314..9e1347d1e84 100644
--- a/app/components/diffs/base_component.rb
+++ b/app/components/diffs/base_component.rb
@@ -2,8 +2,6 @@
module Diffs
class BaseComponent < ViewComponent::Base
- warn_on_deprecated_slot_setter
-
# To make converting the partials to components easier,
# we delegate all missing methods to the helpers,
# where they probably are.
diff --git a/app/components/layouts/horizontal_section_component.rb b/app/components/layouts/horizontal_section_component.rb
index caeaa1782c0..48c960f17d9 100644
--- a/app/components/layouts/horizontal_section_component.rb
+++ b/app/components/layouts/horizontal_section_component.rb
@@ -2,8 +2,6 @@
module Layouts
class HorizontalSectionComponent < ViewComponent::Base
- warn_on_deprecated_slot_setter
-
# @param [Boolean] border
# @param [Hash] options
def initialize(border: true, options: {})
diff --git a/app/components/pajamas/component.rb b/app/components/pajamas/component.rb
index a7b45ffd7fd..3b1826a646c 100644
--- a/app/components/pajamas/component.rb
+++ b/app/components/pajamas/component.rb
@@ -2,8 +2,6 @@
module Pajamas
class Component < ViewComponent::Base
- warn_on_deprecated_slot_setter
-
private
# Filter a given a value against a list of allowed values
diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb
index 030b438afc7..7b2cf131fce 100644
--- a/app/controllers/concerns/notes_actions.rb
+++ b/app/controllers/concerns/notes_actions.rb
@@ -80,10 +80,16 @@ module NotesActions
return
end
- prepare_notes_for_rendering([@note])
-
respond_to do |format|
- format.json { render json: note_json(@note) }
+ format.json do
+ if @note.errors.present?
+ render json: { errors: @note.errors.full_messages.to_sentence }, status: :unprocessable_entity
+ else
+ prepare_notes_for_rendering([@note])
+ render json: note_json(@note)
+ end
+ end
+
format.html { redirect_back_or_default }
end
end
diff --git a/app/finders/groups/environment_scopes_finder.rb b/app/finders/groups/environment_scopes_finder.rb
new file mode 100644
index 00000000000..886be7881ee
--- /dev/null
+++ b/app/finders/groups/environment_scopes_finder.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+# Groups::EnvironmentsScopesFinder
+#
+# Arguments:
+# group
+# params:
+# search: string
+#
+module Groups
+ class EnvironmentScopesFinder
+ DEFAULT_ENVIRONMENT_SCOPES_LIMIT = 100
+
+ def initialize(group:, params: {})
+ @group = group
+ @params = params
+ end
+
+ EnvironmentScope = Struct.new(:name)
+
+ def execute
+ variables = group.variables
+ variables = by_name(variables)
+ variables = by_search(variables)
+ variables = variables.limit(DEFAULT_ENVIRONMENT_SCOPES_LIMIT)
+ environment_scope_names = variables.environment_scope_names
+ environment_scope_names.map { |environment_scope| EnvironmentScope.new(environment_scope) }
+ end
+
+ private
+
+ attr_reader :group, :params
+
+ def by_name(group_variables)
+ if params[:name].present?
+ group_variables.by_environment_scope(params[:name])
+ else
+ group_variables
+ end
+ end
+
+ def by_search(group_variables)
+ if params[:search].present?
+ group_variables.for_environment_scope_like(params[:search])
+ else
+ group_variables
+ end
+ end
+ end
+end
diff --git a/app/finders/groups_finder.rb b/app/finders/groups_finder.rb
index 879c94ae4d4..63f7616884f 100644
--- a/app/finders/groups_finder.rb
+++ b/app/finders/groups_finder.rb
@@ -103,13 +103,11 @@ class GroupsFinder < UnionFinder
end
# rubocop: enable CodeReuse/ActiveRecord
- # rubocop: disable CodeReuse/ActiveRecord
def by_search(groups)
return groups unless params[:search].present?
groups.search(params[:search], include_parents: params[:parent].blank?)
end
- # rubocop: enable CodeReuse/ActiveRecord
def owned_groups
current_user&.owned_groups || Group.none
diff --git a/app/graphql/resolvers/group_environment_scopes_resolver.rb b/app/graphql/resolvers/group_environment_scopes_resolver.rb
new file mode 100644
index 00000000000..61ccb2eefbb
--- /dev/null
+++ b/app/graphql/resolvers/group_environment_scopes_resolver.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class GroupEnvironmentScopesResolver < BaseResolver
+ type Types::Ci::GroupEnvironmentScopeType.connection_type, null: true
+
+ alias_method :group, :object
+
+ argument :name, GraphQL::Types::String,
+ required: false,
+ description: 'Name of the environment scope.'
+
+ argument :search, GraphQL::Types::String,
+ required: false,
+ description: 'Search query for environment scope name.'
+
+ def resolve(**args)
+ return unless group.present?
+
+ ::Groups::EnvironmentScopesFinder.new(group: group, params: args).execute
+ end
+ end
+end
diff --git a/app/graphql/types/ci/group_environment_scope_connection_type.rb b/app/graphql/types/ci/group_environment_scope_connection_type.rb
new file mode 100644
index 00000000000..ddbc00d3870
--- /dev/null
+++ b/app/graphql/types/ci/group_environment_scope_connection_type.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Types
+ module Ci
+ # rubocop: disable Graphql/AuthorizeTypes
+ class GroupEnvironmentScopeConnectionType < GraphQL::Types::Relay::BaseConnection
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+ end
+end
diff --git a/app/graphql/types/ci/group_environment_scope_type.rb b/app/graphql/types/ci/group_environment_scope_type.rb
new file mode 100644
index 00000000000..3a3a5a3f59f
--- /dev/null
+++ b/app/graphql/types/ci/group_environment_scope_type.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Types
+ module Ci
+ # rubocop: disable Graphql/AuthorizeTypes
+ class GroupEnvironmentScopeType < BaseObject
+ graphql_name 'CiGroupEnvironmentScope'
+ description 'Ci/CD environment scope for a group.'
+
+ connection_type_class(Types::Ci::GroupEnvironmentScopeConnectionType)
+
+ field :name, GraphQL::Types::String,
+ null: true,
+ description: 'Scope name defininig the enviromnments that can use the variable.'
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+ end
+end
diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb
index 24326c131ce..5fd6ee948d3 100644
--- a/app/graphql/types/group_type.rb
+++ b/app/graphql/types/group_type.rb
@@ -83,6 +83,12 @@ module Types
description: 'Merge requests for projects in this group.',
resolver: Resolvers::GroupMergeRequestsResolver
+ field :environment_scopes,
+ Types::Ci::GroupEnvironmentScopeType.connection_type,
+ description: 'Environment scopes of the group.',
+ null: true,
+ resolver: Resolvers::GroupEnvironmentScopesResolver
+
field :milestones,
description: 'Milestones of the group.',
extras: [:lookahead],
diff --git a/app/models/ci/group_variable.rb b/app/models/ci/group_variable.rb
index f04f0d27e51..5522a01758f 100644
--- a/app/models/ci/group_variable.rb
+++ b/app/models/ci/group_variable.rb
@@ -23,6 +23,19 @@ module Ci
scope :by_environment_scope, -> (environment_scope) { where(environment_scope: environment_scope) }
scope :for_groups, ->(group_ids) { where(group_id: group_ids) }
+ scope :for_environment_scope_like, -> (query) do
+ top_level = 'LOWER(ci_group_variables.environment_scope) LIKE LOWER(?) || \'%\''
+ search_like = "%#{sanitize_sql_like(query)}%"
+
+ where(top_level, search_like)
+ end
+
+ scope :environment_scope_names, -> do
+ group(:environment_scope)
+ .order(:environment_scope)
+ .pluck(:environment_scope)
+ end
+
self.limit_name = 'group_ci_variables'
self.limit_scope = :group
diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb
index ff9f39ce0be..52940281018 100644
--- a/app/services/notes/update_service.rb
+++ b/app/services/notes/update_service.rb
@@ -9,6 +9,8 @@ module Notes
note.assign_attributes(params)
+ return note unless note.valid?
+
track_note_edit_usage_for_issues(note) if note.for_issue?
track_note_edit_usage_for_merge_requests(note) if note.for_merge_request?
@@ -23,11 +25,7 @@ module Notes
note.note = content
end
- if note.note_changed?
- note.assign_attributes(last_edited_at: Time.current, updated_by: current_user)
- note.check_for_spam(action: :update, user: current_user) unless only_commands
- end
-
+ update_note(note, only_commands)
note.save
unless only_commands || note.for_personal_snippet?
@@ -56,6 +54,13 @@ module Notes
private
+ def update_note(note, only_commands)
+ return unless note.note_changed?
+
+ note.assign_attributes(last_edited_at: Time.current, updated_by: current_user)
+ note.check_for_spam(action: :update, user: current_user) unless only_commands
+ end
+
def delete_note(note, message)
# We must add the error after we call #save because errors are reset
# when #save is called
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 09162e6a349..ee1ca364b07 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -46,7 +46,7 @@
.js-sidebar-milestone-widget-root{ data: { can_edit: can_edit_issuable.to_s, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid] } }
- if in_group_context_with_iterations
- .block.gl-collapse-empty{ data: { qa_selector: 'iteration_container', testid: 'iteration_container' } }<
+ .block.gl-collapse-empty{ data: { testid: 'iteration_container' } }<
= render_if_exists 'shared/issuable/iteration_select', can_edit: can_edit_issuable.to_s, group_path: @project.group.full_path, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid], issuable_type: issuable_type
- if issuable_sidebar[:show_crm_contacts]
diff --git a/app/views/shared/notes/_edit_form.html.haml b/app/views/shared/notes/_edit_form.html.haml
index 72081856da6..40a71aa53dc 100644
--- a/app/views/shared/notes/_edit_form.html.haml
+++ b/app/views/shared/notes/_edit_form.html.haml
@@ -2,6 +2,7 @@
= form_tag '#', method: :put, class: 'edit-note common-note-form js-quick-submit' do
= hidden_field_tag :target_id, '', class: 'js-form-target-id'
= hidden_field_tag :target_type, '', class: 'js-form-target-type'
+ .flash-container
= render layout: 'shared/md_preview', locals: { url: preview_markdown_path(project), referenced_users: true } do
= render 'shared/zen', attr: 'note[note]', classes: 'note-textarea js-note-text js-task-list-field', qa_selector: 'edit_note_field', placeholder: _("Write a comment or drag your files hereā€¦")
= render 'shared/notes/hints'