Commit 517a288b authored by Phil Hughes's avatar Phil Hughes

Merge branch '7751-add-refactored-epics-sidebar-participants-support' into 'master'

[Part 6] Add support for Participants list and Subscription in Epic sidebar

See merge request gitlab-org/gitlab-ee!9329
parents 69a163f0 26d40daf
......@@ -10,6 +10,8 @@ import SidebarTodo from './sidebar_items/sidebar_todo.vue';
import SidebarDatePicker from './sidebar_items/sidebar_date_picker.vue';
import SidebarDatePickerCollapsed from '~/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue';
import SidebarLabels from './sidebar_items/sidebar_labels.vue';
import SidebarParticipants from '~/sidebar/components/participants/participants.vue';
import SidebarSubscription from './sidebar_items/sidebar_subscription.vue';
import { dateTypes } from '../constants';
......@@ -22,11 +24,14 @@ export default {
SidebarDatePickerCollapsed,
SidebarLabels,
SidebarParentEpic,
SidebarParticipants,
SidebarSubscription,
},
computed: {
...mapState([
'canUpdate',
'sidebarCollapsed',
'participants',
'startDateSourcingMilestoneTitle',
'startDateSourcingMilestoneDates',
'startDateIsFixed',
......@@ -182,6 +187,13 @@ export default {
<div class="block parent-epic">
<sidebar-parent-epic :block-title="__('Parent epic')" :initial-epic="parentEpic" />
</div>
<div class="block participants">
<sidebar-participants
:participants="participants"
@toggleSidebar="toggleSidebar({ sidebarCollapsed })"
/>
</div>
<sidebar-subscription :sidebar-collapsed="sidebarCollapsed" />
</div>
</aside>
</template>
<script>
import { mapState, mapActions } from 'vuex';
import Subscription from '~/sidebar/components/subscriptions/subscriptions.vue';
export default {
components: {
Subscription,
},
props: {
sidebarCollapsed: {
type: Boolean,
required: true,
},
},
computed: {
...mapState(['subscribed', 'epicSubscriptionToggleInProgress']),
},
methods: {
...mapActions(['toggleSidebar', 'toggleEpicSubscription']),
},
};
</script>
<template>
<div class="block subscription">
<subscription
:loading="epicSubscriptionToggleInProgress"
:subscribed="subscribed"
@toggleSubscription="toggleEpicSubscription"
@toggleSidebar="toggleSidebar({ sidebarCollapsed })"
/>
</div>
</template>
......@@ -150,5 +150,34 @@ export const saveDate = ({ state, dispatch }, { dateType, dateTypeIsFixed, newDa
});
};
/**
* Methods to handle Epic subscription (AKA Notifications) toggle from sidebar
*/
export const requestEpicSubscriptionToggle = ({ commit }) =>
commit(types.REQUEST_EPIC_SUBSCRIPTION_TOGGLE);
export const requestEpicSubscriptionToggleSuccess = ({ commit }, data) =>
commit(types.REQUEST_EPIC_SUBSCRIPTION_TOGGLE_SUCCESS, data);
export const requestEpicSubscriptionToggleFailure = ({ commit, state }) => {
commit(types.REQUEST_EPIC_SUBSCRIPTION_TOGGLE_FAILURE);
if (state.subscribed) {
flash(__('An error occurred while unsubscribing to notifications.'));
} else {
flash(__('An error occurred while subscribing to notifications.'));
}
};
export const toggleEpicSubscription = ({ state, dispatch }) => {
dispatch('requestEpicSubscriptionToggle');
axios
.post(state.toggleSubscriptionPath)
.then(() => {
dispatch('requestEpicSubscriptionToggleSuccess', {
subscribed: !state.subscribed,
});
})
.catch(() => {
dispatch('requestEpicSubscriptionToggleFailure');
});
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
......@@ -17,3 +17,7 @@ export const TOGGLE_EPIC_DUE_DATE_TYPE = 'TOGGLE_EPIC_DUE_DATE_TYPE';
export const REQUEST_EPIC_DATE_SAVE = 'REQUEST_EPIC_DATE_SAVE';
export const REQUEST_EPIC_DATE_SAVE_SUCCESS = 'REQUEST_EPIC_DATE_SAVE_SUCCESS';
export const REQUEST_EPIC_DATE_SAVE_FAILURE = 'REQUEST_EPIC_DATE_SAVE_FAILURE';
export const REQUEST_EPIC_SUBSCRIPTION_TOGGLE = 'REQUEST_EPIC_SUBSCRIPTION_TOGGLE';
export const REQUEST_EPIC_SUBSCRIPTION_TOGGLE_SUCCESS = 'REQUEST_EPIC_SUBSCRIPTION_TOGGLE_SUCCESS';
export const REQUEST_EPIC_SUBSCRIPTION_TOGGLE_FAILURE = 'REQUEST_EPIC_SUBSCRIPTION_TOGGLE_FAILURE';
......@@ -73,4 +73,15 @@ export default {
state.dueDateIsFixed = dateTypeIsFixed;
}
},
[types.REQUEST_EPIC_SUBSCRIPTION_TOGGLE](state) {
state.epicSubscriptionToggleInProgress = true;
},
[types.REQUEST_EPIC_SUBSCRIPTION_TOGGLE_SUCCESS](state, { subscribed }) {
state.epicSubscriptionToggleInProgress = false;
state.subscribed = subscribed;
},
[types.REQUEST_EPIC_SUBSCRIPTION_TOGGLE_FAILURE](state) {
state.epicSubscriptionToggleInProgress = false;
},
};
......@@ -64,5 +64,6 @@ export default () => ({
epicTodoToggleInProgress: false,
epicStartDateSaveInProgress: false,
epicDueDateSaveInProgress: false,
epicSubscriptionToggleInProgress: false,
sidebarCollapsed: false,
});
......@@ -221,5 +221,13 @@ describe('EpicSidebarComponent', () => {
.then(done)
.catch(done.fail);
});
it('renders participants list element', () => {
expect(vm.$el.querySelector('.block.participants')).not.toBeNull();
});
it('renders subscription toggle element', () => {
expect(vm.$el.querySelector('.block.subscription')).not.toBeNull();
});
});
});
import Vue from 'vue';
import SidebarSubscription from 'ee/epic/components/sidebar_items/sidebar_subscription.vue';
import createStore from 'ee/epic/store';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { mockEpicMeta, mockEpicData } from '../../mock_data';
describe('SidebarSubscriptionComponent', () => {
let vm;
let store;
beforeEach(done => {
const Component = Vue.extend(SidebarSubscription);
store = createStore();
store.dispatch('setEpicMeta', mockEpicMeta);
store.dispatch('setEpicData', mockEpicData);
vm = mountComponentWithStore(Component, {
store,
props: { sidebarCollapsed: false },
});
setTimeout(done);
});
afterEach(() => {
vm.$destroy();
});
describe('template', () => {
it('renders subscription toggle element container', () => {
expect(vm.$el.classList.contains('block')).toBe(true);
expect(vm.$el.classList.contains('subscription')).toBe(true);
});
it('renders toggle title text', () => {
expect(vm.$el.querySelector('.issuable-header-text').innerText.trim()).toBe('Notifications');
});
it('renders toggle button element', () => {
expect(vm.$el.querySelector('.js-issuable-subscribe-button button')).not.toBeNull();
});
});
});
import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
import defaultState from 'ee/epic/store/state';
......@@ -86,17 +85,12 @@ describe('Epic Store Actions', () => {
);
});
it('should show flash error', done => {
it('should show flash error', () => {
actions.requestEpicStatusChangeFailure({ commit: () => {} });
Vue.nextTick()
.then(() => {
expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(
'Unable to update this epic at this time.',
);
})
.then(done)
.catch(done.fail);
expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(
'Unable to update this epic at this time.',
);
});
});
......@@ -290,7 +284,7 @@ describe('Epic Store Actions', () => {
);
});
it('Should show flash error with message "There was an error deleting the todo." when `state.todoExists` is `true`', done => {
it('Should show flash error with message "There was an error deleting the todo." when `state.todoExists` is `true`', () => {
actions.requestEpicTodoToggleFailure(
{
commit: () => {},
......@@ -299,17 +293,12 @@ describe('Epic Store Actions', () => {
{},
);
Vue.nextTick()
.then(() => {
expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(
'There was an error deleting the todo.',
);
})
.then(done)
.catch(done.fail);
expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(
'There was an error deleting the todo.',
);
});
it('Should show flash error with message "There was an error adding a todo." when `state.todoExists` is `false`', done => {
it('Should show flash error with message "There was an error adding a todo." when `state.todoExists` is `false`', () => {
actions.requestEpicTodoToggleFailure(
{
commit: () => {},
......@@ -318,14 +307,9 @@ describe('Epic Store Actions', () => {
{},
);
Vue.nextTick()
.then(() => {
expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(
'There was an error adding a todo.',
);
})
.then(done)
.catch(done.fail);
expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(
'There was an error adding a todo.',
);
});
});
......@@ -592,7 +576,7 @@ describe('Epic Store Actions', () => {
);
});
it('should show flash error with message "An error occurred while saving the start date" when called with `dateType` as `start`', done => {
it('should show flash error with message "An error occurred while saving the start date" when called with `dateType` as `start`', () => {
actions.requestEpicDateSaveFailure(
{
commit: () => {},
......@@ -600,17 +584,12 @@ describe('Epic Store Actions', () => {
{ dateType: dateTypes.start },
);
Vue.nextTick()
.then(() => {
expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(
'An error occurred while saving the start date',
);
})
.then(done)
.catch(done.fail);
expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(
'An error occurred while saving the start date',
);
});
it('should show flash error with message "An error occurred while saving the due date" when called with `dateType` as `due`', done => {
it('should show flash error with message "An error occurred while saving the due date" when called with `dateType` as `due`', () => {
actions.requestEpicDateSaveFailure(
{
commit: () => {},
......@@ -618,14 +597,9 @@ describe('Epic Store Actions', () => {
{ dateType: dateTypes.due },
);
Vue.nextTick()
.then(() => {
expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(
'An error occurred while saving the due date',
);
})
.then(done)
.catch(done.fail);
expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(
'An error occurred while saving the due date',
);
});
});
......@@ -737,4 +711,144 @@ describe('Epic Store Actions', () => {
);
});
});
describe('requestEpicSubscriptionToggle', () => {
it('should set `state.epicSubscriptionToggleInProgress` flag to `true`', done => {
testAction(
actions.requestEpicSubscriptionToggle,
{},
state,
[{ type: 'REQUEST_EPIC_SUBSCRIPTION_TOGGLE' }],
[],
done,
);
});
});
describe('requestEpicSubscriptionToggleSuccess', () => {
it('should set `state.requestEpicSubscriptionToggleSuccess` flag to `false` and passes opposite of the value of `subscribed` as param', done => {
const stateSubscribed = {
subscribed: false,
};
testAction(
actions.requestEpicSubscriptionToggleSuccess,
{ subscribed: !stateSubscribed.subscribed },
stateSubscribed,
[
{
type: 'REQUEST_EPIC_SUBSCRIPTION_TOGGLE_SUCCESS',
payload: { subscribed: !stateSubscribed.subscribed },
},
],
[],
done,
);
});
});
describe('requestEpicSubscriptionToggleFailure', () => {
beforeEach(() => {
setFixtures('<div class="flash-container"></div>');
});
it('should set `state.requestEpicSubscriptionToggleFailure` flag to `false`', done => {
testAction(
actions.requestEpicSubscriptionToggleFailure,
{},
state,
[{ type: 'REQUEST_EPIC_SUBSCRIPTION_TOGGLE_FAILURE' }],
[],
done,
);
});
it('should show flash error with message "An error occurred while subscribing to notifications." when `state.subscribed` is `false`', () => {
actions.requestEpicSubscriptionToggleFailure(
{
commit: () => {},
state: { subscribed: false },
},
{},
);
expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(
'An error occurred while subscribing to notifications.',
);
});
it('should show flash error with message "An error occurred while unsubscribing to notifications." when `state.subscribed` is `true`', () => {
actions.requestEpicSubscriptionToggleFailure(
{
commit: () => {},
state: { subscribed: true },
},
{},
);
expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(
'An error occurred while unsubscribing to notifications.',
);
});
});
describe('toggleEpicSubscription', () => {
let mock;
const stateSubscribed = {
subscribed: false,
};
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
describe('success', () => {
it('dispatches requestEpicSubscriptionToggle and requestEpicSubscriptionToggleSuccess with param `subscribed` when request is complete', done => {
mock.onPost(/(.*)/).replyOnce(200, {});
testAction(
actions.toggleEpicSubscription,
{ subscribed: !stateSubscribed.subscribed },
stateSubscribed,
[],
[
{
type: 'requestEpicSubscriptionToggle',
},
{
type: 'requestEpicSubscriptionToggleSuccess',
payload: { subscribed: !stateSubscribed.subscribed },
},
],
done,
);
});
});
describe('failure', () => {
it('dispatches requestEpicSubscriptionToggle and requestEpicSubscriptionToggleFailure when request fails', done => {
mock.onPost(/(.*)/).replyOnce(500, {});
testAction(
actions.toggleEpicSubscription,
{ subscribed: !stateSubscribed.subscribed },
stateSubscribed,
[],
[
{
type: 'requestEpicSubscriptionToggle',
},
{
type: 'requestEpicSubscriptionToggleFailure',
},
],
done,
);
});
});
});
});
......@@ -235,4 +235,44 @@ describe('Epic Store Mutations', () => {
expect(state.dueDateIsFixed).toBe(dueDateIsFixed);
});
});
describe('REQUEST_EPIC_SUBSCRIPTION_TOGGLE', () => {
it('Should set `epicSubscriptionToggleInProgress` flag on state as `true`', () => {
const state = {
epicSubscriptionToggleInProgress: false,
};
mutations[types.REQUEST_EPIC_SUBSCRIPTION_TOGGLE](state);
expect(state.epicSubscriptionToggleInProgress).toBe(true);
});
});
describe('REQUEST_EPIC_SUBSCRIPTION_TOGGLE_SUCCESS', () => {
it('Should set `epicSubscriptionToggleInProgress` flag on state as `false` and set value of provided `subscribed` param on state', () => {
const state = {
epicSubscriptionToggleInProgress: true,
subscribed: false,
};
mutations[types.REQUEST_EPIC_SUBSCRIPTION_TOGGLE_SUCCESS](state, {
subscribed: true,
});
expect(state.epicSubscriptionToggleInProgress).toBe(false);
expect(state.subscribed).toBe(true);
});
});
describe('REQUEST_EPIC_SUBSCRIPTION_TOGGLE_FAILURE', () => {
it('Should set `epicSubscriptionToggleInProgress` flag on state as `false`', () => {
const state = {
epicSubscriptionToggleInProgress: true,
};
mutations[types.REQUEST_EPIC_SUBSCRIPTION_TOGGLE_FAILURE](state);
expect(state.epicSubscriptionToggleInProgress).toBe(false);
});
});
});
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