Commit 84bf4915 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'ph/28154/moveFrontendBatchCommentsFiles' into 'master'

[RUN AS-IF-FOSS] Move frontend batch comments code into core

See merge request gitlab-org/gitlab!32558
parents 0af80aed fdd25500
import Vue from 'vue';
import { mapActions } from 'vuex';
import store from '~/mr_notes/stores';
import ReviewBar from './components/review_bar.vue';
// eslint-disable-next-line import/prefer-default-export
export const initReviewBar = () => {
const el = document.getElementById('js-review-bar');
// eslint-disable-next-line no-new
new Vue({
el,
store,
mounted() {
this.fetchDrafts();
},
methods: {
...mapActions('batchComments', ['fetchDrafts']),
},
render(createElement) {
return createElement(ReviewBar);
},
});
};
import { sprintf, __ } from '~/locale';
import { mapGetters } from 'vuex';
import { sprintf, s__, __ } from '~/locale';
export default {
props: {
discussionId: {
type: String,
required: false,
default: null,
},
resolveDiscussion: {
type: Boolean,
required: false,
default: false,
},
isDraft: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
...mapGetters(['isDiscussionResolved']),
resolvedStatusMessage() {
let message;
const discussionResolved = this.isDiscussionResolved(
this.draft ? this.draft.discussion_id : this.discussionId,
);
const discussionToBeResolved = this.draft
? this.draft.resolve_discussion
: this.resolveDiscussion;
if (discussionToBeResolved && discussionResolved && !this.$options.showStaysResolved) {
return undefined;
}
if (discussionToBeResolved) {
message = discussionResolved
? s__('MergeRequests|Thread stays resolved')
: s__('MergeRequests|Thread will be resolved');
} else if (discussionResolved) {
message = s__('MergeRequests|Thread will be unresolved');
} else if (this.$options.showStaysResolved) {
message = s__('MergeRequests|Thread stays unresolved');
}
return message;
},
componentClasses() {
return this.resolveDiscussion ? 'is-resolving-discussion' : 'is-unresolving-discussion';
},
resolveButtonTitle() {
let title = __('Mark comment as resolved');
if (this.isDraft || this.discussionId) return this.resolvedStatusMessage;
let title = __('Mark as resolved');
if (this.resolvedBy) {
title = sprintf(__('Resolved by %{name}'), { name: this.resolvedBy.name });
......@@ -12,4 +61,5 @@ export default {
return title;
},
},
showStaysResolved: true,
};
......@@ -5,10 +5,6 @@ import service from '../../../services/drafts_service';
import * as types from './mutation_types';
import { CHANGES_TAB, DISCUSSION_TAB, SHOW_TAB } from '../../../constants';
export const enableBatchComments = ({ commit }) => {
commit(types.ENABLE_BATCH_COMMENTS);
};
export const saveDraft = ({ dispatch }, draft) =>
dispatch('saveNote', { ...draft, isDraft: true }, { root: true });
......
......@@ -6,10 +6,6 @@ const processDraft = draft => ({
});
export default {
[types.ENABLE_BATCH_COMMENTS](state) {
state.withBatchComments = true;
},
[types.ADD_NEW_DRAFT](state, draft) {
state.drafts.push(processDraft(draft));
},
......
export default () => ({
withBatchComments: false,
withBatchComments: true,
isDraftsFetched: false,
drafts: [],
isPublishing: false,
......
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import { GlLoadingIcon } from '@gitlab/ui';
import diffLineNoteFormMixin from 'ee_else_ce/notes/mixins/diff_line_note_form';
import draftCommentsMixin from 'ee_else_ce/diffs/mixins/draft_comments';
import diffLineNoteFormMixin from '~/notes/mixins/diff_line_note_form';
import draftCommentsMixin from '~/diffs/mixins/draft_comments';
import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
import NotDiffableViewer from '~/vue_shared/components/diff_viewer/viewers/not_diffable.vue';
import NoPreviewViewer from '~/vue_shared/components/diff_viewer/viewers/no_preview.vue';
import DiffFileDrafts from '~/batch_comments/components/diff_file_drafts.vue';
import InlineDiffView from './inline_diff_view.vue';
import ParallelDiffView from './parallel_diff_view.vue';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
......@@ -29,7 +30,7 @@ export default {
NotDiffableViewer,
NoPreviewViewer,
userAvatarLink,
DiffFileDrafts: () => import('ee_component/batch_comments/components/diff_file_drafts.vue'),
DiffFileDrafts,
},
mixins: [diffLineNoteFormMixin, draftCommentsMixin],
props: {
......
<script>
import { mapState, mapGetters, mapActions } from 'vuex';
import diffLineNoteFormMixin from 'ee_else_ce/notes/mixins/diff_line_note_form';
import diffLineNoteFormMixin from '~/notes/mixins/diff_line_note_form';
import { s__ } from '~/locale';
import noteForm from '../../notes/components/note_form.vue';
import autosave from '../../notes/mixins/autosave';
......
<script>
import { mapGetters } from 'vuex';
import draftCommentsMixin from 'ee_else_ce/diffs/mixins/draft_comments';
import draftCommentsMixin from '~/diffs/mixins/draft_comments';
import InlineDraftCommentRow from '~/batch_comments/components/inline_draft_comment_row.vue';
import inlineDiffTableRow from './inline_diff_table_row.vue';
import inlineDiffCommentRow from './inline_diff_comment_row.vue';
import inlineDiffExpansionRow from './inline_diff_expansion_row.vue';
......@@ -9,8 +10,7 @@ export default {
components: {
inlineDiffCommentRow,
inlineDiffTableRow,
InlineDraftCommentRow: () =>
import('ee_component/batch_comments/components/inline_draft_comment_row.vue'),
InlineDraftCommentRow,
inlineDiffExpansionRow,
},
mixins: [draftCommentsMixin],
......
<script>
import { mapGetters } from 'vuex';
import draftCommentsMixin from 'ee_else_ce/diffs/mixins/draft_comments';
import draftCommentsMixin from '~/diffs/mixins/draft_comments';
import ParallelDraftCommentRow from '~/batch_comments/components/parallel_draft_comment_row.vue';
import parallelDiffTableRow from './parallel_diff_table_row.vue';
import parallelDiffCommentRow from './parallel_diff_comment_row.vue';
import parallelDiffExpansionRow from './parallel_diff_expansion_row.vue';
......@@ -10,8 +11,7 @@ export default {
parallelDiffExpansionRow,
parallelDiffTableRow,
parallelDiffCommentRow,
ParallelDraftCommentRow: () =>
import('ee_component/batch_comments/components/parallel_draft_comment_row.vue'),
ParallelDraftCommentRow,
},
mixins: [draftCommentsMixin],
props: {
......
import { mapGetters } from 'vuex';
export default {
computed: {
shouldRenderDraftRow: () => () => false,
shouldRenderParallelDraftRow: () => () => false,
draftForLine: () => () => ({}),
...mapGetters('batchComments', [
'shouldRenderDraftRow',
'shouldRenderParallelDraftRow',
'draftForLine',
'draftsForFile',
'hasParallelDraftLeft',
'hasParallelDraftRight',
]),
imageDiscussions() {
return this.diffFile.discussions;
return this.diffFile.discussions.concat(this.draftsForFile(this.diffFile.file_hash));
},
hasParallelDraftLeft: () => () => false,
hasParallelDraftRight: () => () => false,
},
};
import Vue from 'vue';
import store from 'ee_else_ce/mr_notes/stores';
import store from '~/mr_notes/stores';
import initNotesApp from './init_notes';
import initDiffsApp from '../diffs';
import discussionCounter from '../notes/components/discussion_counter.vue';
......
import $ from 'jquery';
import Vue from 'vue';
import { mapActions, mapState, mapGetters } from 'vuex';
import store from 'ee_else_ce/mr_notes/stores';
import store from '~/mr_notes/stores';
import notesApp from '../notes/components/notes_app.vue';
import discussionKeyboardNavigator from '../notes/components/discussion_keyboard_navigator.vue';
import initWidget from '../vue_merge_request_widget';
......
import Vue from 'vue';
import Vuex from 'vuex';
import batchCommentsModule from '~/batch_comments/stores/modules/batch_comments';
import notesModule from '~/notes/stores/modules';
import diffsModule from '~/diffs/store/modules';
import mrPageModule from './modules';
......@@ -12,6 +13,7 @@ export const createStore = () =>
page: mrPageModule(),
notes: notesModule(),
diffs: diffsModule(),
batchComments: batchCommentsModule(),
},
});
......
<script>
import { mapGetters } from 'vuex';
import { GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
import resolvedStatusMixin from 'ee_else_ce/batch_comments/mixins/resolved_status';
import resolvedStatusMixin from '~/batch_comments/mixins/resolved_status';
import Icon from '~/vue_shared/components/icon.vue';
import ReplyButton from './note_actions/reply_button.vue';
......
<script>
import { mapActions } from 'vuex';
import { mapActions, mapGetters } from 'vuex';
import $ from 'jquery';
import '~/behaviors/markdown/render_gfm';
import getDiscussion from 'ee_else_ce/notes/mixins/get_discussion';
import noteEditedText from './note_edited_text.vue';
import noteAwardsList from './note_awards_list.vue';
import noteAttachment from './note_attachment.vue';
......@@ -18,7 +17,7 @@ export default {
noteForm,
Suggestions,
},
mixins: [autosave, getDiscussion],
mixins: [autosave],
props: {
note: {
type: Object,
......@@ -45,6 +44,12 @@ export default {
},
},
computed: {
...mapGetters(['getDiscussion']),
discussion() {
if (!this.note.isDraft) return {};
return this.getDiscussion(this.note.discussion_id);
},
noteBody() {
return this.note.note;
},
......
<script>
import { mapGetters, mapActions } from 'vuex';
import noteFormMixin from 'ee_else_ce/notes/mixins/note_form';
import { mapGetters, mapActions, mapState } from 'vuex';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import eventHub from '../event_hub';
import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
......@@ -16,7 +15,7 @@ export default {
issueWarning,
markdownField,
},
mixins: [issuableStateMixin, resolvable, noteFormMixin],
mixins: [issuableStateMixin, resolvable],
props: {
noteBody: {
type: String,
......@@ -82,6 +81,11 @@ export default {
required: false,
default: false,
},
isDraft: {
type: Boolean,
required: false,
default: false,
},
},
data() {
let updatedNoteBody = this.noteBody;
......@@ -107,6 +111,16 @@ export default {
'getNotesDataByProp',
'getUserDataByProp',
]),
...mapState({
withBatchComments: state => state.batchComments?.withBatchComments,
}),
...mapGetters('batchComments', ['hasDrafts']),
showBatchCommentsActions() {
return this.withBatchComments && this.noteId === '' && !this.discussion.for_commit;
},
showResolveDiscussionToggle() {
return (this.discussion?.id && this.discussion.resolvable) || this.isDraft;
},
noteHash() {
if (this.noteId) {
return `#note_${this.noteId}`;
......@@ -202,8 +216,6 @@ export default {
methods: {
...mapActions(['toggleResolveNote']),
shouldToggleResolved(shouldResolve, beforeSubmitDiscussionState) {
// shouldBeResolved() checks the actual resolution state,
// considering batchComments (EEP), if applicable/enabled.
const newResolvedStateAfterUpdate =
this.shouldBeResolved && this.shouldBeResolved(shouldResolve);
......@@ -234,6 +246,50 @@ export default {
updateDraft(autosaveKey, text);
}
},
handleKeySubmit() {
if (this.showBatchCommentsActions) {
this.handleAddToReview();
} else {
this.handleUpdate();
}
},
handleUpdate(shouldResolve) {
const beforeSubmitDiscussionState = this.discussionResolved;
this.isSubmitting = true;
this.$emit(
'handleFormUpdate',
this.updatedNoteBody,
this.$refs.editNoteForm,
() => {
this.isSubmitting = false;
if (this.shouldToggleResolved(shouldResolve, beforeSubmitDiscussionState)) {
this.resolveHandler(beforeSubmitDiscussionState);
}
},
this.discussionResolved ? !this.isUnresolving : this.isResolving,
);
},
shouldBeResolved(resolveStatus) {
if (this.withBatchComments) {
return (
(this.discussionResolved && !this.isUnresolving) ||
(!this.discussionResolved && this.isResolving)
);
}
return resolveStatus;
},
handleAddToReview() {
// check if draft should resolve thread
const shouldResolve =
(this.discussionResolved && !this.isUnresolving) ||
(!this.discussionResolved && this.isResolving);
this.isSubmitting = true;
this.$emit('handleFormUpdateAddToReview', this.updatedNoteBody, shouldResolve);
},
},
};
</script>
......@@ -293,6 +349,7 @@ export default {
<input
v-model="isUnresolving"
type="checkbox"
class="js-unresolve-checkbox"
data-qa-selector="unresolve_review_discussion_checkbox"
/>
{{ __('Unresolve thread') }}
......@@ -301,6 +358,7 @@ export default {
<input
v-model="isResolving"
type="checkbox"
class="js-resolve-checkbox"
data-qa-selector="resolve_review_discussion_checkbox"
/>
{{ __('Resolve thread') }}
......@@ -320,7 +378,7 @@ export default {
<button
:disabled="isDisabled"
type="button"
class="btn qa-comment-now"
class="btn qa-comment-now js-comment-button"
@click="handleUpdate()"
>
{{ __('Add comment now') }}
......
<script>
import { mapActions, mapGetters } from 'vuex';
import { GlTooltipDirective } from '@gitlab/ui';
import diffLineNoteFormMixin from 'ee_else_ce/notes/mixins/diff_line_note_form';
import diffLineNoteFormMixin from '~/notes/mixins/diff_line_note_form';
import { s__, __ } from '~/locale';
import { clearDraft, getDiscussionReplyKey } from '~/lib/utils/autosave';
import icon from '~/vue_shared/components/icon.vue';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import DraftNote from '~/batch_comments/components/draft_note.vue';
import Flash from '../../flash';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import diffDiscussionHeader from './diff_discussion_header.vue';
......@@ -26,7 +27,7 @@ export default {
diffDiscussionHeader,
noteSignedOutWidget,
noteForm,
DraftNote: () => import('ee_component/batch_comments/components/draft_note.vue'),
DraftNote,
TimelineEntryItem,
DiscussionNotes,
DiscussionActions,
......
......@@ -2,7 +2,6 @@
import $ from 'jquery';
import { mapGetters, mapActions } from 'vuex';
import { escape } from 'lodash';
import draftMixin from 'ee_else_ce/notes/mixins/draft';
import { truncateSha } from '~/lib/utils/text_utility';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import { __, s__, sprintf } from '../../locale';
......@@ -25,7 +24,7 @@ export default {
NoteBody,
TimelineEntryItem,
},
mixins: [noteable, resolvable, draftMixin],
mixins: [noteable, resolvable],
props: {
note: {
type: Object,
......@@ -105,6 +104,15 @@ export default {
)}</a>`;
return sprintf(s__('MergeRequests|commented on commit %{commitLink}'), { commitLink }, false);
},
isDraft() {
return this.note.isDraft;
},
canResolve() {
return (
this.note.current_user.can_resolve ||
(this.note.isDraft && this.note.discussion_id !== null)
);
},
},
created() {
......
import { mapActions, mapGetters, mapState } from 'vuex';
import { getDraftReplyFormData, getDraftFormData } from '~/batch_comments/utils';
import { TEXT_DIFF_POSITION_TYPE, IMAGE_DIFF_POSITION_TYPE } from '~/diffs/constants';
import createFlash from '~/flash';
import { s__ } from '~/locale';
import { clearDraft } from '~/lib/utils/autosave';
export default {
computed: {
draftForDiscussion: () => () => ({}),
...mapState({
noteableData: state => state.notes.noteableData,
notesData: state => state.notes.notesData,
withBatchComments: state => state.batchComments?.withBatchComments,
}),
...mapGetters('diffs', ['getDiffFileByHash']),
...mapGetters('batchComments', ['shouldRenderDraftRowInDiscussion', 'draftForDiscussion']),
...mapState('diffs', ['commit']),
},
methods: {
showDraft: () => false,
addReplyToReview: () => {},
addToReview: () => {},
...mapActions('diffs', ['cancelCommentForm']),
...mapActions('batchComments', ['addDraftToReview', 'saveDraft', 'insertDraftIntoDrafts']),
addReplyToReview(noteText, isResolving) {
const postData = getDraftReplyFormData({
in_reply_to_discussion_id: this.discussion.reply_id,
target_type: this.getNoteableData.targetType,
notesData: this.notesData,
draft_note: {
note: noteText,
resolve_discussion: isResolving,
},
});
if (this.discussion.for_commit) {
postData.note_project_id = this.discussion.project_id;
}
this.isReplying = false;
this.saveDraft(postData)
.then(() => {
this.handleClearForm(this.discussion.line_code);
})
.catch(() => {
createFlash(s__('MergeRequests|An error occurred while saving the draft comment.'));
});
},
addToReview(note) {
const positionType = this.diffFileCommentForm
? IMAGE_DIFF_POSITION_TYPE
: TEXT_DIFF_POSITION_TYPE;
const selectedDiffFile = this.getDiffFileByHash(this.diffFileHash);
const postData = getDraftFormData({
note,
notesData: this.notesData,
noteableData: this.noteableData,
noteableType: this.noteableType,
noteTargetLine: this.noteTargetLine,
diffViewType: this.diffViewType,
diffFile: selectedDiffFile,
linePosition: this.position,
positionType,
...this.diffFileCommentForm,
});
const diffFileHeadSha = this.commit && this?.diffFile?.diff_refs?.head_sha;
postData.data.note.commit_id = diffFileHeadSha || null;
return this.saveDraft(postData)
.then(() => {
if (positionType === IMAGE_DIFF_POSITION_TYPE) {
this.closeDiffFileCommentForm(this.diffFileHash);
} else {
this.handleClearForm(this.line.line_code);
}
})
.catch(() => {
createFlash(s__('MergeRequests|An error occurred while saving the draft comment.'));
});
},
handleClearForm(lineCode) {
this.cancelCommentForm({
lineCode,
fileHash: this.diffFileHash,
});
this.$nextTick(() => {
if (this.autosaveKey) {
clearDraft(this.autosaveKey);
} else {
// TODO: remove the following after replacing the autosave mixin
// https://gitlab.com/gitlab-org/gitlab-foss/issues/60587
this.resetAutoSave();
}
});
},
showDraft(replyId) {
return this.withBatchComments && this.shouldRenderDraftRowInDiscussion(replyId);
},
},
};
export default {
computed: {
isDraft: () => false,
canResolve() {
return this.note.current_user.can_resolve;
},
},
};
export default {
computed: {
discussion() {
return {};
},
},
};
export default {
data() {
return {
showBatchCommentsActions: false,
};
},
methods: {
handleKeySubmit() {
this.handleUpdate();
},
handleUpdate(shouldResolve) {
const beforeSubmitDiscussionState = this.discussionResolved;
this.isSubmitting = true;
this.$emit('handleFormUpdate', this.updatedNoteBody, this.$refs.editNoteForm, () => {
this.isSubmitting = false;
if (this.shouldToggleResolved(shouldResolve, beforeSubmitDiscussionState)) {
this.resolveHandler(beforeSubmitDiscussionState);
}
});
},
},
};
import initMrNotes from '~/mr_notes';
import { initReviewBar } from '~/batch_comments';
import initSidebarBundle from '~/sidebar/sidebar_bundle';
import initShow from '../init_merge_request_show';
......@@ -8,4 +9,5 @@ document.addEventListener('DOMContentLoaded', () => {
initSidebarBundle();
}
initMrNotes();
initReviewBar();
});
......@@ -169,7 +169,7 @@ module NotesHelper
end
def notes_data(issuable)
{
data = {
discussionsPath: discussions_path(issuable),
registerPath: new_session_path(:user, redirect_to_referer: 'yes', anchor: 'register-pane'),
newSessionPath: new_session_path(:user, redirect_to_referer: 'yes'),
......@@ -181,6 +181,16 @@ module NotesHelper
prerenderedNotesCount: issuable.capped_notes_count(MAX_PRERENDERED_NOTES),
lastFetchedAt: Time.now.to_i
}
if issuable.is_a?(MergeRequest)
data.merge!(
draftsPath: project_merge_request_drafts_path(@project, issuable),
draftsPublishPath: publish_project_merge_request_drafts_path(@project, issuable),
draftsDiscardPath: discard_project_merge_request_drafts_path(@project, issuable)
)
end
data
end
def discussion_resolved_intro(discussion)
......
......@@ -100,3 +100,5 @@
= render "projects/commit/change", type: 'revert', commit: @merge_request.merge_commit, title: @merge_request.title
- if @merge_request.can_be_cherry_picked?
= render "projects/commit/change", type: 'cherry-pick', commit: @merge_request.merge_commit, title: @merge_request.title
#js-review-bar
---
title: Moves merge request reviews into Core
merge_request: 32558
author:
type: other
import Vue from 'vue';
import { mapState, mapActions } from 'vuex';
import store from 'ee_else_ce/mr_notes/stores';
import ReviewBar from './components/review_bar.vue';
// eslint-disable-next-line import/prefer-default-export
export const initReviewBar = () => {
const el = document.getElementById('js-review-bar');
if (el) {
// eslint-disable-next-line no-new
new Vue({
el,
store,
computed: {
...mapState('batchComments', ['withBatchComments']),
},
created() {
this.enableBatchComments();
},
mounted() {
this.fetchDrafts();
},
methods: {
...mapActions('batchComments', ['fetchDrafts', 'enableBatchComments']),
},
render(createElement) {
if (this.withBatchComments) {
return createElement(ReviewBar);
}
return null;
},
});
}
};
import { mapGetters } from 'vuex';
import { sprintf, s__, __ } from '~/locale';
export default {
props: {
discussionId: {
type: String,
required: false,
default: null,
},
resolveDiscussion: {
type: Boolean,
required: false,
default: false,
},
isDraft: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
...mapGetters(['isDiscussionResolved']),
resolvedStatusMessage() {
let message;
const discussionResolved = this.isDiscussionResolved(
this.draft ? this.draft.discussion_id : this.discussionId,
);
const discussionToBeResolved = this.draft
? this.draft.resolve_discussion
: this.resolveDiscussion;
if (discussionToBeResolved && discussionResolved && !this.$options.showStaysResolved) {
return undefined;
}
if (discussionToBeResolved) {
if (discussionResolved) {
message = s__('MergeRequests|Thread stays resolved');
} else {
message = s__('MergeRequests|Thread will be resolved');
}
} else if (discussionResolved) {
message = s__('MergeRequests|Thread will be unresolved');
} else if (this.$options.showStaysResolved) {
message = s__('MergeRequests|Thread stays unresolved');
}
return message;
},
componentClasses() {
return this.resolveDiscussion ? 'is-resolving-discussion' : 'is-unresolving-discussion';
},
resolveButtonTitle() {
if (this.isDraft || this.discussionId) return this.resolvedStatusMessage;
let title = __('Mark as resolved');
if (this.resolvedBy) {
title = sprintf(__('Resolved by %{name}'), { name: this.resolvedBy.name });
}
return title;
},
},
showStaysResolved: true,
};
import { mapGetters } from 'vuex';
export default {
computed: {
...mapGetters('batchComments', [
'shouldRenderDraftRow',
'shouldRenderParallelDraftRow',
'draftForLine',
'draftsForFile',
'hasParallelDraftLeft',
'hasParallelDraftRight',
]),
imageDiscussions() {
return this.diffFile.discussions.concat(this.draftsForFile(this.diffFile.file_hash));
},
},
};
import Vue from 'vue';
import Vuex from 'vuex';
import batchCommentsModule from 'ee/batch_comments/stores/modules/batch_comments';
import notesModule from '~/notes/stores/modules';
import diffsModule from '~/diffs/store/modules';
import mrPageModule from '~/mr_notes/stores/modules';
Vue.use(Vuex);
export const createStore = () =>
new Vuex.Store({
modules: {
page: mrPageModule(),
notes: notesModule(),
diffs: diffsModule(),
batchComments: batchCommentsModule(),
},
});
export default createStore();
import { mapActions, mapGetters, mapState } from 'vuex';
import { getDraftReplyFormData, getDraftFormData } from 'ee/batch_comments/utils';
import { TEXT_DIFF_POSITION_TYPE, IMAGE_DIFF_POSITION_TYPE } from '~/diffs/constants';
import createFlash from '~/flash';
import { s__ } from '~/locale';
import { clearDraft } from '~/lib/utils/autosave';
export default {
computed: {
...mapState({
noteableData: state => state.notes.noteableData,
notesData: state => state.notes.notesData,
withBatchComments: state => state.batchComments && state.batchComments.withBatchComments,
}),
...mapGetters('diffs', ['getDiffFileByHash']),
...mapGetters('batchComments', ['shouldRenderDraftRowInDiscussion', 'draftForDiscussion']),
...mapState('diffs', ['commit']),
},
methods: {
...mapActions('diffs', ['cancelCommentForm']),
...mapActions('batchComments', ['addDraftToReview', 'saveDraft', 'insertDraftIntoDrafts']),
addReplyToReview(noteText, isResolving) {
const postData = getDraftReplyFormData({
in_reply_to_discussion_id: this.discussion.reply_id,
target_type: this.getNoteableData.targetType,
notesData: this.notesData,
draft_note: {
note: noteText,
resolve_discussion: isResolving,
},
});
if (this.discussion.for_commit) {
postData.note_project_id = this.discussion.project_id;
}
this.isReplying = false;
this.saveDraft(postData)
.then(() => {
this.handleClearForm(this.discussion.line_code);
})
.catch(() => {
createFlash(s__('MergeRequests|An error occurred while saving the draft comment.'));
});
},
addToReview(note) {
const positionType = this.diffFileCommentForm
? IMAGE_DIFF_POSITION_TYPE
: TEXT_DIFF_POSITION_TYPE;
const selectedDiffFile = this.getDiffFileByHash(this.diffFileHash);
const postData = getDraftFormData({
note,
notesData: this.notesData,
noteableData: this.noteableData,
noteableType: this.noteableType,
noteTargetLine: this.noteTargetLine,
diffViewType: this.diffViewType,
diffFile: selectedDiffFile,
linePosition: this.position,
positionType,
...this.diffFileCommentForm,
});
const diffFileHeadSha = this.commit && this?.diffFile?.diff_refs?.head_sha;
postData.data.note.commit_id = diffFileHeadSha || null;
return this.saveDraft(postData)
.then(() => {
if (positionType === IMAGE_DIFF_POSITION_TYPE) {
this.closeDiffFileCommentForm(this.diffFileHash);
} else {
this.handleClearForm(this.line.line_code);
}
})
.catch(() => {
createFlash(s__('MergeRequests|An error occurred while saving the draft comment.'));
});
},
handleClearForm(lineCode) {
this.cancelCommentForm({
lineCode,
fileHash: this.diffFileHash,
});
this.$nextTick(() => {
if (this.autosaveKey) {
clearDraft(this.autosaveKey);
} else {
// TODO: remove the following after replacing the autosave mixin
// https://gitlab.com/gitlab-org/gitlab-foss/issues/60587
this.resetAutoSave();
}
});
},
showDraft(replyId) {
if (this.withBatchComments) {
return this.shouldRenderDraftRowInDiscussion(replyId);
}
return false;
},
},
};
export default {
computed: {
isDraft() {
return this.note.isDraft;
},
canResolve() {
return (
this.note.current_user.can_resolve ||
(this.note.isDraft && this.note.discussion_id !== null)
);
},
},
};
import { mapGetters } from 'vuex';
export default {
computed: {
...mapGetters(['getDiscussion']),
discussion() {
if (!this.note.isDraft) return {};
return this.getDiscussion(this.note.discussion_id);
},
},
};
import { mapGetters, mapState } from 'vuex';
export default {
props: {
isDraft: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
...mapState({
withBatchComments: state => state.batchComments && state.batchComments.withBatchComments,
}),
...mapGetters('batchComments', ['hasDrafts']),
showBatchCommentsActions() {
return this.withBatchComments && this.noteId === '' && !this.discussion.for_commit;
},
showResolveDiscussionToggle() {
return (
((this.discussion && this.discussion.id && this.discussion.resolvable) || this.isDraft) &&
this.withBatchComments
);
},
},
methods: {
handleKeySubmit() {
if (this.showBatchCommentsActions) {
this.handleAddToReview();
} else {
this.handleUpdate();
}
},
handleUpdate(shouldResolve) {
const beforeSubmitDiscussionState = this.discussionResolved;
this.isSubmitting = true;
this.$emit(
'handleFormUpdate',
this.updatedNoteBody,
this.$refs.editNoteForm,
() => {
this.isSubmitting = false;
if (this.shouldToggleResolved(shouldResolve, beforeSubmitDiscussionState)) {
this.resolveHandler(beforeSubmitDiscussionState);
}
},
this.discussionResolved ? !this.isUnresolving : this.isResolving,
);
},
shouldBeResolved(resolveStatus) {
if (this.withBatchComments) {
return (
(this.discussionResolved && !this.isUnresolving) ||
(!this.discussionResolved && this.isResolving)
);
}
return resolveStatus;
},
handleAddToReview() {
// check if draft should resolve thread
const shouldResolve =
(this.discussionResolved && !this.isUnresolving) ||
(!this.discussionResolved && this.isResolving);
this.isSubmitting = true;
this.$emit('handleFormUpdateAddToReview', this.updatedNoteBody, shouldResolve);
},
},
};
import initSidebarBundle from 'ee/sidebar/sidebar_bundle';
import { initReviewBar } from 'ee/batch_comments';
import { initReviewBar } from '~/batch_comments';
import initMrNotes from '~/mr_notes';
import initShow from '~/pages/projects/merge_requests/init_merge_request_show';
......
......@@ -20,21 +20,6 @@ module EE
super
end
override :notes_data
def notes_data(issuable)
data = super
if issuable.is_a?(MergeRequest)
data.merge!(
draftsPath: project_merge_request_drafts_path(@project, issuable),
draftsPublishPath: publish_project_merge_request_drafts_path(@project, issuable),
draftsDiscardPath: discard_project_merge_request_drafts_path(@project, issuable)
)
end
data
end
def description_diff_path(issuable, version_id)
case issuable
when Issue
......
= render_ce "projects/merge_requests/show"
- if batch_comments_enabled?
#js-review-bar
= javascript_tag nonce: true do
:plain
// Append static, server-generated data not included in merge request entity (EE-Only)
......
import { shallowMount } from '@vue/test-utils';
import { TEST_HOST } from 'spec/test_constants';
import createStore from '~/notes/stores';
import noteActions from '~/notes/components/note_actions.vue';
import { userDataMock } from 'jest/notes/mock_data';
describe('noteActions', () => {
let wrapper;
let store;
let props;
const createWrapper = propsData =>
shallowMount(noteActions, {
store,
propsData,
});
beforeEach(() => {
store = createStore();
props = {
accessLevel: 'Maintainer',
authorId: 26,
canDelete: true,
canEdit: true,
canAwardEmoji: true,
canReportAsAbuse: true,
canResolve: true,
noteId: '539',
noteUrl: `${TEST_HOST}/group/project/-/merge_requests/1#note_1`,
reportAbusePath: `${TEST_HOST}/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-foss%2Fissues%2F7%23note_539&user_id=26`,
showReply: false,
isDraft: true,
};
});
afterEach(() => {
wrapper.destroy();
});
describe('Draft notes', () => {
beforeEach(() => {
store.dispatch('setUserData', userDataMock);
wrapper = createWrapper(props);
});
it('should render the right resolve button title', () => {
const resolveButton = wrapper.find({ ref: 'resolveButton' });
expect(resolveButton.exists()).toBe(true);
expect(resolveButton.attributes('title')).toBe('Thread stays unresolved');
});
});
});
import Vue from 'vue';
import { createStore } from 'ee/batch_comments/stores';
import { keyboardDownEvent } from 'jest/issue_show/helpers';
import { noteableDataMock, discussionMock, notesDataMock } from 'jest/notes/mock_data';
import diffsModule from '~/diffs/store/modules';
import notesModule from '~/notes/stores/modules';
import issueNoteForm from '~/notes/components/note_form.vue';
describe('issue_note_form component', () => {
let store;
let vm;
let props;
beforeEach(() => {
const Component = Vue.extend(issueNoteForm);
store = createStore();
store.registerModule('diffs', diffsModule());
store.registerModule('notes', notesModule());
store.dispatch('setNoteableData', noteableDataMock);
store.dispatch('setNotesData', notesDataMock);
props = {
isEditing: false,
noteBody: 'Magni suscipit eius consectetur enim et ex et commodi.',
discussion: { ...discussionMock, for_commit: false },
};
vm = new Component({
store,
propsData: props,
}).$mount();
});
afterEach(() => {
vm.$destroy();
});
describe('without batch comments', () => {
it('does not show resolve checkbox', () => {
expect(vm.$el.querySelector('[data-qa-selector="resolve_review_discussion_checkbox"]')).toBe(
null,
);
});
describe('on enter', () => {
it('should add comment when cmd+enter is pressed', () => {
jest.spyOn(vm, 'handleUpdate');
vm.$el.querySelector('textarea').value = 'Foo';
vm.$el.querySelector('textarea').dispatchEvent(keyboardDownEvent(13, true));
expect(vm.handleUpdate).toHaveBeenCalled();
});
it('should add comment when ctrl+enter is pressed', () => {
jest.spyOn(vm, 'handleUpdate');
vm.$el.querySelector('textarea').value = 'Foo';
vm.$el.querySelector('textarea').dispatchEvent(keyboardDownEvent(13, false, true));
expect(vm.handleUpdate).toHaveBeenCalled();
});
});
});
describe('with batch comments', () => {
beforeEach(() => {
return store.dispatch('batchComments/enableBatchComments').then(vm.$nextTick);
});
it('should be possible to cancel', () => {
jest.spyOn(vm, 'cancelHandler');
return vm.$nextTick().then(() => {
const cancelButton = vm.$el.querySelector('[data-testid="cancelBatchCommentsEnabled"]');
cancelButton.click();
expect(vm.cancelHandler).toHaveBeenCalledWith(true);
});
});
it('shows resolve checkbox', () => {
expect(
vm.$el.querySelector('[data-qa-selector="resolve_review_discussion_checkbox"]'),
).not.toBe(null);
});
it('hides actions for commits', () => {
vm.discussion.for_commit = true;
return vm.$nextTick(() => {
expect(vm.$el.querySelector('.note-form-actions').textContent).not.toContain(
'Start a review',
);
});
});
describe('on enter', () => {
it('should start review or add to review when cmd+enter is pressed', () => {
jest.spyOn(vm, 'handleAddToReview');
vm.$el.querySelector('textarea').value = 'Foo';
vm.$el.querySelector('textarea').dispatchEvent(keyboardDownEvent(13, true));
expect(vm.handleAddToReview).toHaveBeenCalled();
});
it('should start review or add to review when ctrl+enter is pressed', () => {
jest.spyOn(vm, 'handleAddToReview');
vm.$el.querySelector('textarea').value = 'Foo';
vm.$el.querySelector('textarea').dispatchEvent(keyboardDownEvent(13, false, true));
expect(vm.handleAddToReview).toHaveBeenCalled();
});
});
});
});
import Vue from 'vue';
import InlineDraftCommentRow from 'ee/batch_comments/components/inline_draft_comment_row.vue';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { createStore } from 'ee/batch_comments/stores';
import '~/behaviors/markdown/render_gfm';
import { createDraft } from '../mock_data';
describe('Batch comments inline draft row component', () => {
let vm;
let Component;
let draft;
beforeAll(() => {
Component = Vue.extend(InlineDraftCommentRow);
});
beforeEach(() => {
const store = createStore();
draft = createDraft();
vm = mountComponentWithStore(Component, { store, props: { draft } });
});
afterEach(() => {
vm.$destroy();
});
it('renders draft', () => {
expect(vm.$el.querySelector('.draft-note-component')).not.toBe(null);
});
});
import Vue from 'vue';
import ParallelDraftCommentRow from 'ee/batch_comments/components/parallel_draft_comment_row.vue';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { createStore } from 'ee/batch_comments/stores';
import '~/behaviors/markdown/render_gfm';
import { createDraft } from '../mock_data';
describe('Batch comments parallel draft row component', () => {
let vm;
let Component;
let draft;
beforeAll(() => {
Component = Vue.extend(ParallelDraftCommentRow);
});
beforeEach(() => {
draft = createDraft();
});
afterEach(() => {
vm.$destroy();
});
['left', 'right'].forEach(side => {
describe(`${side} side of diff`, () => {
beforeEach(() => {
const store = createStore();
vm = createComponentWithStore(Component, store, {
line: { code: '1' },
diffFileContentSha: 'test',
});
spyOnProperty(vm, 'draftForLine').and.returnValue((sha, line, draftSide) => {
if (draftSide === side) return draft;
return {};
});
vm.$mount();
});
it(`it renders draft on ${side} side`, () => {
const sideClass = side === 'left' ? '.old' : '.new';
const oppositeSideClass = side === 'left' ? '.new' : '.old';
expect(vm.$el.querySelector(`.parallel${sideClass} .draft-note-component`)).not.toBe(null);
expect(vm.$el.querySelector(`.parallel${oppositeSideClass} .draft-note-component`)).toBe(
null,
);
});
});
});
});
import Vue from 'vue';
import ReviewBar from 'ee/batch_comments/components/review_bar.vue';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { createStore } from 'ee/batch_comments/stores';
describe('Batch comments review bar component', () => {
let vm;
let Component;
beforeAll(() => {
Component = Vue.extend(ReviewBar);
});
beforeEach(() => {
const store = createStore();
vm = mountComponentWithStore(Component, { store });
spyOn(vm.$store, 'dispatch').and.stub();
});
afterEach(() => {
vm.$destroy();
});
it('hides when no drafts exist', () => {
expect(vm.$el.style.display).toBe('none');
});
describe('with batch comments', () => {
beforeEach(done => {
vm.$store.state.batchComments.drafts.push('comment');
vm.$nextTick(done);
});
it('shows bar', () => {
expect(vm.$el.style.display).not.toBe('none');
});
it('calls discardReview when clicking modal button', done => {
vm.$el.querySelector('.btn.btn-align-content').click();
vm.$nextTick(() => {
const modal = document.querySelector('#discard-draft-review');
modal.querySelector('.btn-danger').click();
expect(vm.$store.dispatch).toHaveBeenCalled();
done();
});
});
it('sets discard button as loading when isDiscarding is true', done => {
vm.$store.state.batchComments.isDiscarding = true;
vm.$nextTick(() => {
expect(vm.$el.querySelector('.btn-align-content').getAttribute('disabled')).toBe(
'disabled',
);
done();
});
});
});
});
// No new code should be added to this file. Instead, modify the
// file this one re-exports from. For more detail about why, see:
// https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/31349
export * from '../../frontend/batch_comments/mock_data';
......@@ -13277,9 +13277,6 @@ msgstr ""
msgid "Mark as resolved"
msgstr ""
msgid "Mark comment as resolved"
msgstr ""
msgid "Mark this issue as a duplicate of another issue"
msgstr ""
......
......@@ -19,27 +19,6 @@ module QA
element :head_mismatch, "The source branch HEAD has recently changed." # rubocop:disable QA/ElementWithPattern
end
view 'ee/app/assets/javascripts/batch_comments/components/publish_button.vue' do
element :submit_review
end
view 'ee/app/assets/javascripts/batch_comments/components/review_bar.vue' do
element :review_bar
element :discard_review
element :modal_delete_pending_comments
end
view 'app/assets/javascripts/notes/components/note_form.vue' do
element :unresolve_review_discussion_checkbox
element :resolve_review_discussion_checkbox
element :start_review
element :comment_now
end
view 'ee/app/assets/javascripts/batch_comments/components/preview_dropdown.vue' do
element :review_preview_toggle
end
view 'ee/app/views/projects/merge_requests/_code_owner_approval_rules.html.haml' do
element :approver
element :approver_list
......@@ -123,49 +102,6 @@ module QA
find_element :approve_button, text: "Revoke approval"
end
def start_review
click_element :start_review
# After clicking the button, wait for it to disappear
# before moving on to the next part of the test
has_no_element? :start_review
end
def comment_now
click_element :comment_now
# After clicking the button, wait for it to disappear
# before moving on to the next part of the test
has_no_element? :comment_now
end
def submit_pending_reviews
within_element :review_bar do
click_element :review_preview_toggle
click_element :submit_review
# After clicking the button, wait for it to disappear
# before moving on to the next part of the test
has_no_element? :submit_review
end
end
def discard_pending_reviews
within_element :review_bar do
click_element :discard_review
end
click_element :modal_delete_pending_comments
end
def resolve_review_discussion
scroll_to_element :start_review
check_element :resolve_review_discussion_checkbox
end
def unresolve_review_discussion
check_element :unresolve_review_discussion_checkbox
end
def expand_license_report
within_element(:license_report_widget) do
click_element(:expand_report_button)
......
......@@ -73,6 +73,70 @@ module QA
element :edit_button
end
view 'app/assets/javascripts/batch_comments/components/publish_button.vue' do
element :submit_review
end
view 'app/assets/javascripts/batch_comments/components/review_bar.vue' do
element :review_bar
element :discard_review
element :modal_delete_pending_comments
end
view 'app/assets/javascripts/notes/components/note_form.vue' do
element :unresolve_review_discussion_checkbox
element :resolve_review_discussion_checkbox
element :start_review
element :comment_now
end
view 'app/assets/javascripts/batch_comments/components/preview_dropdown.vue' do
element :review_preview_toggle
end
def start_review
click_element :start_review
# After clicking the button, wait for it to disappear
# before moving on to the next part of the test
has_no_element? :start_review
end
def comment_now
click_element :comment_now
# After clicking the button, wait for it to disappear
# before moving on to the next part of the test
has_no_element? :comment_now
end
def submit_pending_reviews
within_element :review_bar do
click_element :review_preview_toggle
click_element :submit_review
# After clicking the button, wait for it to disappear
# before moving on to the next part of the test
has_no_element? :submit_review
end
end
def discard_pending_reviews
within_element :review_bar do
click_element :discard_review
end
click_element :modal_delete_pending_comments
end
def resolve_review_discussion
scroll_to_element :start_review
check_element :resolve_review_discussion_checkbox
end
def unresolve_review_discussion
check_element :unresolve_review_discussion_checkbox
end
def add_comment_to_diff(text)
wait_until(sleep_interval: 5) do
has_text?("No newline at end of file")
......
......@@ -18,23 +18,9 @@ describe 'Merge request > Batch comments', :js do
sign_in(user)
end
context 'Feature is disabled' do
before do
stub_feature_flags(batch_comments: false)
stub_feature_flags(diffs_batch_load: false)
visit_diffs
end
it 'does not have review bar' do
expect(page).not_to have_css('.review-bar-component')
end
end
context 'Feature is enabled' do
before do
stub_feature_flags(diffs_batch_load: false)
stub_licensed_features(batch_comments: true)
visit_diffs
end
......
......@@ -27,7 +27,7 @@ describe 'User comments on a diff', :js do
page.within('.js-discussion-note-form') do
fill_in('note_note', with: 'Line is wrong')
click_button('Comment')
click_button('Add comment now')
end
page.within('.diff-files-holder > div:nth-child(3)') do
......@@ -46,7 +46,7 @@ describe 'User comments on a diff', :js do
page.within('.js-discussion-note-form') do
fill_in('note_note', with: 'Line is correct')
click_button('Comment')
click_button('Add comment now')
end
wait_for_requests
......@@ -59,7 +59,7 @@ describe 'User comments on a diff', :js do
page.within('.js-discussion-note-form') do
fill_in('note_note', with: 'Line is wrong')
click_button('Comment')
click_button('Add comment now')
end
wait_for_requests
......@@ -120,7 +120,7 @@ describe 'User comments on a diff', :js do
page.within('.js-discussion-note-form') do
fill_in(:note_note, with: 'Line is wrong')
click_button('Comment')
click_button('Add comment now')
end
page.within('.diff-file:nth-of-type(5) .discussion .note') do
......@@ -146,7 +146,7 @@ describe 'User comments on a diff', :js do
page.within('.js-discussion-note-form') do
fill_in(:note_note, with: 'Line is wrong')
click_button('Comment')
click_button('Add comment now')
end
page.within('.notes-tab .badge') do
......
......@@ -22,14 +22,14 @@ describe 'Batch diffs', :js do
click_diff_line(find('.diff-file.file-holder:first-of-type tr.line_holder.new:first-of-type'))
page.within('.js-discussion-note-form') do
fill_in('note_note', with: 'First Line Comment')
click_button('Comment')
click_button('Add comment now')
end
# Add discussion to first line of last file
click_diff_line(find('.diff-file.file-holder:last-of-type tr.line_holder.new:first-of-type'))
page.within('.js-discussion-note-form') do
fill_in('note_note', with: 'Last Line Comment')
click_button('Comment')
click_button('Add comment now')
end
wait_for_requests
......
......@@ -225,7 +225,7 @@ describe 'Merge request > User posts diff notes', :js do
def should_allow_commenting(line_holder, diff_side = nil, asset_form_reset: true)
write_comment_on_line(line_holder, diff_side)
click_button 'Comment'
click_button 'Add comment now'
wait_for_requests
......
......@@ -105,7 +105,7 @@ describe 'Merge request > User posts notes', :js do
page.within('.discussion-reply-holder') do
fill_in 'note[note]', with: 'A reply'
click_button 'Comment'
click_button 'Add comment now'
wait_for_requests
expect(page).to have_content('Your comment could not be submitted because discussion to reply to cannot be found')
end
......
......@@ -146,17 +146,16 @@ describe 'Merge request > User resolves diff notes and threads', :js do
describe 'reply form' do
before do
click_button 'Toggle thread'
page.within '.diff-content' do
click_button 'Reply...'
end
end
it 'allows user to comment' do
page.within '.diff-content' do
click_button 'Reply...'
find(".js-unresolve-checkbox").set false
find('.js-note-text').set 'testing'
click_button 'Comment'
click_button 'Add comment now'
wait_for_requests
end
......@@ -181,9 +180,11 @@ describe 'Merge request > User resolves diff notes and threads', :js do
it 'allows user to comment & unresolve thread' do
page.within '.diff-content' do
click_button 'Reply...'
find('.js-note-text').set 'testing'
click_button 'Comment & unresolve thread'
click_button 'Add comment now'
wait_for_requests
end
......@@ -197,8 +198,6 @@ describe 'Merge request > User resolves diff notes and threads', :js do
it 'allows user to resolve from reply form without a comment' do
page.within '.diff-content' do
click_button 'Reply...'
click_button 'Resolve thread'
end
......@@ -214,7 +213,9 @@ describe 'Merge request > User resolves diff notes and threads', :js do
find('.js-note-text').set 'testing'
click_button 'Comment & resolve thread'
find('.js-resolve-checkbox').set(true)
click_button 'Add comment now'
end
page.within '.line-resolve-all-container' do
......@@ -445,7 +446,9 @@ describe 'Merge request > User resolves diff notes and threads', :js do
find('.js-note-text').set 'testing'
click_button 'Comment & resolve thread'
find('.js-resolve-checkbox').set(true)
click_button 'Add comment now'
end
page.within '.line-resolve-all-container' do
......@@ -462,7 +465,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
find('.js-note-text').set 'testing'
click_button 'Comment & unresolve thread'
click_button 'Add comment now'
end
page.within '.line-resolve-all-container' do
......
......@@ -11,8 +11,6 @@ describe 'Merge request > image review', :js do
let(:merge_request) { create(:merge_request_with_diffs, :with_image_diffs, source_project: project, author: user) }
before do
stub_licensed_features(batch_comments: true)
sign_in(user)
allow_any_instance_of(DiffHelper).to receive(:diff_file_blob_raw_url).and_return('/apple-touch-icon.png')
......
......@@ -42,7 +42,7 @@ describe 'Merge request > User sees avatars on diff notes', :js do
page.within('.js-discussion-note-form') do
find('.note-textarea').native.send_keys('Test comment')
click_button 'Comment'
click_button 'Add comment now'
end
expect(page).to have_content('Test comment')
......@@ -137,7 +137,7 @@ describe 'Merge request > User sees avatars on diff notes', :js do
page.within '.js-discussion-note-form' do
find('.js-note-text').native.send_keys('Test')
click_button 'Comment'
click_button 'Add comment now'
wait_for_requests
end
......@@ -155,7 +155,7 @@ describe 'Merge request > User sees avatars on diff notes', :js do
page.within '.js-discussion-note-form' do
find('.js-note-text').native.send_keys('Test')
find('.js-comment-button').click
click_button 'Add comment now'
wait_for_requests
end
......
......@@ -34,7 +34,7 @@ describe 'Merge request > User sees versions', :js do
page.within("form[data-line-code='#{line_code}']") do
fill_in "note[note]", with: comment
find(".js-comment-button").click
click_button('Add comment now')
end
wait_for_requests
......
......@@ -49,7 +49,7 @@ describe 'User comments on a diff', :js do
page.within('.js-discussion-note-form') do
fill_in('note_note', with: "```suggestion\n# change to a comment\n```")
click_button('Comment')
click_button('Add comment now')
end
wait_for_requests
......@@ -77,7 +77,7 @@ describe 'User comments on a diff', :js do
page.within('.js-discussion-note-form') do
fill_in('note_note', with: "```suggestion\n# change to a comment\n```")
click_button('Comment')
click_button('Add comment now')
end
wait_for_requests
......@@ -119,7 +119,7 @@ describe 'User comments on a diff', :js do
page.within('.js-discussion-note-form') do
fill_in('note_note', with: "```suggestion\n# change to a comment\n```")
click_button('Comment')
click_button('Add comment now')
wait_for_requests
end
......@@ -127,7 +127,7 @@ describe 'User comments on a diff', :js do
page.within('.js-discussion-note-form') do
fill_in('note_note', with: "```suggestion\n# 2nd change to a comment\n```")
click_button('Comment')
click_button('Add comment now')
wait_for_requests
end
......@@ -158,7 +158,7 @@ describe 'User comments on a diff', :js do
page.within('.js-discussion-note-form') do
fill_in('note_note', with: "```suggestion\n# change to a comment\n```\n```suggestion:-2\n# or that\n# heh\n```")
click_button('Comment')
click_button('Add comment now')
end
wait_for_requests
......@@ -201,7 +201,7 @@ describe 'User comments on a diff', :js do
page.within('.js-discussion-note-form') do
fill_in('note_note', with: "```suggestion:-3+5\n# change to a\n# comment\n# with\n# broken\n# lines\n```")
click_button('Comment')
click_button('Add comment now')
end
wait_for_requests
......
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import DiffFileDrafts from 'ee/batch_comments/components/diff_file_drafts.vue';
import DraftNote from 'ee/batch_comments/components/draft_note.vue';
import DiffFileDrafts from '~/batch_comments/components/diff_file_drafts.vue';
import DraftNote from '~/batch_comments/components/draft_note.vue';
const localVue = createLocalVue();
......
import { shallowMount, createLocalVue } from '@vue/test-utils';
import DraftNote from 'ee/batch_comments/components/draft_note.vue';
import { createStore } from 'ee/batch_comments/stores';
import DraftNote from '~/batch_comments/components/draft_note.vue';
import { createStore } from '~/batch_comments/stores';
import NoteableNote from '~/notes/components/noteable_note.vue';
import '~/behaviors/markdown/render_gfm';
import { createDraft } from '../mock_data';
......
import Vue from 'vue';
import DraftsCount from 'ee/batch_comments/components/drafts_count.vue';
import DraftsCount from '~/batch_comments/components/drafts_count.vue';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import { createStore } from 'ee/batch_comments/stores';
import { createStore } from '~/batch_comments/stores';
describe('Batch comments drafts count component', () => {
let vm;
......
import Vue from 'vue';
import PreviewItem from 'ee/batch_comments/components/preview_item.vue';
import PreviewItem from '~/batch_comments/components/preview_item.vue';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import { createStore } from 'ee/batch_comments/stores';
import { createStore } from '~/batch_comments/stores';
import diffsModule from '~/diffs/store/modules';
import notesModule from '~/notes/stores/modules';
import '~/behaviors/markdown/render_gfm';
......
import Vue from 'vue';
import PublishButton from 'ee/batch_comments/components/publish_button.vue';
import PublishButton from '~/batch_comments/components/publish_button.vue';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import { createStore } from 'ee/batch_comments/stores';
import { createStore } from '~/batch_comments/stores';
describe('Batch comments publish button component', () => {
let vm;
......
import Vue from 'vue';
import PreviewDropdown from 'ee/batch_comments/components/preview_dropdown.vue';
import PreviewDropdown from '~/batch_comments/components/preview_dropdown.vue';
import { mountComponentWithStore } from 'helpers/vue_mount_component_helper';
import { createStore } from 'ee/mr_notes/stores';
import { createStore } from '~/mr_notes/stores';
import '~/behaviors/markdown/render_gfm';
import { createDraft } from '../mock_data';
......
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import * as actions from 'ee/batch_comments/stores/modules/batch_comments/actions';
import * as actions from '~/batch_comments/stores/modules/batch_comments/actions';
import axios from '~/lib/utils/axios_utils';
describe('Batch comments store actions', () => {
......@@ -16,19 +16,6 @@ describe('Batch comments store actions', () => {
mock.restore();
});
describe('enableBatchComments', () => {
it('commits ENABLE_BATCH_COMMENTS', done => {
testAction(
actions.enableBatchComments,
null,
null,
[{ type: 'ENABLE_BATCH_COMMENTS' }],
[],
done,
);
});
});
describe('saveDraft', () => {
it('dispatches saveNote on root', () => {
const dispatch = jest.fn();
......
import * as getters from 'ee/batch_comments/stores/modules/batch_comments/getters';
import * as getters from '~/batch_comments/stores/modules/batch_comments/getters';
describe('Batch comments store getters', () => {
describe('draftsForFile', () => {
......
import createState from 'ee/batch_comments/stores/modules/batch_comments/state';
import mutations from 'ee/batch_comments/stores/modules/batch_comments/mutations';
import * as types from 'ee/batch_comments/stores/modules/batch_comments/mutation_types';
import createState from '~/batch_comments/stores/modules/batch_comments/state';
import mutations from '~/batch_comments/stores/modules/batch_comments/mutations';
import * as types from '~/batch_comments/stores/modules/batch_comments/mutation_types';
describe('Batch comments mutations', () => {
let state;
......@@ -9,14 +9,6 @@ describe('Batch comments mutations', () => {
state = createState();
});
describe(types.ENABLE_BATCH_COMMENTS, () => {
it('sets withBatchComments to true', () => {
mutations[types.ENABLE_BATCH_COMMENTS](state);
expect(state.withBatchComments).toBe(true);
});
});
describe(types.ADD_NEW_DRAFT, () => {
it('adds processed object into drafts array', () => {
const draft = { id: 1, note: 'test' };
......
import Vue from 'vue';
import { createStore } from 'ee_else_ce/mr_notes/stores';
import { createStore } from '~/mr_notes/stores';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import { mockTracking, triggerEvent } from 'helpers/tracking_helper';
import DiffFileComponent from '~/diffs/components/diff_file.vue';
......
import Vue from 'vue';
import '~/behaviors/markdown/render_gfm';
import { createStore } from 'ee_else_ce/mr_notes/stores';
import { createStore } from '~/mr_notes/stores';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import InlineDiffView from '~/diffs/components/inline_diff_view.vue';
import diffFileMockData from '../mock_data/diff_file';
......
import Vue from 'vue';
import { createStore } from 'ee_else_ce/mr_notes/stores';
import { createStore } from '~/mr_notes/stores';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper';
import ParallelDiffView from '~/diffs/components/parallel_diff_view.vue';
import * as constants from '~/diffs/constants';
......
......@@ -157,4 +157,19 @@ describe('noteActions', () => {
expect(replyButton.exists()).toBe(false);
});
});
describe('Draft notes', () => {
beforeEach(() => {
store.dispatch('setUserData', userDataMock);
wrapper = shallowMountNoteActions({ ...props, canResolve: true, isDraft: true });
});
it('should render the right resolve button title', () => {
const resolveButton = wrapper.find({ ref: 'resolveButton' });
expect(resolveButton.exists()).toBe(true);
expect(resolveButton.attributes('title')).toBe('Thread stays unresolved');
});
});
});
import { shallowMount, createLocalVue } from '@vue/test-utils';
import createStore from '~/notes/stores';
import NoteForm from '~/notes/components/note_form.vue';
import batchComments from '~/batch_comments/stores/modules/batch_comments';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import { noteableDataMock, notesDataMock } from '../mock_data';
import { noteableDataMock, notesDataMock, discussionMock } from '../mock_data';
import { getDraft, updateDraft } from '~/lib/utils/autosave';
......@@ -245,4 +246,55 @@ describe('issue_note_form component', () => {
expect(updateDraft).toHaveBeenCalledWith(dummyAutosaveKey, dummyContent);
});
});
describe('with batch comments', () => {
beforeEach(() => {
store.registerModule('batchComments', batchComments());
wrapper = createComponentWrapper();
wrapper.setProps({
...props,
noteId: '',
discussion: { ...discussionMock, for_commit: false },
});
});
it('should be possible to cancel', () => {
jest.spyOn(wrapper.vm, 'cancelHandler');
return wrapper.vm.$nextTick().then(() => {
const cancelButton = wrapper.find('[data-testid="cancelBatchCommentsEnabled"]');
cancelButton.trigger('click');
expect(wrapper.vm.cancelHandler).toHaveBeenCalledWith(true);
});
});
it('shows resolve checkbox', () => {
expect(wrapper.find('.js-resolve-checkbox').exists()).toBe(true);
});
it('hides actions for commits', () => {
wrapper.setProps({ discussion: { for_commit: true } });
return wrapper.vm.$nextTick(() => {
expect(wrapper.find('.note-form-actions').text()).not.toContain('Start a review');
});
});
describe('on enter', () => {
it('should start review or add to review when cmd+enter is pressed', () => {
const textarea = wrapper.find('textarea');
jest.spyOn(wrapper.vm, 'handleAddToReview');
textarea.setValue('Foo');
textarea.trigger('keydown.enter', { metaKey: true });
return wrapper.vm.$nextTick(() => {
expect(wrapper.vm.handleAddToReview).toHaveBeenCalled();
});
});
});
});
});
......@@ -6,7 +6,7 @@ RSpec.shared_examples 'comment on merge request file' do
page.within('.js-discussion-note-form') do
fill_in(:note_note, with: 'Line is wrong')
click_button('Comment')
find('.js-comment-button').click
end
wait_for_requests
......
......@@ -15,7 +15,7 @@ RSpec.shared_examples 'thread comments' do |resource_name|
find("#{form_selector} .note-textarea").send_keys(comment)
click_button 'Comment'
find('.js-comment-button').click
expect(page).to have_content(comment)
......@@ -146,7 +146,7 @@ RSpec.shared_examples 'thread comments' do |resource_name|
find("#{comments_selector} .js-vue-discussion-reply").click
find("#{comments_selector} .note-textarea").send_keys(text)
click_button "Comment"
find("#{comments_selector} .js-comment-button").click
wait_for_requests
end
......
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