summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKushal Pandya <kushalspandya@gmail.com>2019-02-26 14:04:15 +0000
committerPhil Hughes <me@iamphill.com>2019-02-26 14:04:15 +0000
commit2dd22ecb4601b546b630d1bdd7203dc4d87959e7 (patch)
tree1406c773cbcc47482db44f592ab51d9010f62c7d
parent8baf9e5f03ea1388d7c65a1f6f1d7eb9cc952611 (diff)
downloadgitlab-ce-2dd22ecb4601b546b630d1bdd7203dc4d87959e7.tar.gz
Add support for toggling discussion filter from notes section
Adds discussion note style section under notes app from where user can toggle discussion when they have selected a filter to show only system notes.
-rw-r--r--app/assets/javascripts/notes/components/discussion_filter.vue24
-rw-r--r--app/assets/javascripts/notes/components/discussion_filter_note.vue52
-rw-r--r--app/assets/javascripts/notes/components/notes_app.vue3
-rw-r--r--app/assets/javascripts/notes/constants.js6
-rw-r--r--app/assets/stylesheets/pages/notes.scss71
-rw-r--r--changelogs/unreleased/51819-show-feed-toggle-under-system-notes.yml5
-rw-r--r--locale/gitlab.pot9
-rw-r--r--spec/javascripts/notes/components/discussion_filter_note_spec.js93
-rw-r--r--spec/javascripts/notes/components/discussion_filter_spec.js22
-rw-r--r--spec/javascripts/notes/components/note_app_spec.js7
10 files changed, 253 insertions, 39 deletions
diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue
index e03d6e9cd02..31164f74201 100644
--- a/app/assets/javascripts/notes/components/discussion_filter.vue
+++ b/app/assets/javascripts/notes/components/discussion_filter.vue
@@ -7,7 +7,9 @@ import {
DISCUSSION_FILTERS_DEFAULT_VALUE,
HISTORY_ONLY_FILTER_VALUE,
DISCUSSION_TAB_LABEL,
+ DISCUSSION_FILTER_TYPES,
} from '../constants';
+import notesEventHub from '../event_hub';
export default {
components: {
@@ -46,6 +48,7 @@ export default {
this.toggleFilters(currentTab);
}
+ notesEventHub.$on('dropdownSelect', this.selectFilter);
window.addEventListener('hashchange', this.handleLocationHash);
this.handleLocationHash();
},
@@ -53,6 +56,7 @@ export default {
this.toggleCommentsForm();
},
destroyed() {
+ notesEventHub.$off('dropdownSelect', this.selectFilter);
window.removeEventListener('hashchange', this.handleLocationHash);
},
methods: {
@@ -86,12 +90,23 @@ export default {
this.setTargetNoteHash(hash);
}
},
+ filterType(value) {
+ if (value === 0) {
+ return DISCUSSION_FILTER_TYPES.ALL;
+ } else if (value === 1) {
+ return DISCUSSION_FILTER_TYPES.COMMENTS;
+ }
+ return DISCUSSION_FILTER_TYPES.HISTORY;
+ },
},
};
</script>
<template>
- <div v-if="displayFilters" class="discussion-filter-container d-inline-block align-bottom">
+ <div
+ v-if="displayFilters"
+ class="discussion-filter-container js-discussion-filter-container d-inline-block align-bottom"
+ >
<button
id="discussion-filter-dropdown"
ref="dropdownToggle"
@@ -102,12 +117,17 @@ export default {
{{ currentFilter.title }} <icon name="chevron-down" />
</button>
<div
+ ref="dropdownMenu"
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">
+ <li
+ v-for="filter in filters"
+ :key="filter.value"
+ :data-filter-type="filterType(filter.value)"
+ >
<button
:class="{ 'is-active': filter.value === currentValue }"
class="qa-filter-options"
diff --git a/app/assets/javascripts/notes/components/discussion_filter_note.vue b/app/assets/javascripts/notes/components/discussion_filter_note.vue
new file mode 100644
index 00000000000..46661e06f6d
--- /dev/null
+++ b/app/assets/javascripts/notes/components/discussion_filter_note.vue
@@ -0,0 +1,52 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import Icon from '~/vue_shared/components/icon.vue';
+import { __, sprintf } from '~/locale';
+
+import notesEventHub from '../event_hub';
+
+export default {
+ components: {
+ GlButton,
+ Icon,
+ },
+ computed: {
+ timelineContent() {
+ return sprintf(
+ __(
+ "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options.",
+ ),
+ {
+ startTag: `<b>`,
+ endTag: `</b>`,
+ },
+ false,
+ );
+ },
+ },
+ methods: {
+ selectFilter(value) {
+ notesEventHub.$emit('dropdownSelect', value);
+ },
+ },
+};
+</script>
+
+<template>
+ <li class="timeline-entry note note-wrapper discussion-filter-note js-discussion-filter-note">
+ <div class="timeline-icon">
+ <icon name="comment" />
+ </div>
+ <div class="timeline-content">
+ <div v-html="timelineContent"></div>
+ <div class="discussion-filter-actions mt-2">
+ <gl-button variant="default" @click="selectFilter(0)">
+ {{ __('Show all activity') }}
+ </gl-button>
+ <gl-button variant="default" @click="selectFilter(1)">
+ {{ __('Show comments only') }}
+ </gl-button>
+ </div>
+ </div>
+ </li>
+</template>
diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue
index 8d3f6d902f8..e2bd59f7631 100644
--- a/app/assets/javascripts/notes/components/notes_app.vue
+++ b/app/assets/javascripts/notes/components/notes_app.vue
@@ -6,6 +6,7 @@ import * as constants from '../constants';
import eventHub from '../event_hub';
import noteableNote from './noteable_note.vue';
import noteableDiscussion from './noteable_discussion.vue';
+import discussionFilterNote from './discussion_filter_note.vue';
import systemNote from '../../vue_shared/components/notes/system_note.vue';
import commentForm from './comment_form.vue';
import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
@@ -24,6 +25,7 @@ export default {
placeholderNote,
placeholderSystemNote,
skeletonLoadingContainer,
+ discussionFilterNote,
},
props: {
noteableData: {
@@ -235,6 +237,7 @@ export default {
:help-page-path="helpPagePath"
/>
</template>
+ <discussion-filter-note v-show="commentsDisabled" />
</ul>
<comment-form v-if="!commentsDisabled" :noteable-type="noteableType" />
diff --git a/app/assets/javascripts/notes/constants.js b/app/assets/javascripts/notes/constants.js
index 78d365fe94b..fba3db8542c 100644
--- a/app/assets/javascripts/notes/constants.js
+++ b/app/assets/javascripts/notes/constants.js
@@ -24,3 +24,9 @@ export const NOTEABLE_TYPE_MAPPING = {
MergeRequest: MERGE_REQUEST_NOTEABLE_TYPE,
Epic: EPIC_NOTEABLE_TYPE,
};
+
+export const DISCUSSION_FILTER_TYPES = {
+ ALL: 'all',
+ COMMENTS: 'comments',
+ HISTORY: 'history',
+};
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 7e7eff1346a..1198b9ea143 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -4,7 +4,7 @@ $note-form-margin-left: 72px;
@mixin vertical-line($left) {
&::before {
- content: '';
+ content: "";
border-left: 2px solid $gray-100;
position: absolute;
top: 0;
@@ -53,12 +53,12 @@ $note-form-margin-left: 72px;
&.note-form {
margin-left: 0;
- @include notes-media('min', map-get($grid-breakpoints, md)) {
+ @include notes-media("min", map-get($grid-breakpoints, md)) {
margin-left: $note-form-margin-left;
}
.timeline-icon {
- @include notes-media('min', map-get($grid-breakpoints, sm)) {
+ @include notes-media("min", map-get($grid-breakpoints, sm)) {
margin-left: -$note-icon-gutter-width;
}
}
@@ -242,7 +242,7 @@ $note-form-margin-left: 72px;
}
.note-header {
- @include notes-media('max', map-get($grid-breakpoints, xs)) {
+ @include notes-media("max", map-get($grid-breakpoints, xs)) {
.inline {
display: block;
}
@@ -303,28 +303,8 @@ $note-form-margin-left: 72px;
}
}
- .timeline-icon {
- float: left;
- display: flex;
- align-items: center;
- background-color: $white-light;
- width: $system-note-icon-size;
- height: $system-note-icon-size;
- border: 1px solid $border-color;
- border-radius: $system-note-icon-size;
- margin: -6px $gl-padding 0 0;
-
- svg {
- width: $system-note-svg-size;
- height: $system-note-svg-size;
- fill: $gray-darkest;
- display: block;
- margin: 0 auto;
- }
- }
-
.timeline-content {
- @include notes-media('min', map-get($grid-breakpoints, sm)) {
+ @include notes-media("min", map-get($grid-breakpoints, sm)) {
margin-left: 30px;
}
}
@@ -368,7 +348,7 @@ $note-form-margin-left: 72px;
}
&::after {
- content: '';
+ content: "";
height: 70px;
position: absolute;
left: $gl-padding-24;
@@ -380,6 +360,37 @@ $note-form-margin-left: 72px;
}
}
}
+
+ .system-note,
+ .discussion-filter-note {
+ .timeline-icon {
+ float: left;
+ display: flex;
+ align-items: center;
+ background-color: $white-light;
+ width: $system-note-icon-size;
+ height: $system-note-icon-size;
+ border: 1px solid $border-color;
+ border-radius: $system-note-icon-size;
+ margin: -6px $gl-padding 0 0;
+
+ svg {
+ width: $system-note-svg-size;
+ height: $system-note-svg-size;
+ fill: $gray-darkest;
+ display: block;
+ margin: 0 auto;
+ }
+ }
+ }
+
+ .discussion-filter-note {
+ .timeline-icon {
+ width: $system-note-icon-size + 6;
+ height: $system-note-icon-size + 6;
+ margin-top: -8px;
+ }
+ }
}
// Diff code in discussion view
@@ -579,7 +590,7 @@ $note-form-margin-left: 72px;
.note-headline-light {
display: inline;
- @include notes-media('max', map-get($grid-breakpoints, xs)) {
+ @include notes-media("max", map-get($grid-breakpoints, xs)) {
display: block;
}
}
@@ -645,7 +656,7 @@ $note-form-margin-left: 72px;
margin-left: 10px;
color: $gray-darkest;
- @include notes-media('max', map-get($grid-breakpoints, sm) - 1) {
+ @include notes-media("max", map-get($grid-breakpoints, sm) - 1) {
float: none;
margin-left: 0;
}
@@ -764,7 +775,7 @@ $note-form-margin-left: 72px;
}
.line-resolve-all-container {
- @include notes-media('min', map-get($grid-breakpoints, sm)) {
+ @include notes-media("min", map-get($grid-breakpoints, sm)) {
margin-right: 0;
}
@@ -905,7 +916,6 @@ $note-form-margin-left: 72px;
}
.discussion-filter-container {
-
.btn > svg {
width: $gl-col-padding;
height: $gl-col-padding;
@@ -927,7 +937,6 @@ $note-form-margin-left: 72px;
//This needs to be deleted when Snippet/Commit comments are convered to Vue
// See https://gitlab.com/gitlab-org/gitlab-ce/issues/53918#note_117038785
.unstyled-comments {
-
.discussion-header {
padding: $gl-padding;
border-bottom: 1px solid $border-color;
diff --git a/changelogs/unreleased/51819-show-feed-toggle-under-system-notes.yml b/changelogs/unreleased/51819-show-feed-toggle-under-system-notes.yml
new file mode 100644
index 00000000000..76ea4149c56
--- /dev/null
+++ b/changelogs/unreleased/51819-show-feed-toggle-under-system-notes.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for toggling discussion filter from notes section
+merge_request: 25426
+author:
+type: added
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index bd45b01d463..150e16ed67f 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -6743,9 +6743,15 @@ msgstr ""
msgid "Sherlock Transactions"
msgstr ""
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr ""
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr ""
@@ -8654,6 +8660,9 @@ msgstr ""
msgid "You'll need to use different branch names to get a valid comparison."
msgstr ""
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr ""
diff --git a/spec/javascripts/notes/components/discussion_filter_note_spec.js b/spec/javascripts/notes/components/discussion_filter_note_spec.js
new file mode 100644
index 00000000000..52d2e7ce947
--- /dev/null
+++ b/spec/javascripts/notes/components/discussion_filter_note_spec.js
@@ -0,0 +1,93 @@
+import Vue from 'vue';
+import DiscussionFilterNote from '~/notes/components/discussion_filter_note.vue';
+import eventHub from '~/notes/event_hub';
+
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+describe('DiscussionFilterNote component', () => {
+ let vm;
+
+ const createComponent = () => {
+ const Component = Vue.extend(DiscussionFilterNote);
+
+ return mountComponent(Component);
+ };
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('computed', () => {
+ describe('timelineContent', () => {
+ it('returns string containing instruction for switching feed type', () => {
+ expect(vm.timelineContent).toBe(
+ "You're only seeing <b>other activity</b> in the feed. To add a comment, switch to one of the following options.",
+ );
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('selectFilter', () => {
+ it('emits `dropdownSelect` event on `eventHub` with provided param', () => {
+ spyOn(eventHub, '$emit');
+
+ vm.selectFilter(1);
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('dropdownSelect', 1);
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('renders component container element', () => {
+ expect(vm.$el.classList.contains('discussion-filter-note')).toBe(true);
+ });
+
+ it('renders comment icon element', () => {
+ expect(vm.$el.querySelector('.timeline-icon svg use').getAttribute('xlink:href')).toContain(
+ 'comment',
+ );
+ });
+
+ it('renders filter information note', () => {
+ expect(vm.$el.querySelector('.timeline-content').innerText.trim()).toContain(
+ "You're only seeing other activity in the feed. To add a comment, switch to one of the following options.",
+ );
+ });
+
+ it('renders filter buttons', () => {
+ const buttonsContainerEl = vm.$el.querySelector('.discussion-filter-actions');
+
+ expect(buttonsContainerEl.querySelector('button:first-child').innerText.trim()).toContain(
+ 'Show all activity',
+ );
+
+ expect(buttonsContainerEl.querySelector('button:last-child').innerText.trim()).toContain(
+ 'Show comments only',
+ );
+ });
+
+ it('clicking `Show all activity` button calls `selectFilter("all")` method', () => {
+ const showAllBtn = vm.$el.querySelector('.discussion-filter-actions button:first-child');
+ spyOn(vm, 'selectFilter');
+
+ showAllBtn.dispatchEvent(new Event('click'));
+
+ expect(vm.selectFilter).toHaveBeenCalledWith(0);
+ });
+
+ it('clicking `Show comments only` button calls `selectFilter("comments")` method', () => {
+ const showAllBtn = vm.$el.querySelector('.discussion-filter-actions button:last-child');
+ spyOn(vm, 'selectFilter');
+
+ showAllBtn.dispatchEvent(new Event('click'));
+
+ expect(vm.selectFilter).toHaveBeenCalledWith(1);
+ });
+ });
+});
diff --git a/spec/javascripts/notes/components/discussion_filter_spec.js b/spec/javascripts/notes/components/discussion_filter_spec.js
index 91dab58ba7f..1c366aee8e2 100644
--- a/spec/javascripts/notes/components/discussion_filter_spec.js
+++ b/spec/javascripts/notes/components/discussion_filter_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import createStore from '~/notes/stores';
import DiscussionFilter from '~/notes/components/discussion_filter.vue';
-import { DISCUSSION_FILTERS_DEFAULT_VALUE } from '~/notes/constants';
+import { DISCUSSION_FILTERS_DEFAULT_VALUE, DISCUSSION_FILTER_TYPES } from '~/notes/constants';
import { mountComponentWithStore } from '../../helpers/vue_mount_component_helper';
import { discussionFiltersMock, discussionMock } from '../mock_data';
@@ -54,14 +54,18 @@ describe('DiscussionFilter component', () => {
});
it('updates to the selected item', () => {
- const filterItem = vm.$el.querySelector('.dropdown-menu li:last-child button');
+ const filterItem = vm.$el.querySelector(
+ `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.HISTORY}"] button`,
+ );
filterItem.click();
expect(vm.currentFilter.title).toEqual(filterItem.textContent.trim());
});
it('only updates when selected filter changes', () => {
- const filterItem = vm.$el.querySelector('.dropdown-menu li:first-child button');
+ const filterItem = vm.$el.querySelector(
+ `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.ALL}"] button`,
+ );
spyOn(vm, 'filterDiscussion');
filterItem.click();
@@ -70,21 +74,27 @@ describe('DiscussionFilter component', () => {
});
it('disables commenting when "Show history only" filter is applied', () => {
- const filterItem = vm.$el.querySelector('.dropdown-menu li:last-child button');
+ const filterItem = vm.$el.querySelector(
+ `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.HISTORY}"] 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');
+ const filterItem = vm.$el.querySelector(
+ `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.ALL}"] 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');
+ const defaultFilter = vm.$el.querySelector(
+ `.dropdown-menu li[data-filter-type="${DISCUSSION_FILTER_TYPES.ALL}"]`,
+ );
expect(defaultFilter.lastChild.classList).toContain('dropdown-divider');
});
diff --git a/spec/javascripts/notes/components/note_app_spec.js b/spec/javascripts/notes/components/note_app_spec.js
index 82f58dafc78..d716ece3766 100644
--- a/spec/javascripts/notes/components/note_app_spec.js
+++ b/spec/javascripts/notes/components/note_app_spec.js
@@ -126,6 +126,13 @@ describe('note_app', () => {
expect(wrapper.find('.js-main-target-form').exists()).toBe(false);
});
+ it('should render discussion filter note `commentsDisabled` is true', () => {
+ store.state.commentsDisabled = true;
+ wrapper = mountComponent();
+
+ expect(wrapper.find('.js-discussion-filter-note').exists()).toBe(true);
+ });
+
it('should render form comment button as disabled', () => {
expect(wrapper.find('.js-note-new-discussion').attributes('disabled')).toEqual('disabled');
});