Skip to content
Snippets Groups Projects
Unverified Commit 05c35ccb authored by Samuel Couillard's avatar Samuel Couillard Committed by GitHub
Browse files

Add status and participants to ServerRooms (#3623)

* Works but slow

* Add Serializer

Fix n+1

checkpoint

checkpoint

* Add RSpec

Move active_rooms logic from Serializer to Controller

Code Review: Add bbb_api to RSpec, clean hook..

* Change data structure from array to hash
parent 4579bd88
No related branches found
No related tags found
No related merge requests found
...@@ -6,18 +6,22 @@ module Api ...@@ -6,18 +6,22 @@ module Api
class ServerRoomsController < ApiController class ServerRoomsController < ApiController
before_action :find_server_room, only: :destroy before_action :find_server_room, only: :destroy
# GET /api/v1/admin/server_rooms.json
def index def index
rooms = Room.all.search(params[:search]) rooms = Room.includes(:user).search(params[:search])
active_rooms = BigBlueButtonApi.new.active_meetings
active_rooms_hash = {}
rooms.map! do |room| active_rooms.each do |active_room|
{ active_rooms_hash[active_room[:meetingID]] = active_room[:participantCount]
friendly_id: room.friendly_id,
name: room.name,
owner: User.find(room.user_id).name
}
end end
render_json data: rooms, status: :ok rooms.each do |room|
room.status = active_rooms_hash.key?(room.meeting_id) ? 'Active' : 'Not Running'
room.participants = active_rooms_hash[room.meeting_id]
end
render_data data: rooms, each_serializer: ServerRoomSerializer
end end
# DELETE /api/v1/admin/server_rooms/:friendly_id # DELETE /api/v1/admin/server_rooms/:friendly_id
......
...@@ -5,6 +5,7 @@ import PropTypes from 'prop-types'; ...@@ -5,6 +5,7 @@ import PropTypes from 'prop-types';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
export default function ServerRoomRow({ room }) { export default function ServerRoomRow({ room }) {
console.log();
return ( return (
<tr className="align-middle text-muted border border-2"> <tr className="align-middle text-muted border border-2">
<td className="border-end-0"> <td className="border-end-0">
...@@ -15,8 +16,8 @@ export default function ServerRoomRow({ room }) { ...@@ -15,8 +16,8 @@ export default function ServerRoomRow({ room }) {
</td> </td>
<td className="border-0"> { room.owner }</td> <td className="border-0"> { room.owner }</td>
<td className="border-0"> { room.friendly_id } </td> <td className="border-0"> { room.friendly_id } </td>
<td className="border-0"> - </td> <td className="border-0"> { room.participants ? room.participants : '-' } </td>
<td className="border-0"> - </td> <td className="border-0"> { room.status } </td>
<td className="border-start-0"> <td className="border-start-0">
<Dropdown className="float-end cursor-pointer"> <Dropdown className="float-end cursor-pointer">
<Dropdown.Toggle className="hi-s" as={DotsVerticalIcon} /> <Dropdown.Toggle className="hi-s" as={DotsVerticalIcon} />
...@@ -35,5 +36,7 @@ ServerRoomRow.propTypes = { ...@@ -35,5 +36,7 @@ ServerRoomRow.propTypes = {
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
owner: PropTypes.string.isRequired, owner: PropTypes.string.isRequired,
friendly_id: PropTypes.string.isRequired, friendly_id: PropTypes.string.isRequired,
status: PropTypes.string.isRequired,
participants: PropTypes.number,
}).isRequired, }).isRequired,
}; };
...@@ -3,10 +3,10 @@ import Card from 'react-bootstrap/Card'; ...@@ -3,10 +3,10 @@ import Card from 'react-bootstrap/Card';
import { import {
Col, Container, Row, Tab, Table, Col, Container, Row, Tab, Table,
} from 'react-bootstrap'; } from 'react-bootstrap';
import useServerRooms from '../../hooks/queries/admin/server_rooms/useServerRooms'; import useServerRooms from '../../../hooks/queries/admin/server_rooms/useServerRooms';
import ServerRoomRow from './server-rooms/ServerRoomRow'; import ServerRoomRow from './ServerRoomRow';
import SearchBarQuery from '../shared/SearchBarQuery'; import SearchBarQuery from '../../shared/SearchBarQuery';
import AdminNavSideBar from './shared/AdminNavSideBar'; import AdminNavSideBar from '../shared/AdminNavSideBar';
export default function ServerRooms() { export default function ServerRooms() {
const [input, setInput] = useState(); const [input, setInput] = useState();
......
import { useQuery } from 'react-query';
import axios from '../../../../helpers/Axios';
export default function useActiveServerRooms() {
return useQuery(
'getServerRooms',
() => axios.get('/admin/server_rooms/active_server_rooms.json').then((resp) => resp.data.data),
);
}
...@@ -18,7 +18,7 @@ import RoomJoin from './components/rooms/RoomJoin'; ...@@ -18,7 +18,7 @@ import RoomJoin from './components/rooms/RoomJoin';
import ForgetPassword from './components/users/FrogetPassword'; import ForgetPassword from './components/users/FrogetPassword';
import ManageUsers from './components/admin/ManageUsers'; import ManageUsers from './components/admin/ManageUsers';
import ServerRecordings from './components/admin/ServerRecordings'; import ServerRecordings from './components/admin/ServerRecordings';
import ServerRooms from './components/admin/ServerRooms'; import ServerRooms from './components/admin/server_rooms/ServerRooms';
import SiteSettings from './components/admin/SiteSettings'; import SiteSettings from './components/admin/SiteSettings';
import RoomConfig from './components/admin/RoomConfig'; import RoomConfig from './components/admin/RoomConfig';
import Roles from './components/admin/Roles'; import Roles from './components/admin/Roles';
......
...@@ -18,7 +18,7 @@ class Room < ApplicationRecord ...@@ -18,7 +18,7 @@ class Room < ApplicationRecord
before_validation :set_friendly_id, :set_meeting_id, on: :create before_validation :set_friendly_id, :set_meeting_id, on: :create
after_create :create_meeting_options after_create :create_meeting_options
attr_accessor :shared # indicates that a room is shared (needed for serialization) attr_accessor :shared, :status, :participants
def self.search(input) def self.search(input)
return where('rooms.name ILIKE ?', "%#{input}%").to_a if input return where('rooms.name ILIKE ?', "%#{input}%").to_a if input
......
# frozen_string_literal: true
class ServerRoomSerializer < RoomSerializer
attributes :owner, :status, :participants
def owner
object.user.name
end
end
...@@ -31,6 +31,10 @@ class BigBlueButtonApi ...@@ -31,6 +31,10 @@ class BigBlueButtonApi
bbb_server.is_meeting_running?(room.meeting_id) bbb_server.is_meeting_running?(room.meeting_id)
end end
def active_meetings
bbb_server.get_meetings[:meetings]
end
# Retrieve the recordings that belong to room with given record_id # Retrieve the recordings that belong to room with given record_id
def get_recording(record_id:) def get_recording(record_id:)
bbb_server.get_recordings(recordID: record_id)[:recordings][0] bbb_server.get_recordings(recordID: record_id)[:recordings][0]
......
...@@ -17,9 +17,78 @@ RSpec.describe Api::V1::Admin::ServerRoomsController, type: :controller do ...@@ -17,9 +17,78 @@ RSpec.describe Api::V1::Admin::ServerRoomsController, type: :controller do
rooms = user_one_rooms + user_two_rooms rooms = user_one_rooms + user_two_rooms
get :index get :index
allow_any_instance_of(BigBlueButtonApi).to receive(:active_meetings)
response_room_ids = JSON.parse(response.body)['data'].map { |room| room['friendly_id'] } response_room_ids = JSON.parse(response.body)['data'].map { |room| room['friendly_id'] }
expect(response_room_ids).to match_array(rooms.pluck(:friendly_id)) expect(response_room_ids).to match_array(rooms.pluck(:friendly_id))
end end
it 'returns the server room status as active if the meeting has started' do
active_server_room = create(:room)
active_server_room.update(meeting_id: 'hulsdzwvitlk1dbekzxdprshsxmvycvar0jeaszc')
allow_any_instance_of(BigBlueButtonApi).to receive(:active_meetings).and_return(bbb_meetings)
get :index
expect(JSON.parse(response.body)['data'][0]['status']).to eql('Active')
end
it 'returns the number of participants in an active server room' do
active_server_room = create(:room)
active_server_room.update(meeting_id: 'hulsdzwvitlk1dbekzxdprshsxmvycvar0jeaszc')
allow_any_instance_of(BigBlueButtonApi).to receive(:active_meetings).and_return(bbb_meetings)
get :index
expect(JSON.parse(response.body)['data'][0]['participants']).to be(1)
end
it 'returns the server status as not running if BBB server does not return any active meetings' do
create(:room)
allow_any_instance_of(BigBlueButtonApi).to receive(:active_meetings).and_return([])
get :index
expect(JSON.parse(response.body)['data'][0]['status']).to eql('Not Running')
end
end
private
def bbb_meetings
[{
meetingName: 'Meeting',
meetingID: 'hulsdzwvitlk1dbekzxdprshsxmvycvar0jeaszc',
internalMeetingID: 'f92e335f4aa90883d0454990d1e292c9cf2a9411-1657223036433',
createTime: 1_657_223_036_433,
createDate: 'Thu Jul 07 19:43:56 UTC 2022',
voiceBridge: 71_094,
dialNumber: '613-555-1234',
attendeePW: 'PfkAgstb',
moderatorPW: 'FySoLjmx',
running: true,
duration: '0',
hasUserJoined: 'true',
recording: 'false',
hasBeenForciblyEnded: false,
startTime: '1657223036446',
endTime: '0',
participantCount: 1,
listenerCount: 0,
voiceParticipantCount: '0',
videoCount: 0,
maxUsers: '0',
moderatorCount: '1',
attendees: { attendee: { userID: 'w_jololdrmvk0l',
fullName: 'Smith',
role: 'MODERATOR',
isPresenter: 'true',
isListeningOnly: 'false',
hasJoinedVoice: 'false',
hasVideo: 'false',
clientType: 'HTML5' } },
metadata: { 'bbb-origin-version': '3',
'bbb-recording-ready-url': 'http://localhost:3000/recording_ready',
'bbb-origin': 'greenlight',
endcallbackurl: 'http://localhost:3000/meeting_ended' },
isBreakout: 'false'
}]
end end
describe '#destroy' do describe '#destroy' do
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment