Skip to content

Commit ff77208

Browse files
authored
Admin can revoke user invites from Manage Users page (bigbluebutton#5846)
* Admin can revoke user invites from Manage Users page * Added tests for revoking user invites * - checked for user count changing when testing for #destroy for invitations\n- fixed locale strings for revoking invites\n- other minor fixes * Added license header for Changed Revoke Invite to Revoke with archive box x mark as icon
1 parent 8aad7b2 commit ff77208

File tree

7 files changed

+83
-7
lines changed

7 files changed

+83
-7
lines changed

app/assets/locales/en.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,8 @@
230230
"empty_invited_users_subtext": "When a user's status gets changed to invited, they will appear here.",
231231
"invited": {
232232
"time_sent": "Time Sent",
233-
"valid": "Valid"
233+
"valid": "Valid",
234+
"revoke": "Revoke"
234235
}
235236
},
236237
"server_rooms": {
@@ -430,7 +431,8 @@
430431
"role_permission_updated": "The role permission has been updated."
431432
},
432433
"invitations": {
433-
"invitation_sent": "An invitation has been sent."
434+
"invitation_sent": "An invitation has been sent.",
435+
"invitation_revoked": "An invitation has been revoked"
434436
}
435437
},
436438
"error": {

app/controllers/api/v1/admin/invitations_controller.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,15 @@ def create
6060
logger.error "Failed to send invitations to #{params[:invitations][:emails]} - #{e}"
6161
render_error status: :bad_request
6262
end
63+
64+
def destroy
65+
invitation = Invitation.find(params[:id])
66+
if invitation.destroy
67+
render_data status: :ok
68+
else
69+
render_error status: :not_found
70+
end
71+
end
6372
end
6473
end
6574
end

app/javascript/components/admin/manage_users/InvitedUsersTable.jsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@
1616

1717
import React, { useState } from 'react';
1818
import PropTypes from 'prop-types';
19-
import { Table } from 'react-bootstrap';
19+
import { Table, Dropdown } from 'react-bootstrap';
2020
import { useTranslation } from 'react-i18next';
21-
import { CheckIcon, XMarkIcon } from '@heroicons/react/24/solid';
21+
import { CheckIcon, XMarkIcon, ArchiveBoxXMarkIcon } from '@heroicons/react/24/solid';
22+
import { EllipsisVerticalIcon } from '@heroicons/react/24/outline';
2223
import SortBy from '../../shared_components/search/SortBy';
2324
import useInvitations from '../../../hooks/queries/admin/manage_users/useInvitations';
2425
import Pagination from '../../shared_components/Pagination';
@@ -27,12 +28,14 @@ import EmptyUsersList from './EmptyUsersList';
2728
import { localizeDateTimeString } from '../../../helpers/DateTimeHelper';
2829
import { useAuth } from '../../../contexts/auth/AuthProvider';
2930
import ManageUsersInvitedRowPlaceHolder from './ManageUsersInvitedRowPlaceHolder';
31+
import useRevokeUserInvite from '../../../hooks/mutations/admin/manage_users/useRevokeUserInvite';
3032

3133
export default function InvitedUsersTable({ searchInput }) {
3234
const { t } = useTranslation();
3335
const [page, setPage] = useState();
3436
const { isLoading, data: invitations } = useInvitations(searchInput, page);
3537
const currentUser = useAuth();
38+
const revokeUserInvite = useRevokeUserInvite();
3639

3740
if (!searchInput && invitations?.data?.length === 0) {
3841
return <EmptyUsersList text={t('admin.manage_users.empty_invited_users')} subtext={t('admin.manage_users.empty_invited_users_subtext')} />;
@@ -73,6 +76,17 @@ export default function InvitedUsersTable({ searchInput }) {
7376
<td className="text-dark border-0">
7477
{ invitation.valid ? <CheckIcon className="text-success hi-s" /> : <XMarkIcon className="text-danger hi-s" />}
7578
</td>
79+
<td className="text-dark border-0">
80+
<Dropdown className="float-end cursor-pointer">
81+
<Dropdown.Toggle className="hi-s" as={EllipsisVerticalIcon} />
82+
<Dropdown.Menu>
83+
<Dropdown.Item onClick={() => revokeUserInvite.mutate(invitation.id)}>
84+
<ArchiveBoxXMarkIcon className="hi-s me-2" />
85+
{t('admin.manage_users.invited.revoke')}
86+
</Dropdown.Item>
87+
</Dropdown.Menu>
88+
</Dropdown>
89+
</td>
7690
</tr>
7791
))
7892
)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
2+
//
3+
// Copyright (c) 2022 BigBlueButton Inc. and by respective authors (see below).
4+
//
5+
// This program is free software; you can redistribute it and/or modify it under the
6+
// terms of the GNU Lesser General Public License as published by the Free Software
7+
// Foundation; either version 3.0 of the License, or (at your option) any later
8+
// version.
9+
//
10+
// Greenlight is distributed in the hope that it will be useful, but WITHOUT ANY
11+
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
12+
// PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License along
15+
// with Greenlight; if not, see <http://www.gnu.org/licenses/>.
16+
17+
import { useMutation, useQueryClient } from 'react-query';
18+
import { toast } from 'react-toastify';
19+
import { useTranslation } from 'react-i18next';
20+
import axios from '../../../../helpers/Axios';
21+
22+
export default function useRevokeUserInvite() {
23+
const { t } = useTranslation();
24+
const queryClient = useQueryClient();
25+
26+
return useMutation(
27+
(id) => axios.delete(`/admin/invitations/${id}.json`),
28+
{
29+
onSuccess: () => {
30+
queryClient.invalidateQueries(['getInvitations']);
31+
toast.success(t('toast.success.invitations.invitation_revoked'));
32+
},
33+
onError: () => {
34+
toast.error(t('toast.error.problem_completing_action'));
35+
},
36+
},
37+
);
38+
}

app/serializers/invitation_serializer.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
# frozen_string_literal: true
1818

1919
class InvitationSerializer < ApplicationSerializer
20-
attributes :email, :updated_at, :valid
20+
attributes :id, :email, :updated_at, :valid
2121

2222
def valid
2323
object.updated_at.in(Invitation::INVITATION_VALIDITY_PERIOD)

config/routes.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@
106106
end
107107
resources :rooms_configurations, only: :update, param: :name
108108
resources :roles
109-
resources :invitations, only: %i[index create]
109+
resources :invitations, only: %i[index create destroy]
110110
resources :role_permissions, only: [:index] do
111111
collection do
112112
post '/', to: 'role_permissions#update'

spec/controllers/admin/invitations_controller_spec.rb

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
require 'rails_helper'
2020

21-
RSpec.describe Api::V1::Admin::InvitationsController, type: :controller do
21+
RSpec.describe Api::V1::Admin::InvitationsController do
2222
let(:user) { create(:user) }
2323
let(:user_with_manage_users_permission) { create(:user, :with_manage_users_permission) }
2424

@@ -105,4 +105,17 @@
105105
end
106106
end
107107
end
108+
109+
describe 'invitation#destroy' do
110+
it 'deletes the invitation' do
111+
invitation = create(:invitation)
112+
expect { delete :destroy, params: { id: invitation.id } }.to change(Invitation, :count).by(-1)
113+
expect(response).to have_http_status(:ok)
114+
end
115+
116+
it 'fails to delete the invitation if the id does not exist' do
117+
expect { delete :destroy, params: { id: 'invalid-id' } }.not_to change(Invitation, :count)
118+
expect(response).to have_http_status(:not_found)
119+
end
120+
end
108121
end

0 commit comments

Comments
 (0)