Commit 26e6adaf authored by Phil Hughes's avatar Phil Hughes

Fixed start review not working on images

Closes https://gitlab.com/gitlab-org/gitlab-ee/issues/10072
parent 91876d57
<script> <script>
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import diffLineNoteFormMixin from 'ee_else_ce/notes/mixins/diff_line_note_form';
import draftCommentsMixin from 'ee_else_ce/diffs/mixins/draft_comments';
import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue'; import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
import NotDiffableViewer from '~/vue_shared/components/diff_viewer/viewers/not_diffable.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 NoPreviewViewer from '~/vue_shared/components/diff_viewer/viewers/no_preview.vue';
...@@ -22,7 +24,9 @@ export default { ...@@ -22,7 +24,9 @@ export default {
ImageDiffOverlay, ImageDiffOverlay,
NotDiffableViewer, NotDiffableViewer,
NoPreviewViewer, NoPreviewViewer,
DiffFileDrafts: () => import('ee_component/batch_comments/components/diff_file_drafts.vue'),
}, },
mixins: [diffLineNoteFormMixin, draftCommentsMixin],
props: { props: {
diffFile: { diffFile: {
type: Object, type: Object,
...@@ -58,10 +62,13 @@ export default { ...@@ -58,10 +62,13 @@ export default {
return this.diffViewerMode === diffViewerModes.not_diffable; return this.diffViewerMode === diffViewerModes.not_diffable;
}, },
diffFileCommentForm() { diffFileCommentForm() {
return this.getCommentFormForDiffFile(this.diffFile.file_hash); return this.getCommentFormForDiffFile(this.diffFileHash);
}, },
showNotesContainer() { showNotesContainer() {
return this.diffFile.discussions.length || this.diffFileCommentForm; return this.imageDiscussions.length || this.diffFileCommentForm;
},
diffFileHash() {
return this.diffFile.file_hash;
}, },
}, },
methods: { methods: {
...@@ -112,15 +119,15 @@ export default { ...@@ -112,15 +119,15 @@ export default {
:new-sha="diffFile.diff_refs.head_sha" :new-sha="diffFile.diff_refs.head_sha"
:old-path="diffFile.old_path" :old-path="diffFile.old_path"
:old-sha="diffFile.diff_refs.base_sha" :old-sha="diffFile.diff_refs.base_sha"
:file-hash="diffFile.file_hash" :file-hash="diffFileHash"
:project-path="projectPath" :project-path="projectPath"
:a-mode="diffFile.a_mode" :a-mode="diffFile.a_mode"
:b-mode="diffFile.b_mode" :b-mode="diffFile.b_mode"
> >
<image-diff-overlay <image-diff-overlay
slot="image-overlay" slot="image-overlay"
:discussions="diffFile.discussions" :discussions="imageDiscussions"
:file-hash="diffFile.file_hash" :file-hash="diffFileHash"
:can-comment="getNoteableData.current_user.can_create_note" :can-comment="getNoteableData.current_user.can_create_note"
/> />
<div v-if="showNotesContainer" class="note-container"> <div v-if="showNotesContainer" class="note-container">
...@@ -131,14 +138,16 @@ export default { ...@@ -131,14 +138,16 @@ export default {
:should-collapse-discussions="true" :should-collapse-discussions="true"
:render-avatar-badge="true" :render-avatar-badge="true"
/> />
<diff-file-drafts :file-hash="diffFileHash" class="diff-file-discussions" />
<note-form <note-form
v-if="diffFileCommentForm" v-if="diffFileCommentForm"
ref="noteForm" ref="noteForm"
:is-editing="false" :is-editing="false"
:save-button-title="__('Comment')" :save-button-title="__('Comment')"
class="diff-comment-form new-note discussion-form discussion-form-container" class="diff-comment-form new-note discussion-form discussion-form-container"
@handleFormUpdateAddToReview="addToReview"
@handleFormUpdate="handleSaveNote" @handleFormUpdate="handleSaveNote"
@cancelForm="closeDiffFileCommentForm(diffFile.file_hash)" @cancelForm="closeDiffFileCommentForm(diffFileHash)"
/> />
</div> </div>
</diff-viewer> </diff-viewer>
......
<script> <script>
import { mapActions, mapGetters } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import _ from 'underscore'; import _ from 'underscore';
import imageDiffMixin from 'ee_else_ce/diffs/mixins/image_diff';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
export default { export default {
...@@ -8,6 +9,7 @@ export default { ...@@ -8,6 +9,7 @@ export default {
components: { components: {
Icon, Icon,
}, },
mixins: [imageDiffMixin],
props: { props: {
discussions: { discussions: {
type: [Array, Object], type: [Array, Object],
...@@ -48,7 +50,6 @@ export default { ...@@ -48,7 +50,6 @@ export default {
}, },
}, },
methods: { methods: {
...mapActions(['toggleDiscussion']),
...mapActions('diffs', ['openDiffFileCommentForm']), ...mapActions('diffs', ['openDiffFileCommentForm']),
getImageDimensions() { getImageDimensions() {
return { return {
...@@ -105,15 +106,15 @@ export default { ...@@ -105,15 +106,15 @@ export default {
v-for="(discussion, index) in allDiscussions" v-for="(discussion, index) in allDiscussions"
:key="discussion.id" :key="discussion.id"
:style="getPosition(discussion)" :style="getPosition(discussion)"
:class="badgeClass" :class="[badgeClass, { 'is-draft': discussion.isDraft }]"
:disabled="!shouldToggleDiscussion" :disabled="!shouldToggleDiscussion"
class="js-image-badge" class="js-image-badge"
type="button" type="button"
@click="toggleDiscussion({ discussionId: discussion.id })" @click="clickedToggle(discussion)"
> >
<icon v-if="showCommentIcon" name="image-comment-dark" /> <icon v-if="showCommentIcon" name="image-comment-dark" />
<template v-else> <template v-else>
{{ index + 1 }} {{ toggleText(discussion, index) }}
</template> </template>
</button> </button>
<button <button
......
...@@ -3,5 +3,8 @@ export default { ...@@ -3,5 +3,8 @@ export default {
shouldRenderDraftRow: () => () => false, shouldRenderDraftRow: () => () => false,
shouldRenderParallelDraftRow: () => () => false, shouldRenderParallelDraftRow: () => () => false,
draftForLine: () => () => ({}), draftForLine: () => () => ({}),
imageDiscussions() {
return this.diffFile.discussions;
},
}, },
}; };
import { mapActions } from 'vuex';
export default {
methods: {
...mapActions(['toggleDiscussion']),
clickedToggle(discussion) {
this.toggleDiscussion({ discussionId: discussion.id });
},
toggleText(discussion, index) {
return index + 1;
},
},
};
...@@ -160,7 +160,9 @@ export default { ...@@ -160,7 +160,9 @@ export default {
} }
if (!file.parallel_diff_lines || !file.highlighted_diff_lines) { if (!file.parallel_diff_lines || !file.highlighted_diff_lines) {
file.discussions = (file.discussions || []).concat(discussion); file.discussions = (file.discussions || [])
.filter(d => d.id !== discussion.id)
.concat(discussion);
} }
return file; return file;
......
<script>
import { mapGetters } from 'vuex';
import imageDiff from '~/diffs/mixins/image_diff';
import DraftNote from './draft_note.vue';
export default {
components: {
DraftNote,
},
mixins: [imageDiff],
props: {
fileHash: {
type: String,
required: true,
},
},
computed: {
...mapGetters('batchComments', ['draftsForFile']),
drafts() {
return this.draftsForFile(this.fileHash);
},
},
};
</script>
<template>
<div>
<div
v-for="(draft, index) in drafts"
:key="draft.id"
class="discussion-notes diff-discussions position-relative"
>
<div class="notes">
<span class="d-block btn-transparent badge badge-pill is-draft js-diff-notes-index">
{{ toggleText(draft, index) }}
</span>
<draft-note :draft="draft" />
</div>
</div>
</div>
</template>
<script> <script>
import { mapActions, mapGetters } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import { IMAGE_DIFF_POSITION_TYPE } from '~/diffs/constants';
import { sprintf, __ } from '~/locale'; import { sprintf, __ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import resolvedStatusMixin from '../mixins/resolved_status'; import resolvedStatusMixin from '../mixins/resolved_status';
...@@ -43,6 +44,10 @@ export default { ...@@ -43,6 +44,10 @@ export default {
}); });
}, },
linePosition() { linePosition() {
if (this.draft.position && this.draft.position.position_type === IMAGE_DIFF_POSITION_TYPE) {
return `${this.draft.position.x}x ${this.draft.position.y}y`;
}
const position = this.discussion ? this.discussion.position : this.draft.position; const position = this.discussion ? this.discussion.position : this.draft.position;
return position.new_line || position.old_line; return position.new_line || position.old_line;
...@@ -78,7 +83,7 @@ export default { ...@@ -78,7 +83,7 @@ export default {
> >
<span class="review-preview-item-header"> <span class="review-preview-item-header">
<icon class="append-right-8 flex-shrink-0" :name="iconName" /> <icon class="append-right-8 flex-shrink-0" :name="iconName" />
<span class="bold"> <span class="bold text-nowrap">
<span class="review-preview-item-header-text block-truncated"> {{ titleText }} </span> <span class="review-preview-item-header-text block-truncated"> {{ titleText }} </span>
<template v-if="showLinePosition"> <template v-if="showLinePosition">
:{{ linePosition }} :{{ linePosition }}
......
...@@ -61,6 +61,9 @@ export const draftForLine = (state, getters) => (diffFileSha, line, side = null) ...@@ -61,6 +61,9 @@ export const draftForLine = (state, getters) => (diffFileSha, line, side = null)
return {}; return {};
}; };
export const draftsForFile = state => diffFileSha =>
state.drafts.filter(draft => draft.file_hash === diffFileSha);
export const isPublishingDraft = state => draftId => export const isPublishingDraft = state => draftId =>
state.currentlyPublishingDrafts.indexOf(draftId) !== -1; state.currentlyPublishingDrafts.indexOf(draftId) !== -1;
......
...@@ -6,6 +6,10 @@ export default { ...@@ -6,6 +6,10 @@ export default {
'shouldRenderDraftRow', 'shouldRenderDraftRow',
'shouldRenderParallelDraftRow', 'shouldRenderParallelDraftRow',
'draftForLine', 'draftForLine',
'draftsForFile',
]), ]),
imageDiscussions() {
return this.diffFile.discussions.concat(this.draftsForFile(this.diffFile.file_hash));
},
}, },
}; };
import { mapActions } from 'vuex';
export default {
computed: {
diffFileDiscussions() {
return this.allDiscussions.filter(d => !d.isDraft);
},
},
methods: {
...mapActions(['toggleDiscussion']),
clickedToggle(discussion) {
if (!discussion.isDraft) {
this.toggleDiscussion({ discussionId: discussion.id });
}
},
toggleText(discussion, index) {
const count = index + 1;
return discussion.isDraft ? count - this.diffFileDiscussions.length : count;
},
},
};
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import { TEXT_DIFF_POSITION_TYPE, IMAGE_DIFF_POSITION_TYPE } from '~/diffs/constants';
import { getDraftReplyFormData, getDraftFormData } from 'ee/batch_comments/utils'; import { getDraftReplyFormData, getDraftFormData } from 'ee/batch_comments/utils';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
...@@ -6,6 +7,7 @@ import { s__ } from '~/locale'; ...@@ -6,6 +7,7 @@ import { s__ } from '~/locale';
export default { export default {
computed: { computed: {
...mapState({ ...mapState({
noteableData: state => state.notes.noteableData,
notesData: state => state.notes.notesData, notesData: state => state.notes.notesData,
withBatchComments: state => state.batchComments && state.batchComments.withBatchComments, withBatchComments: state => state.batchComments && state.batchComments.withBatchComments,
}), }),
...@@ -41,6 +43,9 @@ export default { ...@@ -41,6 +43,9 @@ export default {
}); });
}, },
addToReview(note) { addToReview(note) {
const positionType = this.diffFileCommentForm
? IMAGE_DIFF_POSITION_TYPE
: TEXT_DIFF_POSITION_TYPE;
const selectedDiffFile = this.getDiffFileByHash(this.diffFileHash); const selectedDiffFile = this.getDiffFileByHash(this.diffFileHash);
const postData = getDraftFormData({ const postData = getDraftFormData({
note, note,
...@@ -51,11 +56,17 @@ export default { ...@@ -51,11 +56,17 @@ export default {
diffViewType: this.diffViewType, diffViewType: this.diffViewType,
diffFile: selectedDiffFile, diffFile: selectedDiffFile,
linePosition: this.position, linePosition: this.position,
positionType,
...this.diffFileCommentForm,
}); });
return this.saveDraft(postData) return this.saveDraft(postData)
.then(() => { .then(() => {
this.handleClearForm(this.line.line_code); if (positionType === IMAGE_DIFF_POSITION_TYPE) {
this.closeDiffFileCommentForm(this.diffFileHash);
} else {
this.handleClearForm(this.line.line_code);
}
}) })
.catch(() => { .catch(() => {
createFlash(s__('MergeRequests|An error occurred while saving the draft comment.')); createFlash(s__('MergeRequests|An error occurred while saving the draft comment.'));
......
...@@ -81,3 +81,10 @@ button[disabled] { ...@@ -81,3 +81,10 @@ button[disabled] {
} }
} }
} }
.frame,
.diff-discussions {
.badge.is-draft {
background-color: $orange-600;
}
}
---
title: Fixed starting a review on images
merge_request:
author:
type: fixed
require 'rails_helper'
describe 'Merge request > image review', :js do
include MergeRequestDiffHelpers
include RepoHelpers
let(:user) { project.owner }
let(:project) { create(:project, :repository) }
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')
allow_any_instance_of(DiffHelper).to receive(:diff_file_old_blob_raw_url).and_return('/favicon.png')
visit diffs_project_merge_request_path(merge_request.project, merge_request)
wait_for_requests
end
it 'leaves review' do
find('.js-add-image-diff-note-button', match: :first).click
find('.diff-content .note-textarea').native.send_keys('image diff test comment')
click_button('Start a review')
wait_for_requests
page.within(find('.draft-note-component')) do
expect(page).to have_content('image diff test comment')
end
end
end
import * as getters from 'ee/batch_comments/stores/modules/batch_comments/getters';
describe('Batch comments store getters', () => {
describe('draftsForFile', () => {
it('returns drafts for a file hash', () => {
const state = {
drafts: [
{
file_hash: 'filehash',
comment: 'testing 123',
},
{
file_hash: 'filehash2',
comment: 'testing 1234',
},
],
};
expect(getters.draftsForFile(state)('filehash')).toEqual([
{
file_hash: 'filehash',
comment: 'testing 123',
},
]);
});
});
});
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';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('Batch comments diff file drafts component', () => {
let vm;
function factory() {
const store = new Vuex.Store({
modules: {
batchComments: {
namespaced: true,
getters: {
draftsForFile: () => () => [{ id: 1 }, { id: 2 }],
},
},
},
});
vm = shallowMount(DiffFileDrafts, { store, localVue, propsData: { fileHash: 'filehash' } });
}
afterEach(() => {
vm.destroy();
});
it('renders list of draft notes', () => {
factory();
expect(vm.findAll(DraftNote).length).toEqual(2);
});
it('renders index of draft note', () => {
factory();
expect(vm.findAll('.js-diff-notes-index').length).toEqual(2);
expect(
vm
.findAll('.js-diff-notes-index')
.at(0)
.text(),
).toEqual('1');
expect(
vm
.findAll('.js-diff-notes-index')
.at(1)
.text(),
).toEqual('2');
});
});
...@@ -87,6 +87,16 @@ describe('Batch comments draft preview item component', () => { ...@@ -87,6 +87,16 @@ describe('Batch comments draft preview item component', () => {
expect(vm.$el.querySelector('.bold').textContent).toContain(':2'); expect(vm.$el.querySelector('.bold').textContent).toContain(':2');
}); });
it('renders image position', () => {
createComponent(false, {
file_path: 'index.js',
file_hash: 'abc',
position: { position_type: 'image', x: 10, y: 20 },
});
expect(vm.$el.querySelector('.bold').textContent).toContain('10x 20y');
});
}); });
describe('for discussion', () => { describe('for discussion', () => {
......
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