Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
21dc2ef9
Commit
21dc2ef9
authored
Apr 13, 2021
by
Florie Guibert
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Epic board sidebar - Edit subscribed state
Allow to turn notification on or off from epic board sidebar for epic
parent
3230030d
Changes
13
Show whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
124 additions
and
32 deletions
+124
-32
app/assets/javascripts/boards/components/sidebar/board_sidebar_subscription.vue
.../boards/components/sidebar/board_sidebar_subscription.vue
+9
-5
app/assets/javascripts/boards/constants.js
app/assets/javascripts/boards/constants.js
+11
-0
app/assets/javascripts/boards/graphql/issue_set_subscription.mutation.graphql
...ts/boards/graphql/issue_set_subscription.mutation.graphql
+1
-1
app/assets/javascripts/boards/index.js
app/assets/javascripts/boards/index.js
+1
-0
app/assets/javascripts/boards/stores/actions.js
app/assets/javascripts/boards/stores/actions.js
+14
-9
app/assets/javascripts/sidebar/queries/update_epic_subscription.mutation.graphql
...sidebar/queries/update_epic_subscription.mutation.graphql
+8
-0
ee/app/assets/javascripts/boards/components/epic_board_content_sidebar.vue
...ascripts/boards/components/epic_board_content_sidebar.vue
+3
-0
ee/app/assets/javascripts/boards/graphql/lists_epics.query.graphql
...sets/javascripts/boards/graphql/lists_epics.query.graphql
+1
-0
ee/app/assets/javascripts/epic_boards/index.js
ee/app/assets/javascripts/epic_boards/index.js
+1
-0
ee/app/helpers/ee/boards_helper.rb
ee/app/helpers/ee/boards_helper.rb
+2
-1
ee/spec/features/epic_boards/epic_boards_sidebar_spec.rb
ee/spec/features/epic_boards/epic_boards_sidebar_spec.rb
+50
-1
spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js
...rds/components/sidebar/board_sidebar_subscription_spec.js
+10
-8
spec/frontend/boards/stores/actions_spec.js
spec/frontend/boards/stores/actions_spec.js
+13
-7
No files found.
app/assets/javascripts/boards/components/sidebar/board_sidebar_subscription.vue
View file @
21dc2ef9
...
...
@@ -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
.
e
mailsDisabled
return
this
.
isE
mailsDisabled
?
this
.
$options
.
i18n
.
header
.
subscribeDisabledDescription
:
this
.
$options
.
i18n
.
header
.
title
;
},
},
methods
:
{
...
mapActions
([
'
setActiveI
ssue
Subscribed
'
]),
...
mapActions
([
'
setActiveI
tem
Subscribed
'
]),
async
handleToggleSubscription
()
{
this
.
loading
=
true
;
try
{
await
this
.
setActiveI
ssue
Subscribed
({
await
this
.
setActiveI
tem
Subscribed
({
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.e
mailsDisabled"
v-if=
"!
isE
mailsDisabled"
:value=
"activeBoardItem.subscribed"
:is-loading=
"loading"
:label=
"$options.i18n.header.title"
...
...
app/assets/javascripts/boards/constants.js
View file @
21dc2ef9
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
,
},
};
app/assets/javascripts/boards/graphql/issue_set_subscription.mutation.graphql
View file @
21dc2ef9
mutation
issueSetSubscription
(
$input
:
IssueSetSubscriptionInput
!)
{
issueSetSubscription
(
input
:
$input
)
{
updateIssuableSubscription
:
issueSetSubscription
(
input
:
$input
)
{
issue
{
subscribed
}
...
...
app/assets/javascripts/boards/index.js
View file @
21dc2ef9
...
...
@@ -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
,
...
...
app/assets/javascripts/boards/stores/actions.js
View file @
21dc2ef9
...
...
@@ -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
:
issueSetSubscriptionM
utation
,
mutation
:
subscriptionQueries
[
issuableType
].
m
utation
,
variables
:
{
input
:
{
iid
:
String
(
getters
.
activeBoardItem
.
iid
)
,
projectPath
:
input
.
projectPath
,
...
workspacePath
,
iid
:
String
(
activeBoardItem
.
iid
)
,
subscribedState
:
input
.
subscribed
,
},
},
});
if
(
data
.
issueSet
Subscription
?.
errors
?.
length
>
0
)
{
throw
new
Error
(
data
.
issueSetSubscription
.
errors
);
if
(
data
.
updateIssuable
Subscription
?.
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
,
});
},
...
...
app/assets/javascripts/sidebar/queries/update_epic_subscription.mutation.graphql
0 → 100644
View file @
21dc2ef9
mutation
epicSetSubscription
(
$input
:
EpicSetSubscriptionInput
!)
{
updateIssuableSubscription
:
epicSetSubscription
(
input
:
$input
)
{
epic
{
subscribed
}
errors
}
}
ee/app/assets/javascripts/boards/components/epic_board_content_sidebar.vue
View file @
21dc2ef9
...
...
@@ -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>
ee/app/assets/javascripts/boards/graphql/lists_epics.query.graphql
View file @
21dc2ef9
...
...
@@ -21,6 +21,7 @@ query ListEpics(
relativePosition
referencePath
:
reference
(
full
:
true
)
confidential
subscribed
labels
{
nodes
{
...
Label
...
...
ee/app/assets/javascripts/epic_boards/index.js
View file @
21dc2ef9
...
...
@@ -76,6 +76,7 @@ export default () => {
milestoneListsAvailable
:
false
,
assigneeListsAvailable
:
false
,
iterationListsAvailable
:
false
,
emailsDisabled
:
parseBoolean
(
$boardApp
.
dataset
.
emailsDisabled
),
},
store
,
apolloProvider
,
...
...
ee/app/helpers/ee/boards_helper.rb
View file @
21dc2ef9
...
...
@@ -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
)
...
...
ee/spec/features/epic_boards/epic_boards_sidebar_spec.rb
View file @
21dc2ef9
...
...
@@ -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
...
...
@@ -127,4 +127,53 @@ 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_selector
(
'[data-testid="notification-subscribe-toggle"]'
)
expect
(
page
).
to
have_content
(
'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
)
toggle
=
find
(
'[data-testid="notification-subscribe-toggle"]'
)
toggle
.
click
expect
(
toggle
).
to
have_css
(
"button.is-checked"
)
toggle
.
click
expect
(
toggle
).
not_to
have_css
(
"button.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
spec/frontend/boards/components/sidebar/board_sidebar_subscription_spec.js
View file @
21dc2ef9
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
,
'
setActiveI
ssue
Subscribed
'
).
mockImplementation
(
async
()
=>
{
jest
.
spyOn
(
wrapper
.
vm
,
'
setActiveI
tem
Subscribed
'
).
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
.
setActiveI
ssue
Subscribed
).
toHaveBeenCalledWith
({
expect
(
wrapper
.
vm
.
setActiveI
tem
Subscribed
).
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
.
setActiveI
ssue
Subscribed
).
toHaveBeenCalledWith
({
expect
(
wrapper
.
vm
.
setActiveI
tem
Subscribed
).
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
,
'
setActiveI
ssue
Subscribed
'
).
mockImplementation
(
async
()
=>
{
jest
.
spyOn
(
wrapper
.
vm
,
'
setActiveI
tem
Subscribed
'
).
mockImplementation
(
async
()
=>
{
throw
new
Error
();
});
...
...
spec/frontend/boards/stores/actions_spec.js
View file @
21dc2ef9
...
...
@@ -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
:
{
issueSet
Subscription
:
{
updateIssuable
Subscription
:
{
issue
:
{
subscribed
:
subscribedState
,
},
...
...
@@ -1384,7 +1390,7 @@ describe('setActiveIssueSubscribed', () => {
};
testAction
(
actions
.
setActiveI
ssue
Subscribed
,
actions
.
setActiveI
tem
Subscribed
,
input
,
{
...
state
,
...
getters
},
[
...
...
@@ -1401,9 +1407,9 @@ describe('setActiveIssueSubscribed', () => {
it
(
'
throws error if fails
'
,
async
()
=>
{
jest
.
spyOn
(
gqlClient
,
'
mutate
'
)
.
mockResolvedValue
({
data
:
{
issueSet
Subscription
:
{
errors
:
[
'
failed mutation
'
]
}
}
});
.
mockResolvedValue
({
data
:
{
updateIssuable
Subscription
:
{
errors
:
[
'
failed mutation
'
]
}
}
});
await
expect
(
actions
.
setActiveI
ssue
Subscribed
({
getters
},
input
)).
rejects
.
toThrow
(
Error
);
await
expect
(
actions
.
setActiveI
tem
Subscribed
({
getters
},
input
)).
rejects
.
toThrow
(
Error
);
});
});
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment