diff --git a/app/assets/javascripts/namespaces/leave_by_url.js b/app/assets/javascripts/namespaces/leave_by_url.js new file mode 100644 index 00000000000..b817d38960c --- /dev/null +++ b/app/assets/javascripts/namespaces/leave_by_url.js @@ -0,0 +1,22 @@ +import Flash from '~/flash'; +import { __, sprintf } from '~/locale'; +import { getParameterByName } from '~/lib/utils/common_utils'; + +const PARAMETER_NAME = 'leave'; +const LEAVE_LINK_SELECTOR = '.js-leave-link'; + +export default function leaveByUrl(namespaceType) { + if (!namespaceType) throw new Error('namespaceType not provided'); + + const param = getParameterByName(PARAMETER_NAME); + if (!param) return; + + const leaveLink = document.querySelector(LEAVE_LINK_SELECTOR); + if (leaveLink) { + leaveLink.click(); + } else { + Flash( + sprintf(__('You do not have permission to leave this %{namespaceType}.'), { namespaceType }), + ); + } +} diff --git a/app/assets/javascripts/pages/groups/show/index.js b/app/assets/javascripts/pages/groups/show/index.js index af924e74f1f..82ee5ead83d 100644 --- a/app/assets/javascripts/pages/groups/show/index.js +++ b/app/assets/javascripts/pages/groups/show/index.js @@ -1,5 +1,7 @@ +import leaveByUrl from '~/namespaces/leave_by_url'; import initGroupDetails from '../shared/group_details'; document.addEventListener('DOMContentLoaded', () => { + leaveByUrl('group'); initGroupDetails(); }); diff --git a/app/assets/javascripts/pages/projects/show/index.js b/app/assets/javascripts/pages/projects/show/index.js index 7302c1ab202..869f70e7d33 100644 --- a/app/assets/javascripts/pages/projects/show/index.js +++ b/app/assets/javascripts/pages/projects/show/index.js @@ -9,6 +9,7 @@ import Activities from '~/activities'; import { ajaxGet } from '~/lib/utils/common_utils'; import GpgBadges from '~/gpg_badges'; import initReadMore from '~/read_more'; +import leaveByUrl from '~/namespaces/leave_by_url'; import Star from '../../../star'; import notificationsDropdown from '../../../notifications_dropdown'; @@ -44,4 +45,5 @@ document.addEventListener('DOMContentLoaded', () => { }); GpgBadges.fetch(); + leaveByUrl('project'); }); diff --git a/app/views/notify/member_access_granted_email.html.haml b/app/views/notify/member_access_granted_email.html.haml index 18dec806539..1c50dba9c97 100644 --- a/app/views/notify/member_access_granted_email.html.haml +++ b/app/views/notify/member_access_granted_email.html.haml @@ -1,3 +1,10 @@ +- link_end = ''.html_safe +- source_type = member_source.model_name.singular +- leave_link = polymorphic_url([member_source], leave: 1) +- source_link = link_to(member_source.human_name, member_source.web_url, target: '_blank', rel: 'noopener noreferrer') + %p - You have been granted #{member.human_access} access to the - #{link_to member_source.human_name, member_source.web_url} #{member_source.model_name.singular}. + = _('You have been granted %{access_level} access to the %{source_link} %{source_type}.').html_safe % { access_level: member.human_access, source_link: source_link, source_type: source_type } +%p + - leave_link_start = ''.html_safe % { url: leave_link } + = _('If this was a mistake you can %{leave_link_start}leave the %{source_type}%{link_end}.').html_safe % { source_type: source_type, leave_link_start: leave_link_start, link_end: link_end } diff --git a/app/views/notify/member_access_granted_email.text.erb b/app/views/notify/member_access_granted_email.text.erb index a9fb3a589a5..445009bb413 100644 --- a/app/views/notify/member_access_granted_email.text.erb +++ b/app/views/notify/member_access_granted_email.text.erb @@ -1,3 +1,8 @@ -You have been granted <%= member.human_access %> access to the <%= member_source.human_name %> <%= member_source.model_name.singular %>. +<% source_type = member_source.model_name.singular %> +<%= _('You have been granted %{access_level} access to the %{source_name} %{source_type}.') % { access_level: member.human_access, source_name: member_source.human_name, source_type: source_type } %> <%= member_source.web_url %> + +<%= _('If this was a mistake you can leave the %{source_type}.') % { source_type: source_type } %> + +<%= polymorphic_url([member_source], leave: 1) %> diff --git a/app/views/shared/members/_access_request_links.html.haml b/app/views/shared/members/_access_request_links.html.haml index f7227b9101e..eac743b5206 100644 --- a/app/views/shared/members/_access_request_links.html.haml +++ b/app/views/shared/members/_access_request_links.html.haml @@ -5,7 +5,7 @@ = link_to link_text, polymorphic_path([:leave, source, :members]), method: :delete, data: { confirm: leave_confirmation_message(source) }, - class: 'access-request-link' + class: 'access-request-link js-leave-link' - elsif requester = source.requesters.find_by(user_id: current_user.id) # rubocop: disable CodeReuse/ActiveRecord = link_to _('Withdraw Access Request'), polymorphic_path([:leave, source, :members]), method: :delete, diff --git a/changelogs/unreleased/member-access-granted-leave-email-fe.yml b/changelogs/unreleased/member-access-granted-leave-email-fe.yml new file mode 100644 index 00000000000..919a2464a4d --- /dev/null +++ b/changelogs/unreleased/member-access-granted-leave-email-fe.yml @@ -0,0 +1,5 @@ +--- +title: Leave project/group from access granted email +merge_request: 27892 +author: +type: added diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 8a51ab80d6f..9a85be6613c 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -4854,6 +4854,12 @@ msgstr "" msgid "If enabled, access to projects will be validated on an external service using their classification label." msgstr "" +msgid "If this was a mistake you can %{leave_link_start}leave the %{source_type}%{link_end}." +msgstr "" + +msgid "If this was a mistake you can leave the %{source_type}." +msgstr "" + msgid "If your HTTP repository is not publicly accessible, add authentication information to the URL: https://username:password@gitlab.company.com/group/project.git." msgstr "" @@ -10912,6 +10918,9 @@ msgstr "" msgid "You do not have any subscriptions yet" msgstr "" +msgid "You do not have permission to leave this %{namespaceType}." +msgstr "" + msgid "You don't have any applications" msgstr "" @@ -10921,6 +10930,12 @@ msgstr "" msgid "You don't have any deployments right now." msgstr "" +msgid "You have been granted %{access_level} access to the %{source_link} %{source_type}." +msgstr "" + +msgid "You have been granted %{access_level} access to the %{source_name} %{source_type}." +msgstr "" + msgid "You have been granted %{member_human_access} access to %{label}." msgstr "" diff --git a/spec/features/groups/members/leave_group_spec.rb b/spec/features/groups/members/leave_group_spec.rb index 7a91c64d7db..439803f9255 100644 --- a/spec/features/groups/members/leave_group_spec.rb +++ b/spec/features/groups/members/leave_group_spec.rb @@ -21,6 +21,20 @@ describe 'Groups > Members > Leave group' do expect(group.users).not_to include(user) end + it 'guest leaves the group by url param', :js do + group.add_guest(user) + group.add_owner(other_user) + + visit group_path(group, leave: 1) + + page.accept_confirm + + expect(find('.flash-notice')).to have_content "You left the \"#{group.full_name}\" group" + expect(page).to have_content left_group_message(group) + expect(current_path).to eq(dashboard_groups_path) + expect(group.users).not_to include(user) + end + it 'guest leaves the group as last member' do group.add_guest(user) @@ -32,7 +46,7 @@ describe 'Groups > Members > Leave group' do expect(group.users).not_to include(user) end - it 'owner leaves the group if they is not the last owner' do + it 'owner leaves the group if they are not the last owner' do group.add_owner(user) group.add_owner(other_user) @@ -44,7 +58,7 @@ describe 'Groups > Members > Leave group' do expect(group.users).not_to include(user) end - it 'owner can not leave the group if they is a last owner' do + it 'owner can not leave the group if they are the last owner' do group.add_owner(user) visit group_path(group) @@ -56,6 +70,14 @@ describe 'Groups > Members > Leave group' do expect(find(:css, '.project-members-page li', text: user.name)).not_to have_selector(:css, 'a.btn-remove') end + it 'owner can not leave the group by url param if they are the last owner', :js do + group.add_owner(user) + + visit group_path(group, leave: 1) + + expect(find('.flash-alert')).to have_content 'You do not have permission to leave this group' + end + def left_group_message(group) "You left the \"#{group.name}\"" end diff --git a/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb b/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb index 0ab29660189..a645b917568 100644 --- a/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb +++ b/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb @@ -8,10 +8,17 @@ describe 'Projects > Members > Group member cannot leave group project' do before do group.add_developer(user) sign_in(user) - visit project_path(project) end it 'user does not see a "Leave project" link' do + visit project_path(project) + expect(page).not_to have_content 'Leave project' end + + it 'renders a flash message if attempting to leave by url', :js do + visit project_path(project, leave: 1) + + expect(find('.flash-alert')).to have_content 'You do not have permission to leave this project' + end end diff --git a/spec/features/projects/members/member_leaves_project_spec.rb b/spec/features/projects/members/member_leaves_project_spec.rb index 94b29de4686..bd2ef9c07c4 100644 --- a/spec/features/projects/members/member_leaves_project_spec.rb +++ b/spec/features/projects/members/member_leaves_project_spec.rb @@ -7,13 +7,24 @@ describe 'Projects > Members > Member leaves project' do before do project.add_developer(user) sign_in(user) - visit project_path(project) end it 'user leaves project' do + visit project_path(project) + click_link 'Leave project' expect(current_path).to eq(dashboard_projects_path) expect(project.users.exists?(user.id)).to be_falsey end + + it 'user leaves project by url param', :js do + visit project_path(project, leave: 1) + + page.accept_confirm + + expect(find('.flash-notice')).to have_content "You left the \"#{project.full_name}\" project" + expect(current_path).to eq(dashboard_projects_path) + expect(project.users.exists?(user.id)).to be_falsey + end end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index fee1d701e3a..8f348b1b053 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -701,6 +701,8 @@ describe Notify do is_expected.to have_body_text project.full_name is_expected.to have_body_text project.web_url is_expected.to have_body_text project_member.human_access + is_expected.to have_body_text 'leave the project' + is_expected.to have_body_text project_url(project, leave: 1) end end @@ -1144,6 +1146,8 @@ describe Notify do is_expected.to have_body_text group.name is_expected.to have_body_text group.web_url is_expected.to have_body_text group_member.human_access + is_expected.to have_body_text 'leave the group' + is_expected.to have_body_text group_url(group, leave: 1) end end