Commit f702b39a authored by Phil Hughes's avatar Phil Hughes

Merge branch 'issue_51323' into 'master'

Add 'only history' option to notes filter

Closes #51323

See merge request gitlab-org/gitlab-ce!22544
parents 90473e06 b4d005eb
<script> <script>
import $ from 'jquery'; import $ from 'jquery';
import Icon from '~/vue_shared/components/icon.vue';
import { mapGetters, mapActions } from 'vuex'; import { mapGetters, mapActions } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
import { DISCUSSION_FILTERS_DEFAULT_VALUE, HISTORY_ONLY_FILTER_VALUE } from '../constants';
export default { export default {
components: { components: {
...@@ -12,14 +13,17 @@ export default { ...@@ -12,14 +13,17 @@ export default {
type: Array, type: Array,
required: true, required: true,
}, },
defaultValue: { selectedValue: {
type: Number, type: Number,
default: null, default: null,
required: false, required: false,
}, },
}, },
data() { data() {
return { currentValue: this.defaultValue }; return {
currentValue: this.selectedValue,
defaultValue: DISCUSSION_FILTERS_DEFAULT_VALUE,
};
}, },
computed: { computed: {
...mapGetters(['getNotesDataByProp']), ...mapGetters(['getNotesDataByProp']),
...@@ -28,8 +32,11 @@ export default { ...@@ -28,8 +32,11 @@ export default {
return this.filters.find(filter => filter.value === this.currentValue); return this.filters.find(filter => filter.value === this.currentValue);
}, },
}, },
mounted() {
this.toggleCommentsForm();
},
methods: { methods: {
...mapActions(['filterDiscussion']), ...mapActions(['filterDiscussion', 'setCommentsDisabled']),
selectFilter(value) { selectFilter(value) {
const filter = parseInt(value, 10); const filter = parseInt(value, 10);
...@@ -39,6 +46,10 @@ export default { ...@@ -39,6 +46,10 @@ export default {
if (filter === this.currentValue) return; if (filter === this.currentValue) return;
this.currentValue = filter; this.currentValue = filter;
this.filterDiscussion({ path: this.getNotesDataByProp('discussionsPath'), filter }); this.filterDiscussion({ path: this.getNotesDataByProp('discussionsPath'), filter });
this.toggleCommentsForm();
},
toggleCommentsForm() {
this.setCommentsDisabled(this.currentValue === HISTORY_ONLY_FILTER_VALUE);
}, },
}, },
}; };
...@@ -73,6 +84,10 @@ export default { ...@@ -73,6 +84,10 @@ export default {
> >
{{ filter.title }} {{ filter.title }}
</button> </button>
<div
v-if="filter.value === defaultValue"
class="dropdown-divider"
></div>
</li> </li>
</ul> </ul>
</div> </div>
......
...@@ -60,6 +60,7 @@ export default { ...@@ -60,6 +60,7 @@ export default {
'getNotesDataByProp', 'getNotesDataByProp',
'discussionCount', 'discussionCount',
'isLoading', 'isLoading',
'commentsDisabled',
]), ]),
noteableType() { noteableType() {
return this.noteableData.noteableType; return this.noteableData.noteableType;
...@@ -206,6 +207,7 @@ export default { ...@@ -206,6 +207,7 @@ export default {
</ul> </ul>
<comment-form <comment-form
v-if="!commentsDisabled"
:noteable-type="noteableType" :noteable-type="noteableType"
:markdown-version="markdownVersion" :markdown-version="markdownVersion"
/> />
......
...@@ -15,6 +15,8 @@ export const MERGE_REQUEST_NOTEABLE_TYPE = 'MergeRequest'; ...@@ -15,6 +15,8 @@ export const MERGE_REQUEST_NOTEABLE_TYPE = 'MergeRequest';
export const UNRESOLVE_NOTE_METHOD_NAME = 'delete'; export const UNRESOLVE_NOTE_METHOD_NAME = 'delete';
export const RESOLVE_NOTE_METHOD_NAME = 'post'; export const RESOLVE_NOTE_METHOD_NAME = 'post';
export const DESCRIPTION_TYPE = 'changed the description'; export const DESCRIPTION_TYPE = 'changed the description';
export const HISTORY_ONLY_FILTER_VALUE = 2;
export const DISCUSSION_FILTERS_DEFAULT_VALUE = 0;
export const NOTEABLE_TYPE_MAPPING = { export const NOTEABLE_TYPE_MAPPING = {
Issue: ISSUE_NOTEABLE_TYPE, Issue: ISSUE_NOTEABLE_TYPE,
......
...@@ -6,7 +6,7 @@ export default store => { ...@@ -6,7 +6,7 @@ export default store => {
if (discussionFilterEl) { if (discussionFilterEl) {
const { defaultFilter, notesFilters } = discussionFilterEl.dataset; const { defaultFilter, notesFilters } = discussionFilterEl.dataset;
const defaultValue = defaultFilter ? parseInt(defaultFilter, 10) : null; const selectedValue = defaultFilter ? parseInt(defaultFilter, 10) : null;
const filterValues = notesFilters ? JSON.parse(notesFilters) : {}; const filterValues = notesFilters ? JSON.parse(notesFilters) : {};
const filters = Object.keys(filterValues).map(entry => ({ const filters = Object.keys(filterValues).map(entry => ({
title: entry, title: entry,
...@@ -24,7 +24,7 @@ export default store => { ...@@ -24,7 +24,7 @@ export default store => {
return createElement('discussion-filter', { return createElement('discussion-filter', {
props: { props: {
filters, filters,
defaultValue, selectedValue,
}, },
}); });
}, },
......
...@@ -364,5 +364,9 @@ export const filterDiscussion = ({ dispatch }, { path, filter }) => { ...@@ -364,5 +364,9 @@ export const filterDiscussion = ({ dispatch }, { path, filter }) => {
}); });
}; };
export const setCommentsDisabled = ({ commit }, data) => {
commit(types.DISABLE_COMMENTS, data);
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests // prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {}; export default () => {};
...@@ -192,5 +192,7 @@ export const firstUnresolvedDiscussionId = (state, getters) => diffOrder => { ...@@ -192,5 +192,7 @@ export const firstUnresolvedDiscussionId = (state, getters) => diffOrder => {
return getters.unresolvedDiscussionsIdsByDate[0]; return getters.unresolvedDiscussionsIdsByDate[0];
}; };
export const commentsDisabled = state => state.commentsDisabled;
// prevent babel-plugin-rewire from generating an invalid default during karma tests // prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {}; export default () => {};
...@@ -21,6 +21,7 @@ export default () => ({ ...@@ -21,6 +21,7 @@ export default () => ({
noteableData: { noteableData: {
current_user: {}, current_user: {},
}, },
commentsDisabled: false,
}, },
actions, actions,
getters, getters,
......
...@@ -15,6 +15,7 @@ export const UPDATE_DISCUSSION = 'UPDATE_DISCUSSION'; ...@@ -15,6 +15,7 @@ export const UPDATE_DISCUSSION = 'UPDATE_DISCUSSION';
export const SET_DISCUSSION_DIFF_LINES = 'SET_DISCUSSION_DIFF_LINES'; export const SET_DISCUSSION_DIFF_LINES = 'SET_DISCUSSION_DIFF_LINES';
export const SET_NOTES_FETCHED_STATE = 'SET_NOTES_FETCHED_STATE'; export const SET_NOTES_FETCHED_STATE = 'SET_NOTES_FETCHED_STATE';
export const SET_NOTES_LOADING_STATE = 'SET_NOTES_LOADING_STATE'; export const SET_NOTES_LOADING_STATE = 'SET_NOTES_LOADING_STATE';
export const DISABLE_COMMENTS = 'DISABLE_COMMENTS';
// DISCUSSION // DISCUSSION
export const COLLAPSE_DISCUSSION = 'COLLAPSE_DISCUSSION'; export const COLLAPSE_DISCUSSION = 'COLLAPSE_DISCUSSION';
......
...@@ -225,4 +225,8 @@ export default { ...@@ -225,4 +225,8 @@ export default {
discussion.truncated_diff_lines = diffLines; discussion.truncated_diff_lines = diffLines;
}, },
[types.DISABLE_COMMENTS](state, value) {
state.commentsDisabled = value;
},
}; };
...@@ -117,6 +117,8 @@ class Note < ActiveRecord::Base ...@@ -117,6 +117,8 @@ class Note < ActiveRecord::Base
case notes_filter case notes_filter
when UserPreference::NOTES_FILTERS[:only_comments] when UserPreference::NOTES_FILTERS[:only_comments]
user user
when UserPreference::NOTES_FILTERS[:only_activity]
system
else else
all all
end end
......
...@@ -4,7 +4,7 @@ class UserPreference < ActiveRecord::Base ...@@ -4,7 +4,7 @@ class UserPreference < ActiveRecord::Base
# We could use enums, but Rails 4 doesn't support multiple # We could use enums, but Rails 4 doesn't support multiple
# enum options with same name for multiple fields, also it creates # enum options with same name for multiple fields, also it creates
# extra methods that aren't really needed here. # extra methods that aren't really needed here.
NOTES_FILTERS = { all_notes: 0, only_comments: 1 }.freeze NOTES_FILTERS = { all_notes: 0, only_comments: 1, only_activity: 2 }.freeze
belongs_to :user belongs_to :user
...@@ -14,7 +14,8 @@ class UserPreference < ActiveRecord::Base ...@@ -14,7 +14,8 @@ class UserPreference < ActiveRecord::Base
def notes_filters def notes_filters
{ {
s_('Notes|Show all activity') => NOTES_FILTERS[:all_notes], s_('Notes|Show all activity') => NOTES_FILTERS[:all_notes],
s_('Notes|Show comments only') => NOTES_FILTERS[:only_comments] s_('Notes|Show comments only') => NOTES_FILTERS[:only_comments],
s_('Notes|Show history only') => NOTES_FILTERS[:only_activity]
} }
end end
end end
......
...@@ -7,4 +7,8 @@ class UserPreferenceEntity < Grape::Entity ...@@ -7,4 +7,8 @@ class UserPreferenceEntity < Grape::Entity
expose :notes_filters do |user_preference| expose :notes_filters do |user_preference|
UserPreference.notes_filters UserPreference.notes_filters
end end
expose :default_notes_filter do |user_preference|
UserPreference::NOTES_FILTERS[:all_notes]
end
end end
---
title: Add 'only history' option to notes filter
merge_request:
author:
type: changed
...@@ -4165,6 +4165,9 @@ msgstr "" ...@@ -4165,6 +4165,9 @@ msgstr ""
msgid "Notes|Show comments only" msgid "Notes|Show comments only"
msgstr "" msgstr ""
msgid "Notes|Show history only"
msgstr ""
msgid "Notification events" msgid "Notification events"
msgstr "" msgstr ""
......
...@@ -13,7 +13,7 @@ describe NotesFinder do ...@@ -13,7 +13,7 @@ describe NotesFinder do
let!(:comment) { create(:note_on_issue, project: project) } let!(:comment) { create(:note_on_issue, project: project) }
let!(:system_note) { create(:note_on_issue, project: project, system: true) } let!(:system_note) { create(:note_on_issue, project: project, system: true) }
it 'filters system notes' do it 'returns only user notes when using only_comments filter' do
finder = described_class.new(project, user, notes_filter: UserPreference::NOTES_FILTERS[:only_comments]) finder = described_class.new(project, user, notes_filter: UserPreference::NOTES_FILTERS[:only_comments])
notes = finder.execute notes = finder.execute
...@@ -21,6 +21,14 @@ describe NotesFinder do ...@@ -21,6 +21,14 @@ describe NotesFinder do
expect(notes).to match_array(comment) expect(notes).to match_array(comment)
end end
it 'returns only system notes when using only_activity filters' do
finder = described_class.new(project, user, notes_filter: UserPreference::NOTES_FILTERS[:only_activity])
notes = finder.execute
expect(notes).to match_array(system_note)
end
it 'gets all notes' do it 'gets all notes' do
finder = described_class.new(project, user, notes_filter: UserPreference::NOTES_FILTERS[:all_activity]) finder = described_class.new(project, user, notes_filter: UserPreference::NOTES_FILTERS[:all_activity])
......
...@@ -19,7 +19,7 @@ describe('DiscussionFilter component', () => { ...@@ -19,7 +19,7 @@ describe('DiscussionFilter component', () => {
}, },
]; ];
const Component = Vue.extend(DiscussionFilter); const Component = Vue.extend(DiscussionFilter);
const defaultValue = discussionFiltersMock[0].value; const selectedValue = discussionFiltersMock[0].value;
store.state.discussions = discussions; store.state.discussions = discussions;
vm = mountComponentWithStore(Component, { vm = mountComponentWithStore(Component, {
...@@ -27,7 +27,7 @@ describe('DiscussionFilter component', () => { ...@@ -27,7 +27,7 @@ describe('DiscussionFilter component', () => {
store, store,
props: { props: {
filters: discussionFiltersMock, filters: discussionFiltersMock,
defaultValue, selectedValue,
}, },
}); });
}); });
...@@ -63,4 +63,24 @@ describe('DiscussionFilter component', () => { ...@@ -63,4 +63,24 @@ describe('DiscussionFilter component', () => {
expect(vm.filterDiscussion).not.toHaveBeenCalled(); expect(vm.filterDiscussion).not.toHaveBeenCalled();
}); });
it('disables commenting when "Show history only" filter is applied', () => {
const filterItem = vm.$el.querySelector('.dropdown-menu li:last-child button');
filterItem.click();
expect(vm.$store.state.commentsDisabled).toBe(true);
});
it('enables commenting when "Show history only" filter is not applied', () => {
const filterItem = vm.$el.querySelector('.dropdown-menu li:first-child button');
filterItem.click();
expect(vm.$store.state.commentsDisabled).toBe(false);
});
it('renders a dropdown divider for the default filter', () => {
const defaultFilter = vm.$el.querySelector('.dropdown-menu li:first-child');
expect(defaultFilter.lastChild.classList).toContain('dropdown-divider');
});
}); });
...@@ -121,6 +121,13 @@ describe('note_app', () => { ...@@ -121,6 +121,13 @@ describe('note_app', () => {
).toEqual('Write a comment or drag your files here…'); ).toEqual('Write a comment or drag your files here…');
}); });
it('should not render form when commenting is disabled', () => {
store.state.commentsDisabled = true;
vm = mountComponent();
expect(vm.$el.querySelector('.js-main-target-form')).toEqual(null);
});
it('should render form comment button as disabled', () => { it('should render form comment button as disabled', () => {
expect(vm.$el.querySelector('.js-note-new-discussion').getAttribute('disabled')).toEqual( expect(vm.$el.querySelector('.js-note-new-discussion').getAttribute('disabled')).toEqual(
'disabled', 'disabled',
......
...@@ -509,4 +509,17 @@ describe('Actions Notes Store', () => { ...@@ -509,4 +509,17 @@ describe('Actions Notes Store', () => {
expect(mrWidgetEventHub.$emit).toHaveBeenCalledWith('mr.discussion.updated'); expect(mrWidgetEventHub.$emit).toHaveBeenCalledWith('mr.discussion.updated');
}); });
}); });
describe('setCommentsDisabled', () => {
it('should set comments disabled state', done => {
testAction(
actions.setCommentsDisabled,
true,
null,
[{ type: 'DISABLE_COMMENTS', payload: true }],
[],
done,
);
});
});
}); });
...@@ -427,4 +427,14 @@ describe('Notes Store mutations', () => { ...@@ -427,4 +427,14 @@ describe('Notes Store mutations', () => {
expect(state.discussions[0].expanded).toBe(true); expect(state.discussions[0].expanded).toBe(true);
}); });
}); });
describe('DISABLE_COMMENTS', () => {
it('should set comments disabled state', () => {
const state = {};
mutations.DISABLE_COMMENTS(state, true);
expect(state.commentsDisabled).toEqual(true);
});
});
}); });
...@@ -6,22 +6,43 @@ describe UserPreference do ...@@ -6,22 +6,43 @@ describe UserPreference do
describe '#set_notes_filter' do describe '#set_notes_filter' do
let(:issuable) { build_stubbed(:issue) } let(:issuable) { build_stubbed(:issue) }
let(:user_preference) { create(:user_preference) } let(:user_preference) { create(:user_preference) }
let(:only_comments) { described_class::NOTES_FILTERS[:only_comments] }
it 'returns updated discussion filter' do shared_examples 'setting system notes' do
filter_name = it 'returns updated discussion filter' do
user_preference.set_notes_filter(only_comments, issuable) filter_name =
user_preference.set_notes_filter(filter, issuable)
expect(filter_name).to eq(filter)
end
it 'updates discussion filter for issuable class' do
user_preference.set_notes_filter(filter, issuable)
expect(user_preference.reload.issue_notes_filter).to eq(filter)
end
end
context 'when filter is set to all notes' do
let(:filter) { described_class::NOTES_FILTERS[:all_notes] }
it_behaves_like 'setting system notes'
end
context 'when filter is set to only comments' do
let(:filter) { described_class::NOTES_FILTERS[:only_comments] }
expect(filter_name).to eq(only_comments) it_behaves_like 'setting system notes'
end end
it 'updates discussion filter for issuable class' do context 'when filter is set to only activity' do
user_preference.set_notes_filter(only_comments, issuable) let(:filter) { described_class::NOTES_FILTERS[:only_activity] }
expect(user_preference.reload.issue_notes_filter).to eq(only_comments) it_behaves_like 'setting system notes'
end end
context 'when notes_filter parameter is invalid' do context 'when notes_filter parameter is invalid' do
let(:only_comments) { described_class::NOTES_FILTERS[:only_comments] }
it 'returns the current notes filter' do it 'returns the current notes filter' do
user_preference.set_notes_filter(only_comments, issuable) user_preference.set_notes_filter(only_comments, issuable)
......
...@@ -34,12 +34,24 @@ shared_examples 'issuable notes filter' do ...@@ -34,12 +34,24 @@ shared_examples 'issuable notes filter' do
expect(user.reload.notes_filter_for(issuable)).to eq(0) expect(user.reload.notes_filter_for(issuable)).to eq(0)
end end
it 'returns no system note' do it 'returns only user comments' do
user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_comments], issuable) user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_comments], issuable)
get :discussions, namespace_id: project.namespace, project_id: project, id: issuable.iid get :discussions, namespace_id: project.namespace, project_id: project, id: issuable.iid
discussions = JSON.parse(response.body)
expect(JSON.parse(response.body).count).to eq(1) expect(discussions.count).to eq(1)
expect(discussions.first["notes"].first["system"]).to be(false)
end
it 'returns only activity notes' do
user.set_notes_filter(UserPreference::NOTES_FILTERS[:only_activity], issuable)
get :discussions, namespace_id: project.namespace, project_id: project, id: issuable.iid
discussions = JSON.parse(response.body)
expect(discussions.count).to eq(1)
expect(discussions.first["notes"].first["system"]).to be(true)
end end
context 'when filter is set to "only_comments"' do context 'when filter is set to "only_comments"' do
......
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