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
fb4431b1
Commit
fb4431b1
authored
Jan 20, 2021
by
wortschi
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add Vue notifications dropdown
- Renders a Vue notification dropdown component on the project overview page
parent
a8c61b1e
Changes
16
Hide whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
584 additions
and
6 deletions
+584
-6
app/assets/javascripts/api.js
app/assets/javascripts/api.js
+31
-0
app/assets/javascripts/notifications/components/notifications_dropdown.vue
...ripts/notifications/components/notifications_dropdown.vue
+169
-0
app/assets/javascripts/notifications/components/notifications_dropdown_item.vue
.../notifications/components/notifications_dropdown_item.vue
+42
-0
app/assets/javascripts/notifications/constants.js
app/assets/javascripts/notifications/constants.js
+27
-0
app/assets/javascripts/notifications/index.js
app/assets/javascripts/notifications/index.js
+38
-0
app/assets/javascripts/pages/projects/show/index.js
app/assets/javascripts/pages/projects/show/index.js
+9
-1
app/controllers/projects_controller.rb
app/controllers/projects_controller.rb
+4
-0
app/helpers/notifications_helper.rb
app/helpers/notifications_helper.rb
+9
-0
app/views/projects/_home_panel.html.haml
app/views/projects/_home_panel.html.haml
+5
-1
config/feature_flags/development/vue_notification_dropdown.yml
...g/feature_flags/development/vue_notification_dropdown.yml
+8
-0
ee/spec/features/projects/kerberos_clone_instructions_spec.rb
...pec/features/projects/kerberos_clone_instructions_spec.rb
+4
-1
locale/gitlab.pot
locale/gitlab.pot
+3
-0
spec/features/admin/admin_disables_git_access_protocol_spec.rb
...features/admin/admin_disables_git_access_protocol_spec.rb
+12
-3
spec/features/projects/show/user_manages_notifications_spec.rb
...features/projects/show/user_manages_notifications_spec.rb
+1
-0
spec/frontend/notifications/components/notifications_dropdown_spec.js
...d/notifications/components/notifications_dropdown_spec.js
+221
-0
spec/views/projects/_home_panel.html.haml_spec.rb
spec/views/projects/_home_panel.html.haml_spec.rb
+1
-0
No files found.
app/assets/javascripts/api.js
View file @
fb4431b1
...
@@ -83,6 +83,9 @@ const Api = {
...
@@ -83,6 +83,9 @@ const Api = {
featureFlagUserList
:
'
/api/:version/projects/:id/feature_flags_user_lists/:list_iid
'
,
featureFlagUserList
:
'
/api/:version/projects/:id/feature_flags_user_lists/:list_iid
'
,
billableGroupMembersPath
:
'
/api/:version/groups/:id/billable_members
'
,
billableGroupMembersPath
:
'
/api/:version/groups/:id/billable_members
'
,
containerRegistryDetailsPath
:
'
/api/:version/registry/repositories/:id/
'
,
containerRegistryDetailsPath
:
'
/api/:version/registry/repositories/:id/
'
,
projectNotificationSettingsPath
:
'
/api/:version/projects/:id/notification_settings
'
,
groupNotificationSettingsPath
:
'
/api/:version/groups/:id/notification_settings
'
,
notificationSettingsPath
:
'
/api/:version/notification_settings
'
,
group
(
groupId
,
callback
=
()
=>
{})
{
group
(
groupId
,
callback
=
()
=>
{})
{
const
url
=
Api
.
buildUrl
(
Api
.
groupPath
).
replace
(
'
:id
'
,
groupId
);
const
url
=
Api
.
buildUrl
(
Api
.
groupPath
).
replace
(
'
:id
'
,
groupId
);
...
@@ -906,6 +909,34 @@ const Api = {
...
@@ -906,6 +909,34 @@ const Api = {
return
{
data
,
headers
};
return
{
data
,
headers
};
});
});
},
},
async
updateNotificationSettings
(
projectId
,
groupId
,
data
=
{})
{
let
url
=
Api
.
buildUrl
(
this
.
notificationSettingsPath
);
if
(
projectId
)
{
url
=
Api
.
buildUrl
(
this
.
projectNotificationSettingsPath
).
replace
(
'
:id
'
,
projectId
);
}
else
if
(
groupId
)
{
url
=
Api
.
buildUrl
(
this
.
groupNotificationSettingsPath
).
replace
(
'
:id
'
,
groupId
);
}
const
result
=
await
axios
.
put
(
url
,
data
);
return
result
;
},
async
getNotificationSettings
(
projectId
,
groupId
)
{
let
url
=
Api
.
buildUrl
(
this
.
notificationSettingsPath
);
if
(
projectId
)
{
url
=
Api
.
buildUrl
(
this
.
projectNotificationSettingsPath
).
replace
(
'
:id
'
,
projectId
);
}
else
if
(
groupId
)
{
url
=
Api
.
buildUrl
(
this
.
groupNotificationSettingsPath
).
replace
(
'
:id
'
,
groupId
);
}
const
result
=
await
axios
.
get
(
url
);
return
result
;
},
};
};
export
default
Api
;
export
default
Api
;
app/assets/javascripts/notifications/components/notifications_dropdown.vue
0 → 100644
View file @
fb4431b1
<
script
>
import
{
GlButtonGroup
,
GlButton
,
GlDropdown
,
GlDropdownDivider
,
GlTooltipDirective
,
}
from
'
@gitlab/ui
'
;
import
{
sprintf
}
from
'
~/locale
'
;
import
Api
from
'
~/api
'
;
import
NotificationsDropdownItem
from
'
./notifications_dropdown_item.vue
'
;
import
{
CUSTOM_LEVEL
,
i18n
}
from
'
../constants
'
;
export
default
{
name
:
'
NotificationsDropdown
'
,
components
:
{
GlButtonGroup
,
GlButton
,
GlDropdown
,
GlDropdownDivider
,
NotificationsDropdownItem
,
},
directives
:
{
GlTooltip
:
GlTooltipDirective
,
},
inject
:
{
containerClass
:
{
default
:
''
,
},
disabled
:
{
default
:
false
,
},
dropdownItems
:
{
default
:
[],
},
buttonSize
:
{
default
:
'
medium
'
,
},
initialNotificationLevel
:
{
default
:
''
,
},
projectId
:
{
default
:
null
,
},
groupId
:
{
default
:
null
,
},
},
data
()
{
return
{
selectedNotificationLevel
:
this
.
initialNotificationLevel
,
isLoading
:
false
,
};
},
computed
:
{
notificationLevels
()
{
return
this
.
dropdownItems
.
map
((
level
)
=>
({
level
,
title
:
this
.
$options
.
i18n
.
notificationTitles
[
level
]
||
''
,
description
:
this
.
$options
.
i18n
.
notificationDescriptions
[
level
]
||
''
,
}));
},
isCustomNotification
()
{
return
this
.
selectedNotificationLevel
===
CUSTOM_LEVEL
;
},
buttonIcon
()
{
if
(
this
.
isLoading
)
{
return
null
;
}
return
this
.
selectedNotificationLevel
===
'
disabled
'
?
'
notifications-off
'
:
'
notifications
'
;
},
buttonTooltip
()
{
const
notificationTitle
=
this
.
$options
.
i18n
.
notificationTitles
[
this
.
selectedNotificationLevel
]
||
this
.
selectedNotificationLevel
;
return
this
.
disabled
?
this
.
$options
.
i18n
.
notificationDescriptions
.
owner_disabled
:
sprintf
(
this
.
$options
.
i18n
.
notificationTooltipTitle
,
{
notification_title
:
notificationTitle
,
});
},
},
methods
:
{
selectItem
(
level
)
{
if
(
level
!==
this
.
selectedNotificationLevel
)
{
this
.
updateNotificationLevel
(
level
);
}
},
async
updateNotificationLevel
(
level
)
{
this
.
isLoading
=
true
;
try
{
await
Api
.
updateNotificationSettings
(
this
.
projectId
,
this
.
groupId
,
{
level
});
this
.
selectedNotificationLevel
=
level
;
}
catch
(
error
)
{
this
.
$toast
.
show
(
this
.
$options
.
i18n
.
updateNotificationLevelErrorMessage
,
{
type
:
'
error
'
});
}
finally
{
this
.
isLoading
=
false
;
}
},
},
customLevel
:
CUSTOM_LEVEL
,
i18n
,
};
</
script
>
<
template
>
<div
:class=
"containerClass"
>
<gl-button-group
v-if=
"isCustomNotification"
v-gl-tooltip=
"
{ title: buttonTooltip }"
data-testid="notificationButton"
:size="buttonSize"
>
<gl-button
:size=
"buttonSize"
:icon=
"buttonIcon"
:loading=
"isLoading"
:disabled=
"disabled"
/>
<gl-dropdown
:size=
"buttonSize"
:disabled=
"disabled"
>
<notifications-dropdown-item
v-for=
"item in notificationLevels"
:key=
"item.level"
:level=
"item.level"
:title=
"item.title"
:description=
"item.description"
:notification-level=
"selectedNotificationLevel"
@
item-selected=
"selectItem"
/>
<gl-dropdown-divider
/>
<notifications-dropdown-item
:key=
"$options.customLevel"
:level=
"$options.customLevel"
:title=
"$options.i18n.notificationTitles.custom"
:description=
"$options.i18n.notificationDescriptions.custom"
:notification-level=
"selectedNotificationLevel"
@
item-selected=
"selectItem"
/>
</gl-dropdown>
</gl-button-group>
<gl-dropdown
v-else
v-gl-tooltip=
"
{ title: buttonTooltip }"
data-testid="notificationButton"
:icon="buttonIcon"
:loading="isLoading"
:size="buttonSize"
:disabled="disabled"
>
<notifications-dropdown-item
v-for=
"item in notificationLevels"
:key=
"item.level"
:level=
"item.level"
:title=
"item.title"
:description=
"item.description"
:notification-level=
"selectedNotificationLevel"
@
item-selected=
"selectItem"
/>
<gl-dropdown-divider
/>
<notifications-dropdown-item
:key=
"$options.customLevel"
:level=
"$options.customLevel"
:title=
"$options.i18n.notificationTitles.custom"
:description=
"$options.i18n.notificationDescriptions.custom"
:notification-level=
"selectedNotificationLevel"
@
item-selected=
"selectItem"
/>
</gl-dropdown>
</div>
</
template
>
app/assets/javascripts/notifications/components/notifications_dropdown_item.vue
0 → 100644
View file @
fb4431b1
<
script
>
import
{
GlDropdownItem
}
from
'
@gitlab/ui
'
;
export
default
{
name
:
'
NotificationsDropdownItem
'
,
components
:
{
GlDropdownItem
,
},
props
:
{
level
:
{
type
:
String
,
required
:
true
,
},
title
:
{
type
:
String
,
required
:
true
,
},
description
:
{
type
:
String
,
required
:
true
,
},
notificationLevel
:
{
type
:
String
,
required
:
true
,
},
},
computed
:
{
isActive
()
{
return
this
.
notificationLevel
===
this
.
level
;
},
},
};
</
script
>
<
template
>
<gl-dropdown-item
is-check-item
:is-checked=
"isActive"
@
click=
"$emit('item-selected', level)"
>
<div
class=
"gl-display-flex gl-flex-direction-column"
>
<span
class=
"gl-font-weight-bold"
>
{{
title
}}
</span>
<span
class=
"gl-text-gray-500"
>
{{
description
}}
</span>
</div>
</gl-dropdown-item>
</
template
>
app/assets/javascripts/notifications/constants.js
0 → 100644
View file @
fb4431b1
import
{
__
,
s__
}
from
'
~/locale
'
;
export
const
CUSTOM_LEVEL
=
'
custom
'
;
export
const
i18n
=
{
notificationTitles
:
{
participating
:
s__
(
'
NotificationLevel|Participate
'
),
mention
:
s__
(
'
NotificationLevel|On mention
'
),
watch
:
s__
(
'
NotificationLevel|Watch
'
),
global
:
s__
(
'
NotificationLevel|Global
'
),
disabled
:
s__
(
'
NotificationLevel|Disabled
'
),
custom
:
s__
(
'
NotificationLevel|Custom
'
),
},
notificationTooltipTitle
:
__
(
'
Notification setting - %{notification_title}
'
),
notificationDescriptions
:
{
participating
:
__
(
'
You will only receive notifications for threads you have participated in
'
),
mention
:
__
(
'
You will receive notifications only for comments in which you were @mentioned
'
),
watch
:
__
(
'
You will receive notifications for any activity
'
),
disabled
:
__
(
'
You will not get any notifications via email
'
),
global
:
__
(
'
Use your global notification setting
'
),
custom
:
__
(
'
You will only receive notifications for the events you choose
'
),
owner_disabled
:
__
(
'
Notifications have been disabled by the project or group owner
'
),
},
updateNotificationLevelErrorMessage
:
__
(
'
An error occured while updating the notification settings. Please try again.
'
,
),
};
app/assets/javascripts/notifications/index.js
0 → 100644
View file @
fb4431b1
import
Vue
from
'
vue
'
;
import
{
GlToast
}
from
'
@gitlab/ui
'
;
import
{
parseBoolean
}
from
'
~/lib/utils/common_utils
'
;
import
NotificationsDropdown
from
'
./components/notifications_dropdown.vue
'
;
Vue
.
use
(
GlToast
);
export
default
()
=>
{
const
el
=
document
.
querySelector
(
'
.js-vue-notification-dropdown
'
);
if
(
!
el
)
return
false
;
const
{
containerClass
,
buttonSize
,
disabled
,
dropdownItems
,
notificationLevel
,
projectId
,
groupId
,
}
=
el
.
dataset
;
return
new
Vue
({
el
,
provide
:
{
containerClass
,
buttonSize
,
disabled
:
parseBoolean
(
disabled
),
dropdownItems
:
JSON
.
parse
(
dropdownItems
),
initialNotificationLevel
:
notificationLevel
,
projectId
,
groupId
,
},
render
(
h
)
{
return
h
(
NotificationsDropdown
);
},
});
};
app/assets/javascripts/pages/projects/show/index.js
View file @
fb4431b1
...
@@ -12,6 +12,7 @@ import notificationsDropdown from '../../../notifications_dropdown';
...
@@ -12,6 +12,7 @@ import notificationsDropdown from '../../../notifications_dropdown';
import
{
showLearnGitLabProjectPopover
}
from
'
~/onboarding_issues
'
;
import
{
showLearnGitLabProjectPopover
}
from
'
~/onboarding_issues
'
;
import
initInviteMembersTrigger
from
'
~/invite_members/init_invite_members_trigger
'
;
import
initInviteMembersTrigger
from
'
~/invite_members/init_invite_members_trigger
'
;
import
initInviteMembersModal
from
'
~/invite_members/init_invite_members_modal
'
;
import
initInviteMembersModal
from
'
~/invite_members/init_invite_members_modal
'
;
import
initVueNotificationsDropdown
from
'
~/notifications
'
;
initReadMore
();
initReadMore
();
new
Star
();
// eslint-disable-line no-new
new
Star
();
// eslint-disable-line no-new
...
@@ -42,7 +43,14 @@ leaveByUrl('project');
...
@@ -42,7 +43,14 @@ leaveByUrl('project');
showLearnGitLabProjectPopover
();
showLearnGitLabProjectPopover
();
notificationsDropdown
();
if
(
gon
.
features
?.
vueNotificationDropdown
)
{
initVueNotificationsDropdown
();
}
else
{
notificationsDropdown
();
}
initVueNotificationsDropdown
();
new
ShortcutsNavigation
();
// eslint-disable-line no-new
new
ShortcutsNavigation
();
// eslint-disable-line no-new
initInviteMembersTrigger
();
initInviteMembersTrigger
();
...
...
app/controllers/projects_controller.rb
View file @
fb4431b1
...
@@ -31,6 +31,10 @@ class ProjectsController < Projects::ApplicationController
...
@@ -31,6 +31,10 @@ class ProjectsController < Projects::ApplicationController
# Project Export Rate Limit
# Project Export Rate Limit
before_action
:export_rate_limit
,
only:
[
:export
,
:download_export
,
:generate_new_export
]
before_action
:export_rate_limit
,
only:
[
:export
,
:download_export
,
:generate_new_export
]
before_action
do
push_frontend_feature_flag
(
:vue_notification_dropdown
,
@project
,
default_enabled: :yaml
)
end
before_action
only:
[
:edit
]
do
before_action
only:
[
:edit
]
do
push_frontend_feature_flag
(
:approval_suggestions
,
@project
,
default_enabled:
true
)
push_frontend_feature_flag
(
:approval_suggestions
,
@project
,
default_enabled:
true
)
push_frontend_feature_flag
(
:allow_editing_commit_messages
,
@project
)
push_frontend_feature_flag
(
:allow_editing_commit_messages
,
@project
)
...
...
app/helpers/notifications_helper.rb
View file @
fb4431b1
...
@@ -125,4 +125,13 @@ module NotificationsHelper
...
@@ -125,4 +125,13 @@ module NotificationsHelper
def
can_read_project?
(
project
)
def
can_read_project?
(
project
)
can?
(
current_user
,
:read_project
,
project
)
can?
(
current_user
,
:read_project
,
project
)
end
end
def
notification_dropdown_items
(
notification_setting
)
NotificationSetting
.
levels
.
each_key
.
map
do
|
level
|
next
if
level
==
"custom"
next
if
level
==
"global"
&&
notification_setting
.
source
.
nil?
level
end
.
compact
end
end
end
app/views/projects/_home_panel.html.haml
View file @
fb4431b1
...
@@ -46,7 +46,11 @@
...
@@ -46,7 +46,11 @@
.project-repo-buttons.col-md-12.col-lg-6.d-inline-flex.flex-wrap.justify-content-lg-end
.project-repo-buttons.col-md-12.col-lg-6.d-inline-flex.flex-wrap.justify-content-lg-end
-
if
current_user
-
if
current_user
.d-inline-flex
.d-inline-flex
=
render
'shared/notifications/new_button'
,
notification_setting:
@notification_setting
,
btn_class:
'btn-xs'
,
dropdown_container_class:
'gl-mr-3'
,
emails_disabled:
emails_disabled
-
if
Feature
.
enabled?
(
:vue_notification_dropdown
,
@project
,
default_enabled: :yaml
)
-
if
@notification_setting
.js-vue-notification-dropdown
{
data:
{
button_size:
"small"
,
disabled:
emails_disabled
,
dropdown_items:
notification_dropdown_items
(
@notification_setting
).
to_json
,
notification_level:
@notification_setting
.
level
,
help_page_path:
help_page_path
(
'user/profile/notifications'
),
project_id:
@project
.
id
,
container_class:
'gl-mr-3 gl-mt-5 gl-vertical-align-top'
}
}
-
else
=
render
'shared/notifications/new_button'
,
notification_setting:
@notification_setting
,
btn_class:
'btn-xs'
,
dropdown_container_class:
'gl-mr-3'
,
emails_disabled:
emails_disabled
.count-buttons.d-inline-flex
.count-buttons.d-inline-flex
=
render
'projects/buttons/star'
=
render
'projects/buttons/star'
...
...
config/feature_flags/development/vue_notification_dropdown.yml
0 → 100644
View file @
fb4431b1
---
name
:
vue_notification_dropdown
introduced_by_url
:
https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52068
rollout_issue_url
:
milestone
:
'
13.8'
type
:
development
group
:
group::optimize
default_enabled
:
false
ee/spec/features/projects/kerberos_clone_instructions_spec.rb
View file @
fb4431b1
...
@@ -29,7 +29,10 @@ RSpec.describe 'Kerberos clone instructions', :js do
...
@@ -29,7 +29,10 @@ RSpec.describe 'Kerberos clone instructions', :js do
it
'shows the Kerberos clone information'
do
it
'shows the Kerberos clone information'
do
resize_screen_xs
resize_screen_xs
visit_project
visit_project
find
(
'.dropdown-toggle'
).
click
within
(
'.js-mobile-git-clone'
)
do
find
(
'.dropdown-toggle'
).
click
end
expect
(
page
).
to
have_content
(
'Copy KRB5 clone URL'
)
expect
(
page
).
to
have_content
(
'Copy KRB5 clone URL'
)
end
end
...
...
locale/gitlab.pot
View file @
fb4431b1
...
@@ -3054,6 +3054,9 @@ msgstr ""
...
@@ -3054,6 +3054,9 @@ msgstr ""
msgid "An error occured while saving changes: %{error}"
msgid "An error occured while saving changes: %{error}"
msgstr ""
msgstr ""
msgid "An error occured while updating the notification settings. Please try again."
msgstr ""
msgid "An error occurred adding a draft to the thread."
msgid "An error occurred adding a draft to the thread."
msgstr ""
msgstr ""
...
...
spec/features/admin/admin_disables_git_access_protocol_spec.rb
View file @
fb4431b1
...
@@ -37,7 +37,10 @@ RSpec.describe 'Admin disables Git access protocol', :js do
...
@@ -37,7 +37,10 @@ RSpec.describe 'Admin disables Git access protocol', :js do
it
'shows only the SSH clone information'
do
it
'shows only the SSH clone information'
do
resize_screen_xs
resize_screen_xs
visit_project
visit_project
find
(
'.dropdown-toggle'
).
click
within
(
'.js-mobile-git-clone'
)
do
find
(
'.dropdown-toggle'
).
click
end
expect
(
page
).
to
have_content
(
'Copy SSH clone URL'
)
expect
(
page
).
to
have_content
(
'Copy SSH clone URL'
)
expect
(
page
).
not_to
have_content
(
'Copy HTTP clone URL'
)
expect
(
page
).
not_to
have_content
(
'Copy HTTP clone URL'
)
...
@@ -66,7 +69,10 @@ RSpec.describe 'Admin disables Git access protocol', :js do
...
@@ -66,7 +69,10 @@ RSpec.describe 'Admin disables Git access protocol', :js do
it
'shows only the HTTP clone information'
do
it
'shows only the HTTP clone information'
do
resize_screen_xs
resize_screen_xs
visit_project
visit_project
find
(
'.dropdown-toggle'
).
click
within
(
'.js-mobile-git-clone'
)
do
find
(
'.dropdown-toggle'
).
click
end
expect
(
page
).
to
have_content
(
'Copy HTTP clone URL'
)
expect
(
page
).
to
have_content
(
'Copy HTTP clone URL'
)
expect
(
page
).
not_to
have_content
(
'Copy SSH clone URL'
)
expect
(
page
).
not_to
have_content
(
'Copy SSH clone URL'
)
...
@@ -97,7 +103,10 @@ RSpec.describe 'Admin disables Git access protocol', :js do
...
@@ -97,7 +103,10 @@ RSpec.describe 'Admin disables Git access protocol', :js do
it
'shows both SSH and HTTP clone information'
do
it
'shows both SSH and HTTP clone information'
do
resize_screen_xs
resize_screen_xs
visit_project
visit_project
find
(
'.dropdown-toggle'
).
click
within
(
'.js-mobile-git-clone'
)
do
find
(
'.dropdown-toggle'
).
click
end
expect
(
page
).
to
have_content
(
'Copy HTTP clone URL'
)
expect
(
page
).
to
have_content
(
'Copy HTTP clone URL'
)
expect
(
page
).
to
have_content
(
'Copy SSH clone URL'
)
expect
(
page
).
to
have_content
(
'Copy SSH clone URL'
)
...
...
spec/features/projects/show/user_manages_notifications_spec.rb
View file @
fb4431b1
...
@@ -6,6 +6,7 @@ RSpec.describe 'Projects > Show > User manages notifications', :js do
...
@@ -6,6 +6,7 @@ RSpec.describe 'Projects > Show > User manages notifications', :js do
let
(
:project
)
{
create
(
:project
,
:public
,
:repository
)
}
let
(
:project
)
{
create
(
:project
,
:public
,
:repository
)
}
before
do
before
do
stub_feature_flags
(
vue_notification_dropdown:
false
)
sign_in
(
project
.
owner
)
sign_in
(
project
.
owner
)
end
end
...
...
spec/frontend/notifications/components/notifications_dropdown_spec.js
0 → 100644
View file @
fb4431b1
import
axios
from
'
axios
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
createMockDirective
,
getBinding
}
from
'
helpers/vue_mock_directive
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
import
{
GlButtonGroup
,
GlDropdown
,
GlDropdownItem
}
from
'
@gitlab/ui
'
;
import
httpStatus
from
'
~/lib/utils/http_status
'
;
import
NotificationsDropdown
from
'
~/notifications/components/notifications_dropdown.vue
'
;
import
NotificationsDropdownItem
from
'
~/notifications/components/notifications_dropdown_item.vue
'
;
const
mockDropdownItems
=
[
'
global
'
,
'
watch
'
,
'
participating
'
,
'
mention
'
,
'
disabled
'
];
const
mockToastShow
=
jest
.
fn
();
describe
(
'
NotificationsDropdown
'
,
()
=>
{
let
wrapper
;
let
mockAxios
;
function
createComponent
(
injectedProperties
=
{})
{
return
shallowMount
(
NotificationsDropdown
,
{
stubs
:
{
GlButtonGroup
,
GlDropdown
,
GlDropdownItem
,
NotificationsDropdownItem
,
},
directives
:
{
GlTooltip
:
createMockDirective
(),
},
provide
:
{
...
injectedProperties
,
},
mocks
:
{
$toast
:
{
show
:
mockToastShow
,
},
},
});
}
const
findButtonGroup
=
()
=>
wrapper
.
find
(
GlButtonGroup
);
const
findDropdown
=
()
=>
wrapper
.
find
(
GlDropdown
);
const
findByTestId
=
(
testId
)
=>
wrapper
.
find
(
`[data-testid="
${
testId
}
"]`
);
const
findAllNotificationsDropdownItems
=
()
=>
wrapper
.
findAll
(
NotificationsDropdownItem
);
const
findDropdownItemAt
=
(
index
)
=>
findAllNotificationsDropdownItems
().
at
(
index
).
find
(
GlDropdownItem
);
const
clickDropdownItemAt
=
async
(
index
)
=>
{
const
dropdownItem
=
findDropdownItemAt
(
index
);
dropdownItem
.
vm
.
$emit
(
'
click
'
);
await
waitForPromises
();
};
beforeEach
(()
=>
{
gon
.
api_version
=
'
v4
'
;
mockAxios
=
new
MockAdapter
(
axios
);
});
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
mockAxios
.
restore
();
});
describe
(
'
template
'
,
()
=>
{
describe
(
'
when notification level is "custom"
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
createComponent
({
dropdownItems
:
mockDropdownItems
,
initialNotificationLevel
:
'
custom
'
,
});
});
it
(
'
renders a button group
'
,
()
=>
{
expect
(
findButtonGroup
().
exists
()).
toBe
(
true
);
});
});
describe
(
'
when notification level is not "custom"
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
createComponent
({
dropdownItems
:
mockDropdownItems
,
initialNotificationLevel
:
'
global
'
,
});
});
it
(
'
does not render a button group
'
,
()
=>
{
expect
(
findButtonGroup
().
exists
()).
toBe
(
false
);
});
});
describe
(
'
button tooltip
'
,
()
=>
{
const
tooltipTitlePrefix
=
'
Notification setting
'
;
it
.
each
`
level | title
${
'
global
'
}
|
${
'
Global
'
}
${
'
watch
'
}
|
${
'
Watch
'
}
${
'
participating
'
}
|
${
'
Participate
'
}
${
'
mention
'
}
|
${
'
On mention
'
}
${
'
disabled
'
}
|
${
'
Disabled
'
}
${
'
custom
'
}
|
${
'
Custom
'
}
`
(
`renders "
${
tooltipTitlePrefix
}
- $title" for "$level" level`
,
({
level
,
title
})
=>
{
wrapper
=
createComponent
({
dropdownItems
:
mockDropdownItems
,
initialNotificationLevel
:
level
,
});
const
tooltipElement
=
findByTestId
(
'
notificationButton
'
);
const
tooltip
=
getBinding
(
tooltipElement
.
element
,
'
gl-tooltip
'
);
expect
(
tooltip
.
value
.
title
).
toBe
(
`
${
tooltipTitlePrefix
}
-
${
title
}
`
);
});
});
describe
(
'
button icon
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
createComponent
({
dropdownItems
:
mockDropdownItems
,
initialNotificationLevel
:
'
disabled
'
,
});
});
it
(
'
renders the "notifications-off" icon when notification level is "disabled"
'
,
()
=>
{
expect
(
findDropdown
().
props
(
'
icon
'
)).
toBe
(
'
notifications-off
'
);
});
it
(
'
renders the "notifications" icon when notification level is not "disabled"
'
,
()
=>
{
wrapper
=
createComponent
({
dropdownItems
:
mockDropdownItems
,
initialNotificationLevel
:
'
global
'
,
});
expect
(
findDropdown
().
props
(
'
icon
'
)).
toBe
(
'
notifications
'
);
});
});
describe
(
'
dropdown items
'
,
()
=>
{
it
.
each
`
dropdownIndex | level | title | description
${
0
}
|
${
'
global
'
}
|
${
'
Global
'
}
|
${
'
Use your global notification setting
'
}
${
1
}
|
${
'
watch
'
}
|
${
'
Watch
'
}
|
${
'
You will receive notifications for any activity
'
}
${
2
}
|
${
'
participating
'
}
|
${
'
Participate
'
}
|
${
'
You will only receive notifications for threads you have participated in
'
}
${
3
}
|
${
'
mention
'
}
|
${
'
On mention
'
}
|
${
'
You will receive notifications only for comments in which you were @mentioned
'
}
${
4
}
|
${
'
disabled
'
}
|
${
'
Disabled
'
}
|
${
'
You will not get any notifications via email
'
}
${
5
}
|
${
'
custom
'
}
|
${
'
Custom
'
}
|
${
'
You will only receive notifications for the events you choose
'
}
`
(
'
displays "$title" and "$description"
'
,
({
dropdownIndex
,
title
,
description
})
=>
{
wrapper
=
createComponent
({
dropdownItems
:
mockDropdownItems
,
initialNotificationLevel
:
'
global
'
,
});
expect
(
findAllNotificationsDropdownItems
().
at
(
dropdownIndex
).
props
(
'
title
'
)).
toBe
(
title
);
expect
(
findAllNotificationsDropdownItems
().
at
(
dropdownIndex
).
props
(
'
description
'
)).
toBe
(
description
,
);
});
});
});
describe
(
'
when selecting an item
'
,
()
=>
{
beforeEach
(()
=>
{
jest
.
spyOn
(
axios
,
'
put
'
);
});
it
.
each
`
projectId | groupId | endpointUrl | endpointType | condition
${
1
}
|
${
null
}
|
${
'
/api/v4/projects/1/notification_settings
'
}
|
${
'
project notifications
'
}
|
${
'
a projectId is given
'
}
${
null
}
|
${
1
}
|
${
'
/api/v4/groups/1/notification_settings
'
}
|
${
'
group notifications
'
}
|
${
'
a groupId is given
'
}
${
null
}
|
${
null
}
|
${
'
/api/v4/notification_settings
'
}
|
${
'
global notifications
'
}
|
${
'
when neither projectId nor groupId are given
'
}
`
(
'
calls the $endpointType endpoint when $condition
'
,
async
({
projectId
,
groupId
,
endpointUrl
})
=>
{
wrapper
=
createComponent
({
dropdownItems
:
mockDropdownItems
,
initialNotificationLevel
:
'
global
'
,
projectId
,
groupId
,
});
await
clickDropdownItemAt
(
1
);
expect
(
axios
.
put
).
toHaveBeenCalledWith
(
endpointUrl
,
{
level
:
'
watch
'
,
});
},
);
it
(
'
updates the selectedNotificationLevel and marks the item with a checkmark
'
,
async
()
=>
{
mockAxios
.
onPut
(
'
/api/v4/notification_settings
'
).
reply
(
httpStatus
.
OK
,
{});
wrapper
=
createComponent
({
dropdownItems
:
mockDropdownItems
,
initialNotificationLevel
:
'
global
'
,
});
const
dropdownItem
=
findDropdownItemAt
(
1
);
await
clickDropdownItemAt
(
1
);
expect
(
wrapper
.
vm
.
selectedNotificationLevel
).
toBe
(
'
watch
'
);
expect
(
dropdownItem
.
props
(
'
isChecked
'
)).
toBe
(
true
);
});
it
(
"
won't update the selectedNotificationLevel and shows a toast message when the request fails and
"
,
async
()
=>
{
mockAxios
.
onPut
(
'
/api/v4/notification_settings
'
).
reply
(
httpStatus
.
NOT_FOUND
,
{});
wrapper
=
createComponent
({
dropdownItems
:
mockDropdownItems
,
initialNotificationLevel
:
'
global
'
,
});
await
clickDropdownItemAt
(
1
);
expect
(
wrapper
.
vm
.
selectedNotificationLevel
).
toBe
(
'
global
'
);
expect
(
mockToastShow
,
).
toHaveBeenCalledWith
(
'
An error occured while updating the notification settings. Please try again.
'
,
{
type
:
'
error
'
},
);
});
});
});
spec/views/projects/_home_panel.html.haml_spec.rb
View file @
fb4431b1
...
@@ -9,6 +9,7 @@ RSpec.describe 'projects/_home_panel' do
...
@@ -9,6 +9,7 @@ RSpec.describe 'projects/_home_panel' do
let
(
:project
)
{
create
(
:project
)
}
let
(
:project
)
{
create
(
:project
)
}
before
do
before
do
stub_feature_flags
(
vue_notification_dropdown:
false
)
assign
(
:project
,
project
)
assign
(
:project
,
project
)
allow
(
view
).
to
receive
(
:current_user
).
and_return
(
user
)
allow
(
view
).
to
receive
(
:current_user
).
and_return
(
user
)
...
...
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