Commit fdd25500 authored by Phil Hughes's avatar Phil Hughes

Move frontend batch comments code into core

This moves all the frontend batch comments related code
out of the EE folder and into core

Part of https://gitlab.com/gitlab-org/gitlab/-/issues/28154
parent 1e7b5fcb
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';
......@@ -13259,9 +13259,6 @@ msgstr ""
msgid "Mark as resolved"
msgstr ""
msgid "Mark comment as resolved"
msgstr ""
msgid "Mark this issue as a duplicate of another issue"
msgstr ""
......
......@@ -17,27 +17,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
......@@ -118,49 +97,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