diff options
author | Oswaldo Ferreira <oswaldo@gitlab.com> | 2018-10-23 09:49:45 +0000 |
---|---|---|
committer | Phil Hughes <me@iamphill.com> | 2018-10-23 09:49:45 +0000 |
commit | 86ead874e217fb1aceb2d09daf29a9a6ade0ff62 (patch) | |
tree | 16d7130d8b4ce09bff582e4506ca191253a2895c /app/assets/javascripts/notes | |
parent | 10bb8297ebe5fc01540b20c3fd365234779b6837 (diff) | |
download | gitlab-ce-86ead874e217fb1aceb2d09daf29a9a6ade0ff62.tar.gz |
Resolve "Filter discussion (tab) by comments or activity in issues and merge requests"
Diffstat (limited to 'app/assets/javascripts/notes')
11 files changed, 160 insertions, 12 deletions
diff --git a/app/assets/javascripts/notes/components/discussion_counter.vue b/app/assets/javascripts/notes/components/discussion_counter.vue index ad6e7cf501d..1f80f24e045 100644 --- a/app/assets/javascripts/notes/components/discussion_counter.vue +++ b/app/assets/javascripts/notes/components/discussion_counter.vue @@ -56,10 +56,11 @@ export default { </script> <template> - <div class="line-resolve-all-container prepend-top-10"> + <div + v-if="discussionCount > 0" + class="line-resolve-all-container prepend-top-8"> <div> <div - v-if="discussionCount > 0" :class="{ 'has-next-btn': hasNextButton }" class="line-resolve-all"> <span diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue new file mode 100644 index 00000000000..27972682ca1 --- /dev/null +++ b/app/assets/javascripts/notes/components/discussion_filter.vue @@ -0,0 +1,82 @@ +<script> +import $ from 'jquery'; +import Icon from '~/vue_shared/components/icon.vue'; +import { mapGetters, mapActions } from 'vuex'; + +export default { + components: { + Icon, + }, + props: { + filters: { + type: Array, + required: true, + }, + defaultValue: { + type: Number, + default: null, + required: false, + }, + }, + data() { + return { currentValue: this.defaultValue }; + }, + computed: { + ...mapGetters([ + 'getNotesDataByProp', + ]), + currentFilter() { + if (!this.currentValue) return this.filters[0]; + return this.filters.find(filter => filter.value === this.currentValue); + }, + }, + methods: { + ...mapActions(['filterDiscussion']), + selectFilter(value) { + const filter = parseInt(value, 10); + + // close dropdown + $(this.$refs.dropdownToggle).dropdown('toggle'); + + if (filter === this.currentValue) return; + this.currentValue = filter; + this.filterDiscussion({ path: this.getNotesDataByProp('discussionsPath'), filter }); + }, + }, +}; +</script> + +<template> + <div class="discussion-filter-container d-inline-block align-bottom"> + <button + id="discussion-filter-dropdown" + ref="dropdownToggle" + class="btn btn-default" + data-toggle="dropdown" + aria-expanded="false" + > + {{ currentFilter.title }} + <icon name="chevron-down" /> + </button> + <div + class="dropdown-menu dropdown-menu-selectable dropdown-menu-right" + aria-labelledby="discussion-filter-dropdown"> + <div class="dropdown-content"> + <ul> + <li + v-for="filter in filters" + :key="filter.value" + > + <button + :class="{ 'is-active': filter.value === currentValue }" + type="button" + @click="selectFilter(filter.value)" + > + {{ filter.title }} + </button> + </li> + </ul> + </div> + </div> + </div> +</template> diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue index 618a1581d8f..b0faa443a18 100644 --- a/app/assets/javascripts/notes/components/notes_app.vue +++ b/app/assets/javascripts/notes/components/notes_app.vue @@ -50,11 +50,11 @@ export default { }, data() { return { - isLoading: true, + currentFilter: null, }; }, computed: { - ...mapGetters(['isNotesFetched', 'discussions', 'getNotesDataByProp', 'discussionCount']), + ...mapGetters(['isNotesFetched', 'discussions', 'getNotesDataByProp', 'discussionCount', 'isLoading']), noteableType() { return this.noteableData.noteableType; }, @@ -102,6 +102,7 @@ export default { }, methods: { ...mapActions({ + setLoadingState: 'setLoadingState', fetchDiscussions: 'fetchDiscussions', poll: 'poll', actionToggleAward: 'toggleAward', @@ -133,19 +134,19 @@ export default { return discussion.individual_note ? { note: discussion.notes[0] } : { discussion }; }, fetchNotes() { - return this.fetchDiscussions(this.getNotesDataByProp('discussionsPath')) + return this.fetchDiscussions({ path: this.getNotesDataByProp('discussionsPath') }) .then(() => { this.initPolling(); }) .then(() => { - this.isLoading = false; + this.setLoadingState(false); this.setNotesFetchedState(true); eventHub.$emit('fetchedNotesData'); }) .then(() => this.$nextTick()) .then(() => this.checkLocationHash()) .catch(() => { - this.isLoading = false; + this.setLoadingState(false); this.setNotesFetchedState(true); Flash('Something went wrong while fetching comments. Please try again.'); }); diff --git a/app/assets/javascripts/notes/discussion_filters.js b/app/assets/javascripts/notes/discussion_filters.js new file mode 100644 index 00000000000..012ffc4093e --- /dev/null +++ b/app/assets/javascripts/notes/discussion_filters.js @@ -0,0 +1,33 @@ +import Vue from 'vue'; +import DiscussionFilter from './components/discussion_filter.vue'; + +export default (store) => { + const discussionFilterEl = document.getElementById('js-vue-discussion-filter'); + + if (discussionFilterEl) { + const { defaultFilter, notesFilters } = discussionFilterEl.dataset; + const defaultValue = defaultFilter ? parseInt(defaultFilter, 10) : null; + const filterValues = notesFilters ? JSON.parse(notesFilters) : {}; + const filters = Object.keys(filterValues).map(entry => + ({ title: entry, value: filterValues[entry] })); + + return new Vue({ + el: discussionFilterEl, + name: 'DiscussionFilter', + components: { + DiscussionFilter, + }, + store, + render(createElement) { + return createElement('discussion-filter', { + props: { + filters, + defaultValue, + }, + }); + }, + }); + } + + return null; +}; diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js index 3aef30c608c..2f715c85fa6 100644 --- a/app/assets/javascripts/notes/index.js +++ b/app/assets/javascripts/notes/index.js @@ -1,10 +1,13 @@ import Vue from 'vue'; import notesApp from './components/notes_app.vue'; +import initDiscussionFilters from './discussion_filters'; import createStore from './stores'; document.addEventListener('DOMContentLoaded', () => { const store = createStore(); + initDiscussionFilters(store); + return new Vue({ el: '#js-vue-notes', components: { diff --git a/app/assets/javascripts/notes/services/notes_service.js b/app/assets/javascripts/notes/services/notes_service.js index f5dce94caad..47a6f07cce2 100644 --- a/app/assets/javascripts/notes/services/notes_service.js +++ b/app/assets/javascripts/notes/services/notes_service.js @@ -5,8 +5,9 @@ import * as constants from '../constants'; Vue.use(VueResource); export default { - fetchDiscussions(endpoint) { - return Vue.http.get(endpoint); + fetchDiscussions(endpoint, filter) { + const config = filter !== undefined ? { params: { notes_filter: filter } } : null; + return Vue.http.get(endpoint, config); }, deleteNote(endpoint) { return Vue.http.delete(endpoint); diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js index 7ab7e5a9abb..b5dd49bc6c9 100644 --- a/app/assets/javascripts/notes/stores/actions.js +++ b/app/assets/javascripts/notes/stores/actions.js @@ -11,6 +11,7 @@ import loadAwardsHandler from '../../awards_handler'; import sidebarTimeTrackingEventHub from '../../sidebar/event_hub'; import { isInViewport, scrollToElement } from '../../lib/utils/common_utils'; import mrWidgetEventHub from '../../vue_merge_request_widget/event_hub'; +import { __ } from '~/locale'; let eTagPoll; @@ -36,9 +37,9 @@ export const setNotesFetchedState = ({ commit }, state) => export const toggleDiscussion = ({ commit }, data) => commit(types.TOGGLE_DISCUSSION, data); -export const fetchDiscussions = ({ commit }, path) => +export const fetchDiscussions = ({ commit }, { path, filter }) => service - .fetchDiscussions(path) + .fetchDiscussions(path, filter) .then(res => res.json()) .then(discussions => { commit(types.SET_INITIAL_DISCUSSIONS, discussions); @@ -251,7 +252,7 @@ const pollSuccessCallBack = (resp, commit, state, getters, dispatch) => { if (discussion) { commit(types.ADD_NEW_REPLY_TO_DISCUSSION, note); } else if (note.type === constants.DIFF_NOTE) { - dispatch('fetchDiscussions', state.notesData.discussionsPath); + dispatch('fetchDiscussions', { path: state.notesData.discussionsPath }); } else { commit(types.ADD_NEW_NOTE, note); } @@ -345,5 +346,23 @@ export const updateMergeRequestWidget = () => { mrWidgetEventHub.$emit('mr.discussion.updated'); }; +export const setLoadingState = ({ commit }, data) => { + commit(types.SET_NOTES_LOADING_STATE, data); +}; + +export const filterDiscussion = ({ dispatch }, { path, filter }) => { + dispatch('setLoadingState', true); + dispatch('fetchDiscussions', { path, filter }) + .then(() => { + dispatch('setLoadingState', false); + dispatch('setNotesFetchedState', true); + }) + .catch(() => { + dispatch('setLoadingState', false); + dispatch('setNotesFetchedState', true); + Flash(__('Something went wrong while fetching comments. Please try again.')); + }); +}; + // prevent babel-plugin-rewire from generating an invalid default during karma tests export default () => {}; diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js index a829149a17e..21c334a9d33 100644 --- a/app/assets/javascripts/notes/stores/getters.js +++ b/app/assets/javascripts/notes/stores/getters.js @@ -11,6 +11,8 @@ export const getNotesData = state => state.notesData; export const isNotesFetched = state => state.isNotesFetched; +export const isLoading = state => state.isLoading; + export const getNotesDataByProp = state => prop => state.notesData[prop]; export const getNoteableData = state => state.noteableData; diff --git a/app/assets/javascripts/notes/stores/modules/index.js b/app/assets/javascripts/notes/stores/modules/index.js index 61dbb075586..400142668ea 100644 --- a/app/assets/javascripts/notes/stores/modules/index.js +++ b/app/assets/javascripts/notes/stores/modules/index.js @@ -11,6 +11,7 @@ export default () => ({ // View layer isToggleStateButtonLoading: false, isNotesFetched: false, + isLoading: true, // holds endpoints and permissions provided through haml notesData: { diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js index 6f374f78691..2fa53aef1d4 100644 --- a/app/assets/javascripts/notes/stores/mutation_types.js +++ b/app/assets/javascripts/notes/stores/mutation_types.js @@ -14,6 +14,7 @@ export const UPDATE_NOTE = 'UPDATE_NOTE'; export const UPDATE_DISCUSSION = 'UPDATE_DISCUSSION'; export const SET_DISCUSSION_DIFF_LINES = 'SET_DISCUSSION_DIFF_LINES'; export const SET_NOTES_FETCHED_STATE = 'SET_NOTES_FETCHED_STATE'; +export const SET_NOTES_LOADING_STATE = 'SET_NOTES_LOADING_STATE'; // DISCUSSION export const COLLAPSE_DISCUSSION = 'COLLAPSE_DISCUSSION'; diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js index 73e55705f39..65085452139 100644 --- a/app/assets/javascripts/notes/stores/mutations.js +++ b/app/assets/javascripts/notes/stores/mutations.js @@ -216,6 +216,10 @@ export default { Object.assign(state, { isNotesFetched: value }); }, + [types.SET_NOTES_LOADING_STATE](state, value) { + state.isLoading = value; + }, + [types.SET_DISCUSSION_DIFF_LINES](state, { discussionId, diffLines }) { const discussion = utils.findNoteObjectById(state.discussions, discussionId); |