Commit 15fea8a4 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '231401-epic-board-sidebar-edit-notifications' into 'master'

Epic board sidebar - Edit subscribed state [RUN AS-IF-FOSS]

See merge request gitlab-org/gitlab!59214
parents 06ab148d f1f2b5b3
......@@ -21,26 +21,30 @@ export default {
components: {
GlToggle,
},
inject: ['emailsDisabled'],
data() {
return {
loading: false,
};
},
computed: {
...mapGetters(['activeBoardItem', 'projectPathForActiveIssue']),
...mapGetters(['activeBoardItem', 'projectPathForActiveIssue', 'isEpicBoard']),
isEmailsDisabled() {
return this.isEpicBoard ? this.emailsDisabled : this.activeBoardItem.emailsDisabled;
},
notificationText() {
return this.activeBoardItem.emailsDisabled
return this.isEmailsDisabled
? this.$options.i18n.header.subscribeDisabledDescription
: this.$options.i18n.header.title;
},
},
methods: {
...mapActions(['setActiveIssueSubscribed']),
...mapActions(['setActiveItemSubscribed']),
async handleToggleSubscription() {
this.loading = true;
try {
await this.setActiveIssueSubscribed({
await this.setActiveItemSubscribed({
subscribed: !this.activeBoardItem.subscribed,
projectPath: this.projectPathForActiveIssue,
});
......@@ -61,7 +65,7 @@ export default {
>
<span data-testid="notification-header-text"> {{ notificationText }} </span>
<gl-toggle
v-if="!activeBoardItem.emailsDisabled"
v-if="!isEmailsDisabled"
:value="activeBoardItem.subscribed"
:is-loading="loading"
:label="$options.i18n.header.title"
......
import { __ } from '~/locale';
import updateEpicSubscriptionMutation from '~/sidebar/queries/update_epic_subscription.mutation.graphql';
import updateEpicTitleMutation from '~/sidebar/queries/update_epic_title.mutation.graphql';
import boardBlockingIssuesQuery from './graphql/board_blocking_issues.query.graphql';
import issueSetSubscriptionMutation from './graphql/issue_set_subscription.mutation.graphql';
import issueSetTitleMutation from './graphql/issue_set_title.mutation.graphql';
export const issuableTypes = {
......@@ -63,3 +65,12 @@ export const titleQueries = {
mutation: updateEpicTitleMutation,
},
};
export const subscriptionQueries = {
[issuableTypes.issue]: {
mutation: issueSetSubscriptionMutation,
},
[issuableTypes.epic]: {
mutation: updateEpicSubscriptionMutation,
},
};
mutation issueSetSubscription($input: IssueSetSubscriptionInput!) {
issueSetSubscription(input: $input) {
updateIssuableSubscription: issueSetSubscription(input: $input) {
issue {
subscribed
}
......
......@@ -95,6 +95,7 @@ export default () => {
assigneeListsAvailable: parseBoolean($boardApp.dataset.assigneeListsAvailable),
iterationListsAvailable: parseBoolean($boardApp.dataset.iterationListsAvailable),
issuableType: issuableTypes.issue,
emailsDisabled: parseBoolean($boardApp.dataset.emailsDisabled),
},
store,
apolloProvider,
......
......@@ -10,6 +10,7 @@ import {
flashAnimationDuration,
ISSUABLE,
titleQueries,
subscriptionQueries,
} from '~/boards/constants';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import createGqClient, { fetchPolicies } from '~/lib/graphql';
......@@ -35,7 +36,6 @@ import issueCreateMutation from '../graphql/issue_create.mutation.graphql';
import issueSetDueDateMutation from '../graphql/issue_set_due_date.mutation.graphql';
import issueSetLabelsMutation from '../graphql/issue_set_labels.mutation.graphql';
import issueSetMilestoneMutation from '../graphql/issue_set_milestone.mutation.graphql';
import issueSetSubscriptionMutation from '../graphql/issue_set_subscription.mutation.graphql';
import listsIssuesQuery from '../graphql/lists_issues.query.graphql';
import * as types from './mutation_types';
......@@ -597,26 +597,31 @@ export default {
});
},
setActiveIssueSubscribed: async ({ commit, getters }, input) => {
setActiveItemSubscribed: async ({ commit, getters, state }, input) => {
const { activeBoardItem, isEpicBoard } = getters;
const { fullPath, issuableType } = state;
const workspacePath = isEpicBoard
? { groupPath: fullPath }
: { projectPath: input.projectPath };
const { data } = await gqlClient.mutate({
mutation: issueSetSubscriptionMutation,
mutation: subscriptionQueries[issuableType].mutation,
variables: {
input: {
iid: String(getters.activeBoardItem.iid),
projectPath: input.projectPath,
...workspacePath,
iid: String(activeBoardItem.iid),
subscribedState: input.subscribed,
},
},
});
if (data.issueSetSubscription?.errors?.length > 0) {
throw new Error(data.issueSetSubscription.errors);
if (data.updateIssuableSubscription?.errors?.length > 0) {
throw new Error(data.updateIssuableSubscription[issuableType].errors);
}
commit(types.UPDATE_BOARD_ITEM_BY_ID, {
itemId: getters.activeBoardItem.id,
itemId: activeBoardItem.id,
prop: 'subscribed',
value: data.issueSetSubscription.issue.subscribed,
value: data.updateIssuableSubscription[issuableType].subscribed,
});
},
......
mutation epicSetSubscription($input: EpicSetSubscriptionInput!) {
updateIssuableSubscription: epicSetSubscription(input: $input) {
epic {
subscribed
}
errors
}
}
......@@ -2,6 +2,7 @@
import { GlDrawer } from '@gitlab/ui';
import { mapState, mapActions, mapGetters } from 'vuex';
import BoardSidebarLabelsSelect from '~/boards/components/sidebar/board_sidebar_labels_select.vue';
import BoardSidebarSubscription from '~/boards/components/sidebar/board_sidebar_subscription.vue';
import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue';
import { ISSUABLE } from '~/boards/constants';
import { contentTop } from '~/lib/utils/common_utils';
......@@ -12,6 +13,7 @@ export default {
components: {
GlDrawer,
BoardSidebarLabelsSelect,
BoardSidebarSubscription,
BoardSidebarTitle,
SidebarConfidentialityWidget,
},
......@@ -51,6 +53,7 @@ export default {
issuable-type="epic"
@confidentialityUpdated="setActiveEpicConfidential($event)"
/>
<board-sidebar-subscription class="subscriptions" />
</template>
</gl-drawer>
</template>
......@@ -21,6 +21,7 @@ query ListEpics(
relativePosition
referencePath: reference(full: true)
confidential
subscribed
labels {
nodes {
...Label
......
......@@ -76,6 +76,7 @@ export default () => {
milestoneListsAvailable: false,
assigneeListsAvailable: false,
iterationListsAvailable: false,
emailsDisabled: parseBoolean($boardApp.dataset.emailsDisabled),
},
store,
apolloProvider,
......
......@@ -32,7 +32,8 @@ module EE
show_promotion: show_feature_promotion,
scoped_labels: current_board_parent.feature_available?(:scoped_labels)&.to_s,
can_update: can_update?.to_s,
can_admin_list: can_admin_list?.to_s
can_admin_list: can_admin_list?.to_s,
emails_disabled: current_board_parent.emails_disabled?.to_s
}
super.merge(data)
......
......@@ -45,7 +45,7 @@ RSpec.describe 'Epic boards sidebar', :js do
expect(page).to have_selector('[data-testid="epic-boards-sidebar"]')
find('[data-testid="close-icon"]').click
find('.gl-drawer-close-button [data-testid="close-icon"]').click
expect(page).not_to have_selector('[data-testid="epic-boards-sidebar"]')
end
......@@ -114,11 +114,11 @@ RSpec.describe 'Epic boards sidebar', :js do
page.within('.confidentiality') do
expect(page).to have_content('Not confidential')
find('[data-testid="edit-button"]').click
click_button 'Edit'
expect(page).to have_css('.sidebar-item-warning-message')
within('.sidebar-item-warning-message') do
find('[data-testid="confidential-toggle"]').click
click_button 'Turn on'
end
wait_for_requests
......@@ -127,4 +127,50 @@ RSpec.describe 'Epic boards sidebar', :js do
end
end
end
context 'in notifications subscription' do
it 'displays notifications toggle', :aggregate_failures do
click_card(card)
page.within('[data-testid="sidebar-notifications"]') do
expect(page).to have_button('Notifications')
expect(page).not_to have_content('Notifications have been disabled by the project or group owner')
end
end
it 'shows toggle as on then as off as user toggles to subscribe and unsubscribe', :aggregate_failures do
click_card(card)
click_button 'Notifications'
expect(page).to have_button('Notifications', class: 'is-checked')
click_button 'Notifications'
expect(page).not_to have_button('Notifications', class: 'is-checked')
end
context 'when notifications have been disabled' do
before do
group.update_attribute(:emails_disabled, true)
refresh_and_click_first_card
end
it 'displays a message that notifications have been disabled' do
page.within('[data-testid="sidebar-notifications"]') do
expect(page).not_to have_selector('[data-testid="notification-subscribe-toggle"]')
expect(page).to have_content('Notifications have been disabled by the project or group owner')
end
end
end
end
def refresh_and_click_first_card
page.refresh
wait_for_requests
click_card(card)
end
end
import { GlToggle, GlLoadingIcon } from '@gitlab/ui';
import { mount, createLocalVue } from '@vue/test-utils';
import { mount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
import BoardSidebarSubscription from '~/boards/components/sidebar/board_sidebar_subscription.vue';
import { createStore } from '~/boards/stores';
......@@ -9,8 +10,7 @@ import { mockActiveIssue } from '../../mock_data';
jest.mock('~/flash.js');
const localVue = createLocalVue();
localVue.use(Vuex);
Vue.use(Vuex);
describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () => {
let wrapper;
......@@ -26,8 +26,10 @@ describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () =
store.state.activeId = activeBoardItem.id;
wrapper = mount(BoardSidebarSubscription, {
localVue,
store,
provide: {
emailsDisabled: false,
},
});
};
......@@ -90,7 +92,7 @@ describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () =
describe('Board sidebar subscription component `behavior`', () => {
const mockSetActiveIssueSubscribed = (subscribedState) => {
jest.spyOn(wrapper.vm, 'setActiveIssueSubscribed').mockImplementation(async () => {
jest.spyOn(wrapper.vm, 'setActiveItemSubscribed').mockImplementation(async () => {
store.commit(types.UPDATE_BOARD_ITEM_BY_ID, {
itemId: mockActiveIssue.id,
prop: 'subscribed',
......@@ -110,7 +112,7 @@ describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () =
await wrapper.vm.$nextTick();
expect(findGlLoadingIcon().exists()).toBe(true);
expect(wrapper.vm.setActiveIssueSubscribed).toHaveBeenCalledWith({
expect(wrapper.vm.setActiveItemSubscribed).toHaveBeenCalledWith({
subscribed: true,
projectPath: 'gitlab-org/test-subgroup/gitlab-test',
});
......@@ -134,7 +136,7 @@ describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () =
await wrapper.vm.$nextTick();
expect(wrapper.vm.setActiveIssueSubscribed).toHaveBeenCalledWith({
expect(wrapper.vm.setActiveItemSubscribed).toHaveBeenCalledWith({
subscribed: false,
projectPath: 'gitlab-org/test-subgroup/gitlab-test',
});
......@@ -148,7 +150,7 @@ describe('~/boards/components/sidebar/board_sidebar_subscription_spec.vue', () =
it('flashes an error message when setting the subscribed state fails', async () => {
createComponent();
jest.spyOn(wrapper.vm, 'setActiveIssueSubscribed').mockImplementation(async () => {
jest.spyOn(wrapper.vm, 'setActiveItemSubscribed').mockImplementation(async () => {
throw new Error();
});
......
......@@ -1356,9 +1356,15 @@ describe('setActiveIssueDueDate', () => {
});
});
describe('setActiveIssueSubscribed', () => {
const state = { boardItems: { [mockActiveIssue.id]: mockActiveIssue } };
const getters = { activeBoardItem: mockActiveIssue };
describe('setActiveItemSubscribed', () => {
const state = {
boardItems: {
[mockActiveIssue.id]: mockActiveIssue,
},
fullPath: 'gitlab-org',
issuableType: 'issue',
};
const getters = { activeBoardItem: mockActiveIssue, isEpicBoard: false };
const subscribedState = true;
const input = {
subscribedState,
......@@ -1368,7 +1374,7 @@ describe('setActiveIssueSubscribed', () => {
it('should commit subscribed status', (done) => {
jest.spyOn(gqlClient, 'mutate').mockResolvedValue({
data: {
issueSetSubscription: {
updateIssuableSubscription: {
issue: {
subscribed: subscribedState,
},
......@@ -1384,7 +1390,7 @@ describe('setActiveIssueSubscribed', () => {
};
testAction(
actions.setActiveIssueSubscribed,
actions.setActiveItemSubscribed,
input,
{ ...state, ...getters },
[
......@@ -1401,9 +1407,9 @@ describe('setActiveIssueSubscribed', () => {
it('throws error if fails', async () => {
jest
.spyOn(gqlClient, 'mutate')
.mockResolvedValue({ data: { issueSetSubscription: { errors: ['failed mutation'] } } });
.mockResolvedValue({ data: { updateIssuableSubscription: { errors: ['failed mutation'] } } });
await expect(actions.setActiveIssueSubscribed({ getters }, input)).rejects.toThrow(Error);
await expect(actions.setActiveItemSubscribed({ getters }, input)).rejects.toThrow(Error);
});
});
......
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