Commit 23e8ae01 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents c2060944 8896ceeb
...@@ -499,10 +499,10 @@ const Api = { ...@@ -499,10 +499,10 @@ const Api = {
return axios.put(url, params); return axios.put(url, params);
}, },
applySuggestionBatch(ids) { applySuggestionBatch(ids, message) {
const url = Api.buildUrl(Api.applySuggestionBatchPath); const url = Api.buildUrl(Api.applySuggestionBatchPath);
return axios.put(url, { ids }); return axios.put(url, { ids, commit_message: message });
}, },
commitPipelines(projectId, sha) { commitPipelines(projectId, sha) {
......
...@@ -51,7 +51,7 @@ export default { ...@@ -51,7 +51,7 @@ export default {
}, },
}, },
computed: { computed: {
...mapGetters(['getDiscussion', 'suggestionsCount']), ...mapGetters(['getDiscussion', 'suggestionsCount', 'getSuggestionsFilePaths']),
...mapGetters('diffs', ['suggestionCommitMessage']), ...mapGetters('diffs', ['suggestionCommitMessage']),
discussion() { discussion() {
if (!this.note.isDraft) return {}; if (!this.note.isDraft) return {};
...@@ -74,9 +74,10 @@ export default { ...@@ -74,9 +74,10 @@ export default {
// Please see this issue comment for why these // Please see this issue comment for why these
// are hard-coded to 1: // are hard-coded to 1:
// https://gitlab.com/gitlab-org/gitlab/-/issues/291027#note_468308022 // https://gitlab.com/gitlab-org/gitlab/-/issues/291027#note_468308022
const suggestionsCount = 1; const suggestionsCount = this.batchSuggestionsInfo.length || 1;
const filesCount = 1; const batchFilePaths = this.getSuggestionsFilePaths();
const filePaths = this.file ? [this.file.file_path] : []; const filePaths = batchFilePaths.length ? batchFilePaths : [this.file.file_path];
const filesCount = filePaths.length;
const suggestion = this.suggestionCommitMessage({ const suggestion = this.suggestionCommitMessage({
file_paths: filePaths.join(', '), file_paths: filePaths.join(', '),
suggestions_count: suggestionsCount, suggestions_count: suggestionsCount,
...@@ -131,8 +132,8 @@ export default { ...@@ -131,8 +132,8 @@ export default {
message, message,
}).then(callback); }).then(callback);
}, },
applySuggestionBatch({ flashContainer }) { applySuggestionBatch({ message, flashContainer }) {
return this.submitSuggestionBatch({ flashContainer }); return this.submitSuggestionBatch({ message, flashContainer });
}, },
addSuggestionToBatch(suggestionId) { addSuggestionToBatch(suggestionId) {
const { discussion_id: discussionId, id: noteId } = this.note; const { discussion_id: discussionId, id: noteId } = this.note;
......
...@@ -631,7 +631,7 @@ export const submitSuggestion = ( ...@@ -631,7 +631,7 @@ export const submitSuggestion = (
}); });
}; };
export const submitSuggestionBatch = ({ commit, dispatch, state }, { flashContainer }) => { export const submitSuggestionBatch = ({ commit, dispatch, state }, { message, flashContainer }) => {
const suggestionIds = state.batchSuggestionsInfo.map(({ suggestionId }) => suggestionId); const suggestionIds = state.batchSuggestionsInfo.map(({ suggestionId }) => suggestionId);
const resolveAllDiscussions = () => const resolveAllDiscussions = () =>
...@@ -644,7 +644,7 @@ export const submitSuggestionBatch = ({ commit, dispatch, state }, { flashContai ...@@ -644,7 +644,7 @@ export const submitSuggestionBatch = ({ commit, dispatch, state }, { flashContai
commit(types.SET_RESOLVING_DISCUSSION, true); commit(types.SET_RESOLVING_DISCUSSION, true);
dispatch('stopPolling'); dispatch('stopPolling');
return Api.applySuggestionBatch(suggestionIds) return Api.applySuggestionBatch(suggestionIds, message)
.then(() => Promise.all(resolveAllDiscussions())) .then(() => Promise.all(resolveAllDiscussions()))
.then(() => commit(types.CLEAR_SUGGESTION_BATCH)) .then(() => commit(types.CLEAR_SUGGESTION_BATCH))
.catch((err) => { .catch((err) => {
......
...@@ -283,3 +283,14 @@ export const suggestionsCount = (state, getters) => ...@@ -283,3 +283,14 @@ export const suggestionsCount = (state, getters) =>
export const hasDrafts = (state, getters, rootState, rootGetters) => export const hasDrafts = (state, getters, rootState, rootGetters) =>
Boolean(rootGetters['batchComments/hasDrafts']); Boolean(rootGetters['batchComments/hasDrafts']);
export const getSuggestionsFilePaths = (state) => () =>
state.batchSuggestionsInfo.reduce((acc, suggestion) => {
const discussion = state.discussions.find((d) => d.id === suggestion.discussionId);
if (acc.indexOf(discussion?.diff_file?.file_path) === -1) {
acc.push(discussion.diff_file.file_path);
}
return acc;
}, []);
...@@ -220,6 +220,7 @@ export default { ...@@ -220,6 +220,7 @@ export default {
class="gl-h-200! gl-mb-4" class="gl-h-200! gl-mb-4"
single-file-selection single-file-selection
:valid-file-mimetypes="$options.validFileMimetypes" :valid-file-mimetypes="$options.validFileMimetypes"
:is-file-valid="() => true"
@change="setFile" @change="setFile"
> >
<div <div
......
<script> <script>
import { GlDropdown, GlDropdownForm, GlFormTextarea, GlButton } from '@gitlab/ui'; import { GlDropdown, GlDropdownForm, GlFormTextarea, GlButton } from '@gitlab/ui';
import { __, n__ } from '~/locale';
export default { export default {
components: { GlDropdown, GlDropdownForm, GlFormTextarea, GlButton }, components: { GlDropdown, GlDropdownForm, GlFormTextarea, GlButton },
...@@ -13,12 +14,26 @@ export default { ...@@ -13,12 +14,26 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
batchSuggestionsCount: {
type: Number,
required: false,
default: 0,
},
}, },
data() { data() {
return { return {
message: null, message: null,
}; };
}, },
computed: {
dropdownText() {
if (this.batchSuggestionsCount <= 1) {
return __('Apply suggestion');
}
return n__('Apply %d suggestion', 'Apply %d suggestions', this.batchSuggestionsCount);
},
},
methods: { methods: {
onApply() { onApply() {
this.$emit('apply', this.message); this.$emit('apply', this.message);
...@@ -29,10 +44,11 @@ export default { ...@@ -29,10 +44,11 @@ export default {
<template> <template>
<gl-dropdown <gl-dropdown
:text="__('Apply suggestion')" :text="dropdownText"
:disabled="disabled" :disabled="disabled"
boundary="window" boundary="window"
right right
lazy
menu-class="gl-w-full!" menu-class="gl-w-full!"
data-qa-selector="apply_suggestion_dropdown" data-qa-selector="apply_suggestion_dropdown"
@shown="$refs.commitMessage.$el.focus()" @shown="$refs.commitMessage.$el.focus()"
......
...@@ -54,8 +54,8 @@ export default { ...@@ -54,8 +54,8 @@ export default {
applySuggestion(callback, message) { applySuggestion(callback, message) {
this.$emit('apply', { suggestionId: this.suggestion.id, callback, message }); this.$emit('apply', { suggestionId: this.suggestion.id, callback, message });
}, },
applySuggestionBatch() { applySuggestionBatch(message) {
this.$emit('applyBatch'); this.$emit('applyBatch', message);
}, },
addSuggestionToBatch() { addSuggestionToBatch() {
this.$emit('addToBatch', this.suggestion.id); this.$emit('addToBatch', this.suggestion.id);
......
...@@ -58,12 +58,19 @@ export default { ...@@ -58,12 +58,19 @@ export default {
isApplyingSingle: false, isApplyingSingle: false,
}; };
}, },
computed: { computed: {
isApplying() { isApplying() {
return this.isApplyingSingle || this.isApplyingBatch; return this.isApplyingSingle || this.isApplyingBatch;
}, },
tooltipMessage() { tooltipMessage() {
return this.canApply ? __('This also resolves this thread') : this.inapplicableReason; if (!this.canApply) {
return this.inapplicableReason;
}
return this.batchSuggestionsCount > 1
? __('This also resolves all related threads')
: __('This also resolves this thread');
}, },
isDisableButton() { isDisableButton() {
return this.isApplying || !this.canApply; return this.isApplying || !this.canApply;
...@@ -72,13 +79,30 @@ export default { ...@@ -72,13 +79,30 @@ export default {
if (this.isApplyingSingle || this.batchSuggestionsCount < 2) { if (this.isApplyingSingle || this.batchSuggestionsCount < 2) {
return __('Applying suggestion...'); return __('Applying suggestion...');
} }
return __('Applying suggestions...'); return __('Applying suggestions...');
}, },
isLoggedIn() { isLoggedIn() {
return isLoggedIn(); return isLoggedIn();
}, },
showApplySuggestion() {
if (!this.isLoggedIn) return false;
if (this.batchSuggestionsCount >= 1 && !this.isBatched) {
return false;
}
return true;
},
}, },
methods: { methods: {
apply(message) {
if (this.batchSuggestionsCount > 1) {
this.applySuggestionBatch(message);
} else {
this.applySuggestion(message);
}
},
applySuggestion(message) { applySuggestion(message) {
if (!this.canApply) return; if (!this.canApply) return;
this.isApplyingSingle = true; this.isApplyingSingle = true;
...@@ -88,9 +112,9 @@ export default { ...@@ -88,9 +112,9 @@ export default {
applySuggestionCallback() { applySuggestionCallback() {
this.isApplyingSingle = false; this.isApplyingSingle = false;
}, },
applySuggestionBatch() { applySuggestionBatch(message) {
if (!this.canApply) return; if (!this.canApply) return;
this.$emit('applyBatch'); this.$emit('applyBatch', message);
}, },
addSuggestionToBatch() { addSuggestionToBatch() {
this.$emit('addToBatch'); this.$emit('addToBatch');
...@@ -115,7 +139,8 @@ export default { ...@@ -115,7 +139,8 @@ export default {
<gl-loading-icon size="sm" class="d-flex-center mr-2" /> <gl-loading-icon size="sm" class="d-flex-center mr-2" />
<span>{{ applyingSuggestionsMessage }}</span> <span>{{ applyingSuggestionsMessage }}</span>
</div> </div>
<div v-else-if="canApply && isBatched" class="d-flex align-items-center"> <div v-else-if="canApply" class="d-flex align-items-center">
<div v-if="isBatched">
<gl-button <gl-button
class="btn-inverted js-remove-from-batch-btn btn-grouped" class="btn-inverted js-remove-from-batch-btn btn-grouped"
:disabled="isApplying" :disabled="isApplying"
...@@ -123,23 +148,10 @@ export default { ...@@ -123,23 +148,10 @@ export default {
> >
{{ __('Remove from batch') }} {{ __('Remove from batch') }}
</gl-button> </gl-button>
<gl-button
v-gl-tooltip.viewport="__('This also resolves all related threads')"
class="btn-inverted js-apply-batch-btn btn-grouped"
data-qa-selector="apply_suggestions_batch_button"
:disabled="isApplying"
variant="success"
@click="applySuggestionBatch"
>
{{ __('Apply suggestions') }}
<span class="badge badge-pill badge-pill-success">
{{ batchSuggestionsCount }}
</span>
</gl-button>
</div> </div>
<div v-else class="d-flex align-items-center"> <div v-else>
<gl-button <gl-button
v-if="suggestionsCount > 1 && !isDisableButton" v-if="!isDisableButton && suggestionsCount > 1"
class="btn-inverted js-add-to-batch-btn btn-grouped" class="btn-inverted js-add-to-batch-btn btn-grouped"
data-qa-selector="add_suggestion_batch_button" data-qa-selector="add_suggestion_batch_button"
:disabled="isDisableButton" :disabled="isDisableButton"
...@@ -147,13 +159,15 @@ export default { ...@@ -147,13 +159,15 @@ export default {
> >
{{ __('Add suggestion to batch') }} {{ __('Add suggestion to batch') }}
</gl-button> </gl-button>
</div>
<apply-suggestion <apply-suggestion
v-if="isLoggedIn" v-if="showApplySuggestion"
v-gl-tooltip.viewport="tooltipMessage" v-gl-tooltip.viewport="tooltipMessage"
:disabled="isDisableButton" :disabled="isDisableButton"
:default-commit-message="defaultCommitMessage" :default-commit-message="defaultCommitMessage"
:batch-suggestions-count="batchSuggestionsCount"
class="gl-ml-3" class="gl-ml-3"
@apply="applySuggestion" @apply="apply"
/> />
</div> </div>
</div> </div>
......
...@@ -68,6 +68,10 @@ export default { ...@@ -68,6 +68,10 @@ export default {
if (this.suggestionsWatch) { if (this.suggestionsWatch) {
this.suggestionsWatch(); this.suggestionsWatch();
} }
if (this.defaultCommitMessageWatch) {
this.defaultCommitMessageWatch();
}
}, },
methods: { methods: {
renderSuggestions() { renderSuggestions() {
...@@ -123,12 +127,16 @@ export default { ...@@ -123,12 +127,16 @@ export default {
suggestionDiff.suggestionsCount = this.suggestionsCount; suggestionDiff.suggestionsCount = this.suggestionsCount;
}); });
this.defaultCommitMessageWatch = this.$watch('defaultCommitMessage', () => {
suggestionDiff.defaultCommitMessage = this.defaultCommitMessage;
});
suggestionDiff.$on('apply', ({ suggestionId, callback, message }) => { suggestionDiff.$on('apply', ({ suggestionId, callback, message }) => {
this.$emit('apply', { suggestionId, callback, flashContainer: this.$el, message }); this.$emit('apply', { suggestionId, callback, flashContainer: this.$el, message });
}); });
suggestionDiff.$on('applyBatch', () => { suggestionDiff.$on('applyBatch', (message) => {
this.$emit('applyBatch', { flashContainer: this.$el }); this.$emit('applyBatch', { message, flashContainer: this.$el });
}); });
suggestionDiff.$on('addToBatch', (suggestionId) => { suggestionDiff.$on('addToBatch', (suggestionId) => {
......
...@@ -45,7 +45,7 @@ export default { ...@@ -45,7 +45,7 @@ export default {
data() { data() {
return { return {
dragCounter: 0, dragCounter: 0,
isDragDataValid: false, isDragDataValid: true,
}; };
}, },
computed: { computed: {
......
...@@ -153,6 +153,10 @@ ...@@ -153,6 +153,10 @@
vertical-align: middle; vertical-align: middle;
margin-bottom: 3px; margin-bottom: 3px;
} }
.dropdown-chevron {
margin-bottom: 0;
}
} }
@include media-breakpoint-down(xs) { @include media-breakpoint-down(xs) {
......
...@@ -61,8 +61,10 @@ module Analytics ...@@ -61,8 +61,10 @@ module Analytics
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
def runner_configured def runner_configured
::Gitlab::Database.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/337541') do
Ci::Runner.active.belonging_to_group_or_project(snapshot_groups, snapshot_project_ids).exists? Ci::Runner.active.belonging_to_group_or_project(snapshot_groups, snapshot_project_ids).exists?
end end
end
def pipeline_succeeded def pipeline_succeeded
Ci::Pipeline.success.for_project(snapshot_project_ids).updated_before(range_end).updated_after(range_start).exists? Ci::Pipeline.success.for_project(snapshot_project_ids).updated_before(range_end).updated_after(range_start).exists?
...@@ -119,10 +121,16 @@ module Analytics ...@@ -119,10 +121,16 @@ module Analytics
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def projects_count_with_artifact(artifacts_scope) def projects_count_with_artifact(artifacts_scope)
subquery = artifacts_scope.created_in_time_range(from: range_start, to: range_end) subquery = artifacts_scope.created_in_time_range(from: range_start, to: range_end)
.where(Ci::JobArtifact.arel_table[:project_id].eq(Project.arel_table[:id])).arel.exists .where(Ci::JobArtifact.arel_table[:project_id].eq(Arel.sql('project_ids.id'))).arel.exists
snapshot_project_ids.each_slice(1000).sum do |project_ids| snapshot_project_ids.each_slice(1000).sum do |project_ids|
Project.where(id: project_ids).where(subquery).count ids = project_ids.map { |id| [id] }
# To avoid cross-database join, we swap out the FROM part with just the project_ids we need
Project
.select(:id)
.from("(#{Arel::Nodes::ValuesList.new(ids).to_sql}) project_ids (id)")
.where(subquery)
.count
end end
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
......
...@@ -17,11 +17,17 @@ module Gitlab ...@@ -17,11 +17,17 @@ module Gitlab
params '#issue' params '#issue'
types Issue types Issue
condition do condition do
quick_action_target.persisted? &&
current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target) current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target)
end end
command :relate do |related_param| command :relate do |related_reference|
IssueLinks::CreateService.new(quick_action_target, current_user, { issuable_references: [related_param] }).execute service = IssueLinks::CreateService.new(quick_action_target, current_user, { issuable_references: [related_reference] })
create_issue_link = proc { service.execute }
if quick_action_target.persisted?
create_issue_link.call
else
quick_action_target.run_after_commit(&create_issue_link)
end
end end
end end
end end
......
...@@ -4110,6 +4110,11 @@ msgstr "" ...@@ -4110,6 +4110,11 @@ msgstr ""
msgid "Apply" msgid "Apply"
msgstr "" msgstr ""
msgid "Apply %d suggestion"
msgid_plural "Apply %d suggestions"
msgstr[0] ""
msgstr[1] ""
msgid "Apply a label" msgid "Apply a label"
msgstr "" msgstr ""
...@@ -4119,9 +4124,6 @@ msgstr "" ...@@ -4119,9 +4124,6 @@ msgstr ""
msgid "Apply suggestion" msgid "Apply suggestion"
msgstr "" msgstr ""
msgid "Apply suggestions"
msgstr ""
msgid "Apply template" msgid "Apply template"
msgstr "" msgstr ""
......
...@@ -93,7 +93,6 @@ module QA ...@@ -93,7 +93,6 @@ module QA
end end
view 'app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue' do view 'app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue' do
element :apply_suggestions_batch_button
element :add_suggestion_batch_button element :add_suggestion_batch_button
end end
...@@ -353,10 +352,6 @@ module QA ...@@ -353,10 +352,6 @@ module QA
all_elements(:add_suggestion_batch_button, minimum: 1).first.click all_elements(:add_suggestion_batch_button, minimum: 1).first.click
end end
def apply_suggestions_batch
all_elements(:apply_suggestions_batch_button, minimum: 1).first.click
end
def cherry_pick! def cherry_pick!
click_element(:cherry_pick_button, Page::Component::CommitModal) click_element(:cherry_pick_button, Page::Component::CommitModal)
click_element(:submit_commit_button) click_element(:submit_commit_button)
......
...@@ -50,7 +50,7 @@ module QA ...@@ -50,7 +50,7 @@ module QA
Page::MergeRequest::Show.perform do |merge_request| Page::MergeRequest::Show.perform do |merge_request|
merge_request.click_diffs_tab merge_request.click_diffs_tab
4.times { merge_request.add_suggestion_to_batch } 4.times { merge_request.add_suggestion_to_batch }
merge_request.apply_suggestions_batch merge_request.apply_suggestion_with_message("Custom commit message")
expect(merge_request).to have_css('.badge-success', text: "Applied", count: 4) expect(merge_request).to have_css('.badge-success', text: "Applied", count: 4)
end end
......
...@@ -159,7 +159,12 @@ RSpec.describe 'User comments on a diff', :js do ...@@ -159,7 +159,12 @@ RSpec.describe 'User comments on a diff', :js do
wait_for_requests wait_for_requests
expect(page).to have_content('Remove from batch') expect(page).to have_content('Remove from batch')
expect(page).to have_content("Apply suggestions #{index + 1}")
if index < 1
expect(page).to have_content("Apply suggestion")
else
expect(page).to have_content("Apply #{index + 1} suggestions")
end
end end
end end
...@@ -167,13 +172,12 @@ RSpec.describe 'User comments on a diff', :js do ...@@ -167,13 +172,12 @@ RSpec.describe 'User comments on a diff', :js do
click_button('Remove from batch') click_button('Remove from batch')
wait_for_requests wait_for_requests
expect(page).to have_content('Apply suggestion')
expect(page).to have_content('Add suggestion to batch') expect(page).to have_content('Add suggestion to batch')
end end
page.within("[id='#{files[1][:hash]}']") do page.within("[id='#{files[1][:hash]}']") do
expect(page).to have_content('Remove from batch') expect(page).to have_content('Remove from batch')
expect(page).to have_content('Apply suggestions 1') expect(page).to have_content('Apply suggestion')
end end
end end
......
...@@ -19,13 +19,15 @@ RSpec.describe 'Projects > Files > User uploads files' do ...@@ -19,13 +19,15 @@ RSpec.describe 'Projects > Files > User uploads files' do
wait_for_requests wait_for_requests
end end
include_examples 'it uploads and commits a new text file' [true, false].each do |value|
include_examples 'it uploads and commits a new text file', drop: value
include_examples 'it uploads and commits a new image file' include_examples 'it uploads and commits a new image file', drop: value
include_examples 'it uploads and commits a new pdf file' include_examples 'it uploads and commits a new pdf file', drop: value
include_examples 'it uploads a file to a sub-directory' include_examples 'it uploads a file to a sub-directory', drop: value
end
end end
context 'when a user does not have write access' do context 'when a user does not have write access' do
...@@ -35,6 +37,8 @@ RSpec.describe 'Projects > Files > User uploads files' do ...@@ -35,6 +37,8 @@ RSpec.describe 'Projects > Files > User uploads files' do
visit(project_tree_path(project2)) visit(project_tree_path(project2))
end end
include_examples 'it uploads and commits a new file to a forked project' [true, false].each do |value|
include_examples 'it uploads and commits a new file to a forked project', drop: value
end
end end
end end
...@@ -21,13 +21,15 @@ RSpec.describe 'Projects > Show > User uploads files' do ...@@ -21,13 +21,15 @@ RSpec.describe 'Projects > Show > User uploads files' do
wait_for_requests wait_for_requests
end end
include_examples 'it uploads and commits a new text file' [true, false].each do |value|
include_examples 'it uploads and commits a new text file', drop: value
include_examples 'it uploads and commits a new image file' include_examples 'it uploads and commits a new image file', drop: value
include_examples 'it uploads and commits a new pdf file' include_examples 'it uploads and commits a new pdf file', drop: value
include_examples 'it uploads a file to a sub-directory' include_examples 'it uploads a file to a sub-directory', drop: value
end
end end
context 'when a user does not have write access' do context 'when a user does not have write access' do
...@@ -37,7 +39,9 @@ RSpec.describe 'Projects > Show > User uploads files' do ...@@ -37,7 +39,9 @@ RSpec.describe 'Projects > Show > User uploads files' do
visit(project_path(project2)) visit(project_path(project2))
end end
include_examples 'it uploads and commits a new file to a forked project' [true, false].each do |value|
include_examples 'it uploads and commits a new file to a forked project', drop: value
end
end end
context 'when in the empty_repo_upload experiment' do context 'when in the empty_repo_upload experiment' do
...@@ -50,13 +54,17 @@ RSpec.describe 'Projects > Show > User uploads files' do ...@@ -50,13 +54,17 @@ RSpec.describe 'Projects > Show > User uploads files' do
context 'with an empty repo' do context 'with an empty repo' do
let(:project) { create(:project, :empty_repo, creator: user) } let(:project) { create(:project, :empty_repo, creator: user) }
include_examples 'uploads and commits a new text file via "upload file" button' [true, false].each do |value|
include_examples 'uploads and commits a new text file via "upload file" button', drop: value
end
end end
context 'with a nonempty repo' do context 'with a nonempty repo' do
let(:project) { create(:project, :repository, creator: user) } let(:project) { create(:project, :repository, creator: user) }
include_examples 'uploads and commits a new text file via "upload file" button' [true, false].each do |value|
include_examples 'uploads and commits a new text file via "upload file" button', drop: value
end
end end
end end
end end
import DropdownUtils from '~/filtered_search/dropdown_utils';
// TODO: Moving this line up throws an error about `FilteredSearchDropdown`
// being undefined in test. See gitlab-org/gitlab#321476 for more info.
import DropdownUser from '~/filtered_search/dropdown_user'; import DropdownUser from '~/filtered_search/dropdown_user';
import DropdownUtils from '~/filtered_search/dropdown_utils';
import FilteredSearchTokenizer from '~/filtered_search/filtered_search_tokenizer'; import FilteredSearchTokenizer from '~/filtered_search/filtered_search_tokenizer';
import IssuableFilteredTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys'; import IssuableFilteredTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
......
...@@ -60,7 +60,7 @@ describe('Suggestion Diff component', () => { ...@@ -60,7 +60,7 @@ describe('Suggestion Diff component', () => {
expect(findHelpButton().exists()).toBe(true); expect(findHelpButton().exists()).toBe(true);
}); });
it('renders apply suggestion and add to batch buttons', () => { it('renders add to batch button when more than 1 suggestion', () => {
createComponent({ createComponent({
suggestionsCount: 2, suggestionsCount: 2,
}); });
...@@ -68,8 +68,7 @@ describe('Suggestion Diff component', () => { ...@@ -68,8 +68,7 @@ describe('Suggestion Diff component', () => {
const applyBtn = findApplyButton(); const applyBtn = findApplyButton();
const addToBatchBtn = findAddToBatchButton(); const addToBatchBtn = findAddToBatchButton();
expect(applyBtn.exists()).toBe(true); expect(applyBtn.exists()).toBe(false);
expect(applyBtn.html().includes('Apply suggestion')).toBe(true);
expect(addToBatchBtn.exists()).toBe(true); expect(addToBatchBtn.exists()).toBe(true);
expect(addToBatchBtn.html().includes('Add suggestion to batch')).toBe(true); expect(addToBatchBtn.html().includes('Add suggestion to batch')).toBe(true);
...@@ -85,7 +84,7 @@ describe('Suggestion Diff component', () => { ...@@ -85,7 +84,7 @@ describe('Suggestion Diff component', () => {
describe('when apply suggestion is clicked', () => { describe('when apply suggestion is clicked', () => {
beforeEach(() => { beforeEach(() => {
createComponent(); createComponent({ batchSuggestionsCount: 0 });
findApplyButton().vm.$emit('apply'); findApplyButton().vm.$emit('apply');
}); });
...@@ -140,11 +139,11 @@ describe('Suggestion Diff component', () => { ...@@ -140,11 +139,11 @@ describe('Suggestion Diff component', () => {
describe('apply suggestions is clicked', () => { describe('apply suggestions is clicked', () => {
it('emits applyBatch', () => { it('emits applyBatch', () => {
createComponent({ isBatched: true }); createComponent({ isBatched: true, batchSuggestionsCount: 2 });
findApplyBatchButton().vm.$emit('click'); findApplyButton().vm.$emit('apply');
expect(wrapper.emitted().applyBatch).toEqual([[]]); expect(wrapper.emitted().applyBatch).toEqual([[undefined]]);
}); });
}); });
...@@ -155,23 +154,24 @@ describe('Suggestion Diff component', () => { ...@@ -155,23 +154,24 @@ describe('Suggestion Diff component', () => {
isBatched: true, isBatched: true,
}); });
const applyBatchBtn = findApplyBatchButton(); const applyBatchBtn = findApplyButton();
const removeFromBatchBtn = findRemoveFromBatchButton(); const removeFromBatchBtn = findRemoveFromBatchButton();
expect(removeFromBatchBtn.exists()).toBe(true); expect(removeFromBatchBtn.exists()).toBe(true);
expect(removeFromBatchBtn.html().includes('Remove from batch')).toBe(true); expect(removeFromBatchBtn.html().includes('Remove from batch')).toBe(true);
expect(applyBatchBtn.exists()).toBe(true); expect(applyBatchBtn.exists()).toBe(true);
expect(applyBatchBtn.html().includes('Apply suggestions')).toBe(true); expect(applyBatchBtn.html().includes('Apply suggestion')).toBe(true);
expect(applyBatchBtn.html().includes(String('9'))).toBe(true); expect(applyBatchBtn.html().includes(String('9'))).toBe(true);
}); });
it('hides add to batch and apply buttons', () => { it('hides add to batch and apply buttons', () => {
createComponent({ createComponent({
isBatched: true, isBatched: true,
batchSuggestionsCount: 9,
}); });
expect(findApplyButton().exists()).toBe(false); expect(findApplyButton().exists()).toBe(true);
expect(findAddToBatchButton().exists()).toBe(false); expect(findAddToBatchButton().exists()).toBe(false);
}); });
...@@ -215,9 +215,8 @@ describe('Suggestion Diff component', () => { ...@@ -215,9 +215,8 @@ describe('Suggestion Diff component', () => {
}); });
it('disables apply suggestion and hides add to batch button', () => { it('disables apply suggestion and hides add to batch button', () => {
expect(findApplyButton().exists()).toBe(true); expect(findApplyButton().exists()).toBe(false);
expect(findAddToBatchButton().exists()).toBe(false); expect(findAddToBatchButton().exists()).toBe(false);
expect(findApplyButton().attributes('disabled')).toBe('true');
}); });
}); });
...@@ -225,20 +224,11 @@ describe('Suggestion Diff component', () => { ...@@ -225,20 +224,11 @@ describe('Suggestion Diff component', () => {
const findTooltip = () => getBinding(findApplyButton().element, 'gl-tooltip'); const findTooltip = () => getBinding(findApplyButton().element, 'gl-tooltip');
it('renders correct tooltip message when button is applicable', () => { it('renders correct tooltip message when button is applicable', () => {
createComponent(); createComponent({ batchSuggestionsCount: 0 });
const tooltip = findTooltip(); const tooltip = findTooltip();
expect(tooltip.modifiers.viewport).toBe(true); expect(tooltip.modifiers.viewport).toBe(true);
expect(tooltip.value).toBe('This also resolves this thread'); expect(tooltip.value).toBe('This also resolves this thread');
}); });
it('renders the inapplicable reason in the tooltip when button is not applicable', () => {
const inapplicableReason = 'lorem';
createComponent({ canApply: false, inapplicableReason });
const tooltip = findTooltip();
expect(tooltip.modifiers.viewport).toBe(true);
expect(tooltip.value).toBe(inapplicableReason);
});
}); });
}); });
...@@ -77,7 +77,7 @@ describe('Suggestion Diff component', () => { ...@@ -77,7 +77,7 @@ describe('Suggestion Diff component', () => {
it.each` it.each`
event | childArgs | args event | childArgs | args
${'apply'} | ${['test-event']} | ${[{ callback: 'test-event', suggestionId }]} ${'apply'} | ${['test-event']} | ${[{ callback: 'test-event', suggestionId }]}
${'applyBatch'} | ${[]} | ${[]} ${'applyBatch'} | ${['test-event']} | ${['test-event']}
${'addToBatch'} | ${[]} | ${[suggestionId]} ${'addToBatch'} | ${[]} | ${[suggestionId]}
${'removeFromBatch'} | ${[]} | ${[suggestionId]} ${'removeFromBatch'} | ${[]} | ${[suggestionId]}
`('emits $event event on sugestion diff header $event', ({ event, childArgs, args }) => { `('emits $event event on sugestion diff header $event', ({ event, childArgs, args }) => {
......
...@@ -45,6 +45,7 @@ exports[`Upload dropzone component correctly overrides description and drop mess ...@@ -45,6 +45,7 @@ exports[`Upload dropzone component correctly overrides description and drop mess
> >
<div <div
class="mw-50 gl-text-center" class="mw-50 gl-text-center"
style="display: none;"
> >
<h3 <h3
class="" class=""
...@@ -61,7 +62,6 @@ exports[`Upload dropzone component correctly overrides description and drop mess ...@@ -61,7 +62,6 @@ exports[`Upload dropzone component correctly overrides description and drop mess
<div <div
class="mw-50 gl-text-center" class="mw-50 gl-text-center"
style="display: none;"
> >
<h3 <h3
class="" class=""
...@@ -146,7 +146,6 @@ exports[`Upload dropzone component when dragging renders correct template when d ...@@ -146,7 +146,6 @@ exports[`Upload dropzone component when dragging renders correct template when d
<div <div
class="mw-50 gl-text-center" class="mw-50 gl-text-center"
style=""
> >
<h3 <h3
class="" class=""
...@@ -231,7 +230,6 @@ exports[`Upload dropzone component when dragging renders correct template when d ...@@ -231,7 +230,6 @@ exports[`Upload dropzone component when dragging renders correct template when d
<div <div
class="mw-50 gl-text-center" class="mw-50 gl-text-center"
style=""
> >
<h3 <h3
class="" class=""
...@@ -299,6 +297,7 @@ exports[`Upload dropzone component when dragging renders correct template when d ...@@ -299,6 +297,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
> >
<div <div
class="mw-50 gl-text-center" class="mw-50 gl-text-center"
style=""
> >
<h3 <h3
class="" class=""
...@@ -383,6 +382,7 @@ exports[`Upload dropzone component when dragging renders correct template when d ...@@ -383,6 +382,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
> >
<div <div
class="mw-50 gl-text-center" class="mw-50 gl-text-center"
style=""
> >
<h3 <h3
class="" class=""
...@@ -467,6 +467,7 @@ exports[`Upload dropzone component when dragging renders correct template when d ...@@ -467,6 +467,7 @@ exports[`Upload dropzone component when dragging renders correct template when d
> >
<div <div
class="mw-50 gl-text-center" class="mw-50 gl-text-center"
style=""
> >
<h3 <h3
class="" class=""
...@@ -551,6 +552,7 @@ exports[`Upload dropzone component when no slot provided renders default dropzon ...@@ -551,6 +552,7 @@ exports[`Upload dropzone component when no slot provided renders default dropzon
> >
<div <div
class="mw-50 gl-text-center" class="mw-50 gl-text-center"
style="display: none;"
> >
<h3 <h3
class="" class=""
...@@ -567,7 +569,6 @@ exports[`Upload dropzone component when no slot provided renders default dropzon ...@@ -567,7 +569,6 @@ exports[`Upload dropzone component when no slot provided renders default dropzon
<div <div
class="mw-50 gl-text-center" class="mw-50 gl-text-center"
style="display: none;"
> >
<h3 <h3
class="" class=""
...@@ -603,6 +604,7 @@ exports[`Upload dropzone component when slot provided renders dropzone with slot ...@@ -603,6 +604,7 @@ exports[`Upload dropzone component when slot provided renders dropzone with slot
> >
<div <div
class="mw-50 gl-text-center" class="mw-50 gl-text-center"
style="display: none;"
> >
<h3 <h3
class="" class=""
...@@ -619,7 +621,6 @@ exports[`Upload dropzone component when slot provided renders dropzone with slot ...@@ -619,7 +621,6 @@ exports[`Upload dropzone component when slot provided renders dropzone with slot
<div <div
class="mw-50 gl-text-center" class="mw-50 gl-text-center"
style="display: none;"
> >
<h3 <h3
class="" class=""
......
...@@ -1935,6 +1935,21 @@ RSpec.describe QuickActions::InterpretService do ...@@ -1935,6 +1935,21 @@ RSpec.describe QuickActions::InterpretService do
it_behaves_like 'relate command' it_behaves_like 'relate command'
end end
context 'when quick action target is unpersisted' do
let(:issue) { build(:issue, project: project) }
let(:other_issue) { create(:issue, project: project) }
let(:issues_related) { [other_issue] }
let(:content) { "/relate #{other_issue.to_reference}" }
it 'relates the issues after the issue is persisted' do
service.execute(content, issue)
issue.save!
expect(IssueLink.where(source: issue).map(&:target)).to match_array(issues_related)
end
end
context 'empty relate command' do context 'empty relate command' do
let(:issues_related) { [] } let(:issues_related) { [] }
let(:content) { '/relate' } let(:content) { '/relate' }
......
...@@ -9,7 +9,6 @@ ...@@ -9,7 +9,6 @@
- "./ee/spec/finders/ee/namespaces/projects_finder_spec.rb" - "./ee/spec/finders/ee/namespaces/projects_finder_spec.rb"
- "./ee/spec/finders/security/findings_finder_spec.rb" - "./ee/spec/finders/security/findings_finder_spec.rb"
- "./ee/spec/graphql/ee/resolvers/namespace_projects_resolver_spec.rb" - "./ee/spec/graphql/ee/resolvers/namespace_projects_resolver_spec.rb"
- "./ee/spec/lib/analytics/devops_adoption/snapshot_calculator_spec.rb"
- "./ee/spec/lib/ee/gitlab/background_migration/migrate_approver_to_approval_rules_spec.rb" - "./ee/spec/lib/ee/gitlab/background_migration/migrate_approver_to_approval_rules_spec.rb"
- "./ee/spec/lib/ee/gitlab/background_migration/migrate_security_scans_spec.rb" - "./ee/spec/lib/ee/gitlab/background_migration/migrate_security_scans_spec.rb"
- "./ee/spec/lib/ee/gitlab/background_migration/populate_latest_pipeline_ids_spec.rb" - "./ee/spec/lib/ee/gitlab/background_migration/populate_latest_pipeline_ids_spec.rb"
......
# frozen_string_literal: true # frozen_string_literal: true
RSpec.shared_examples 'it uploads and commits a new text file' do RSpec.shared_examples 'it uploads and commits a new text file' do |drop: false|
it 'uploads and commits a new text file', :js do it 'uploads and commits a new text file', :js do
find('.add-to-tree').click find('.add-to-tree').click
...@@ -10,7 +10,11 @@ RSpec.shared_examples 'it uploads and commits a new text file' do ...@@ -10,7 +10,11 @@ RSpec.shared_examples 'it uploads and commits a new text file' do
wait_for_requests wait_for_requests
end end
if drop
find(".upload-dropzone-card").drop(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
else
attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true) attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true)
end
page.within('#modal-upload-blob') do page.within('#modal-upload-blob') do
fill_in(:commit_message, with: 'New commit message') fill_in(:commit_message, with: 'New commit message')
...@@ -32,7 +36,7 @@ RSpec.shared_examples 'it uploads and commits a new text file' do ...@@ -32,7 +36,7 @@ RSpec.shared_examples 'it uploads and commits a new text file' do
end end
end end
RSpec.shared_examples 'it uploads and commits a new image file' do RSpec.shared_examples 'it uploads and commits a new image file' do |drop: false|
it 'uploads and commits a new image file', :js do it 'uploads and commits a new image file', :js do
find('.add-to-tree').click find('.add-to-tree').click
...@@ -42,7 +46,11 @@ RSpec.shared_examples 'it uploads and commits a new image file' do ...@@ -42,7 +46,11 @@ RSpec.shared_examples 'it uploads and commits a new image file' do
wait_for_requests wait_for_requests
end end
if drop
find(".upload-dropzone-card").drop(File.join(Rails.root, 'spec', 'fixtures', 'logo_sample.svg'))
else
attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'logo_sample.svg'), make_visible: true) attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'logo_sample.svg'), make_visible: true)
end
page.within('#modal-upload-blob') do page.within('#modal-upload-blob') do
fill_in(:commit_message, with: 'New commit message') fill_in(:commit_message, with: 'New commit message')
...@@ -58,7 +66,7 @@ RSpec.shared_examples 'it uploads and commits a new image file' do ...@@ -58,7 +66,7 @@ RSpec.shared_examples 'it uploads and commits a new image file' do
end end
end end
RSpec.shared_examples 'it uploads and commits a new pdf file' do RSpec.shared_examples 'it uploads and commits a new pdf file' do |drop: false|
it 'uploads and commits a new pdf file', :js do it 'uploads and commits a new pdf file', :js do
find('.add-to-tree').click find('.add-to-tree').click
...@@ -68,7 +76,11 @@ RSpec.shared_examples 'it uploads and commits a new pdf file' do ...@@ -68,7 +76,11 @@ RSpec.shared_examples 'it uploads and commits a new pdf file' do
wait_for_requests wait_for_requests
end end
if drop
find(".upload-dropzone-card").drop(File.join(Rails.root, 'spec', 'fixtures', 'git-cheat-sheet.pdf'))
else
attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'git-cheat-sheet.pdf'), make_visible: true) attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'git-cheat-sheet.pdf'), make_visible: true)
end
page.within('#modal-upload-blob') do page.within('#modal-upload-blob') do
fill_in(:commit_message, with: 'New commit message') fill_in(:commit_message, with: 'New commit message')
...@@ -84,7 +96,7 @@ RSpec.shared_examples 'it uploads and commits a new pdf file' do ...@@ -84,7 +96,7 @@ RSpec.shared_examples 'it uploads and commits a new pdf file' do
end end
end end
RSpec.shared_examples 'it uploads and commits a new file to a forked project' do RSpec.shared_examples 'it uploads and commits a new file to a forked project' do |drop: false|
let(:fork_message) do let(:fork_message) do
"You're not allowed to make changes to this project directly. "\ "You're not allowed to make changes to this project directly. "\
"A fork of this project has been created that you can make changes in, so you can submit a merge request." "A fork of this project has been created that you can make changes in, so you can submit a merge request."
...@@ -100,7 +112,12 @@ RSpec.shared_examples 'it uploads and commits a new file to a forked project' do ...@@ -100,7 +112,12 @@ RSpec.shared_examples 'it uploads and commits a new file to a forked project' do
find('.add-to-tree').click find('.add-to-tree').click
click_link('Upload file') click_link('Upload file')
if drop
find(".upload-dropzone-card").drop(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
else
attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true) attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true)
end
page.within('#modal-upload-blob') do page.within('#modal-upload-blob') do
fill_in(:commit_message, with: 'New commit message') fill_in(:commit_message, with: 'New commit message')
...@@ -123,7 +140,7 @@ RSpec.shared_examples 'it uploads and commits a new file to a forked project' do ...@@ -123,7 +140,7 @@ RSpec.shared_examples 'it uploads and commits a new file to a forked project' do
end end
end end
RSpec.shared_examples 'it uploads a file to a sub-directory' do RSpec.shared_examples 'it uploads a file to a sub-directory' do |drop: false|
it 'uploads a file to a sub-directory', :js do it 'uploads a file to a sub-directory', :js do
click_link 'files' click_link 'files'
...@@ -133,7 +150,12 @@ RSpec.shared_examples 'it uploads a file to a sub-directory' do ...@@ -133,7 +150,12 @@ RSpec.shared_examples 'it uploads a file to a sub-directory' do
find('.add-to-tree').click find('.add-to-tree').click
click_link('Upload file') click_link('Upload file')
if drop
find(".upload-dropzone-card").drop(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
else
attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true) attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true)
end
page.within('#modal-upload-blob') do page.within('#modal-upload-blob') do
fill_in(:commit_message, with: 'New commit message') fill_in(:commit_message, with: 'New commit message')
...@@ -150,11 +172,15 @@ RSpec.shared_examples 'it uploads a file to a sub-directory' do ...@@ -150,11 +172,15 @@ RSpec.shared_examples 'it uploads a file to a sub-directory' do
end end
end end
RSpec.shared_examples 'uploads and commits a new text file via "upload file" button' do RSpec.shared_examples 'uploads and commits a new text file via "upload file" button' do |drop: false|
it 'uploads and commits a new text file via "upload file" button', :js do it 'uploads and commits a new text file via "upload file" button', :js do
find('[data-testid="upload-file-button"]').click find('[data-testid="upload-file-button"]').click
if drop
find(".upload-dropzone-card").drop(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
else
attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true) attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true)
end
page.within('#details-modal-upload-blob') do page.within('#details-modal-upload-blob') do
fill_in(:commit_message, with: 'New commit message') fill_in(:commit_message, with: 'New commit message')
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment