SEBSERV-204 fixes and improvements

This commit is contained in:
anhefti 2021-07-07 17:20:51 +02:00
parent c7952b32bc
commit 6043d752a6
18 changed files with 244 additions and 138 deletions

View file

@ -36,9 +36,6 @@ public class ProctoringServiceSettings implements Entity {
ONE_TO_ONE,
BROADCAST,
ENABLE_CHAT,
WAITING_ROOM,
SEND_REJOIN_COLLECTING_ROOM,
RESET_BROADCAST_ON_LAVE
}
public static final String ATTR_ENABLE_PROCTORING = "enableProctoring";

View file

@ -73,6 +73,12 @@ public class RAPConfiguration implements ApplicationConfiguration {
properties.put(WebClient.FAVICON, "fav_icon");
application.addEntryPoint(guiEntrypoint, new RAPSpringEntryPointFactory(), properties);
properties.put(WebClient.PAGE_TITLE, "SEB Server Proctoring");
properties.put(WebClient.BODY_HTML, "<big>Loading Application<big>");
properties.put(WebClient.THEME_ID, DEFAULT_THEME_NAME);
properties.put(WebClient.FAVICON, "fav_icon");
application.addEntryPoint(proctoringEntrypoint, new RAPRemoteProcotringEntryPointFactory(), properties);
} catch (final RuntimeException re) {

View file

@ -15,6 +15,7 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection.ConnectionStatus;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
@ -43,6 +44,8 @@ public class MonitoringExamSearchPopup {
new LocTextKey("sebserver.monitoring.search.list.name");
private static final LocTextKey TABLE_COLUMN_IP_ADDRESS =
new LocTextKey("sebserver.monitoring.search.list.ip");
private static final LocTextKey TABLE_COLUMN_STATUS =
new LocTextKey("sebserver.monitoring.search.list.status");
private final PageService pageService;
@ -50,9 +53,16 @@ public class MonitoringExamSearchPopup {
new TableFilterAttribute(CriteriaType.TEXT, ClientConnection.FILTER_ATTR_SESSION_ID);
private final TableFilterAttribute ipFilter =
new TableFilterAttribute(CriteriaType.TEXT, ClientConnection.FILTER_ATTR_IP_STRING);
private final TableFilterAttribute statusFilter;
protected MonitoringExamSearchPopup(final PageService pageService) {
this.pageService = pageService;
this.statusFilter = new TableFilterAttribute(
CriteriaType.SINGLE_SELECTION,
ClientConnection.FILTER_ATTR_STATUS,
ConnectionStatus.ACTIVE.name(),
pageService.getResourceService()::localizedClientConnectionStatusResources);
}
public void show(final PageContext pageContext) {
@ -60,6 +70,7 @@ public class MonitoringExamSearchPopup {
pageContext.getParent().getShell(),
this.pageService.getWidgetFactory());
dialog.setLargeDialogWidth();
dialog.setDialogHeight(380);
dialog.open(
TITLE_TEXT_KEY,
pageContext,
@ -90,6 +101,12 @@ public class MonitoringExamSearchPopup {
ClientConnection::getClientAddress)
.withFilter(this.ipFilter))
.withColumn(new ColumnDefinition<>(
Domain.CLIENT_CONNECTION.ATTR_STATUS,
TABLE_COLUMN_STATUS,
ClientConnection::getStatus)
.withFilter(this.statusFilter))
.withDefaultAction(t -> actionBuilder
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION)
.withParentEntityKey(examKey)

View file

@ -8,21 +8,28 @@
package ch.ethz.seb.sebserver.gui.content;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetCollectingRoomConnections;
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
import ch.ethz.seb.sebserver.gui.table.EntityTable;
@Lazy
@Component
@ -31,6 +38,10 @@ public class ProctorRoomConnectionsPopup {
private static final LocTextKey TITLE_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.proctoring.room.connections.title");
private static final LocTextKey EMPTY_LIST_TEXT_KEY =
new LocTextKey("sebserver.monitoring.search.list.empty");
private static final LocTextKey TABLE_COLUMN_NAME =
new LocTextKey("sebserver.monitoring.search.list.name");
private final PageService pageService;
@ -43,29 +54,60 @@ public class ProctorRoomConnectionsPopup {
pageContext.getParent().getShell(),
this.pageService.getWidgetFactory());
dialog.setLargeDialogWidth();
dialog.setDialogHeight(380);
dialog.open(
new LocTextKey(TITLE_TEXT_KEY.name, roomSubject),
pageContext,
this::compose);
c -> this.compose(c, dialog));
}
private void compose(final PageContext pageContext) {
final Composite parent = pageContext.getParent();
final Composite grid = this.pageService.getWidgetFactory().createPopupScrollComposite(parent);
private void compose(final PageContext pageContext, final ModalInputDialog<Void> dialog) {
final EntityKey entityKey = pageContext.getEntityKey();
final EntityKey parentEntityKey = pageContext.getParentEntityKey();
this.pageService.getRestService()
final PageActionBuilder actionBuilder = this.pageService
.pageActionBuilder(pageContext.clearEntityKeys());
final List<ClientConnection> connections = new ArrayList<>(this.pageService.getRestService()
.getBuilder(GetCollectingRoomConnections.class)
.withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId)
.withQueryParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, entityKey.modelId)
.call()
.getOrThrow()
.stream()
.forEach(connection -> {
final Label label = new Label(grid, SWT.NONE);
label.setText(connection.userSessionId);
});
.getOrThrow());
this.pageService.staticListTableBuilder(connections, EntityType.CLIENT_CONNECTION)
.withEmptyMessage(EMPTY_LIST_TEXT_KEY)
.withPaging(10)
.withColumn(new ColumnDefinition<>(
Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
TABLE_COLUMN_NAME,
ClientConnection::getUserSessionId))
.withDefaultAction(t -> actionBuilder
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION)
.withParentEntityKey(parentEntityKey)
.withExec(action -> showClientConnection(action, dialog, t))
.create())
.compose(pageContext);
}
private PageAction showClientConnection(
final PageAction pageAction,
final ModalInputDialog<Void> dialog,
final EntityTable<ClientConnection> table) {
final ClientConnection singleSelectedROWData = table.getSingleSelectedROWData();
dialog.close();
return pageAction
.withEntityKey(new EntityKey(
singleSelectedROWData.id,
EntityType.CLIENT_CONNECTION))
.withAttribute(
Domain.CLIENT_CONNECTION.ATTR_CONNECTION_TOKEN,
singleSelectedROWData.getConnectionToken());
}
}

View file

@ -567,6 +567,14 @@ public class ResourceService {
.getText(SEB_CONNECTION_STATUS_KEY_PREFIX + name, name);
}
public List<Tuple<String>> localizedClientConnectionStatusResources() {
return Arrays.stream(ConnectionStatus.values())
.map(type -> new Tuple<>(type.name(),
this.i18nSupport.getText(SEB_CONNECTION_STATUS_KEY_PREFIX + type.name())))
.sorted(RESOURCE_COMPARATOR)
.collect(Collectors.toList());
}
public String localizedExamTypeName(final ExamConfigurationMap examMap) {
if (examMap.examType == null) {
return Constants.EMPTY_NOTE;

View file

@ -17,6 +17,7 @@ import org.eclipse.swt.layout.RowData;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
@ -78,6 +79,11 @@ public class JitsiMeetProctoringView extends AbstractProctoringView {
final GridData headerCell = new GridData(SWT.FILL, SWT.FILL, true, true);
content.setLayoutData(headerCell);
final Label title = this.pageService
.getWidgetFactory()
.label(content, proctoringWindowData.connectionData.subject);
title.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, true, true));
parent.addListener(SWT.Dispose, event -> closeRoom(proctoringGUIService, proctoringWindowData));
final String url = this.guiServiceInfo

View file

@ -49,6 +49,7 @@ public class ModalInputDialog<T> extends Dialog {
private int dialogWidth = DEFAULT_DIALOG_WIDTH;
private int dialogHeight = DEFAULT_DIALOG_HEIGHT;
private int buttonWidth = DEFAULT_DIALOG_BUTTON_WIDTH;
private boolean forceHeight = false;
public ModalInputDialog(
final Shell parent,
@ -75,6 +76,7 @@ public class ModalInputDialog<T> extends Dialog {
public ModalInputDialog<T> setDialogHeight(final int dialogHeight) {
this.dialogHeight = dialogHeight;
this.forceHeight = true;
return this;
}
@ -215,6 +217,9 @@ public class ModalInputDialog<T> extends Dialog {
}
private int calcDialogHeight(final Composite main) {
if (this.forceHeight) {
return this.dialogHeight;
}
final int actualHeight = main.computeSize(SWT.DEFAULT, SWT.DEFAULT).y;
final int displayHeight = main.getDisplay().getClientArea().height;
final int availableHeight = (displayHeight < actualHeight + 100)

View file

@ -17,6 +17,7 @@ import org.eclipse.swt.layout.RowData;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
@ -78,6 +79,11 @@ public class ZoomProctoringView extends AbstractProctoringView {
final GridData headerCell = new GridData(SWT.FILL, SWT.FILL, true, true);
content.setLayoutData(headerCell);
final Label title = this.pageService
.getWidgetFactory()
.label(content, proctoringWindowData.connectionData.subject);
title.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, true, false));
parent.addListener(SWT.Dispose, event -> closeRoom(proctoringGUIService, proctoringWindowData));
final String url = this.guiServiceInfo

View file

@ -10,7 +10,6 @@ package ch.ethz.seb.sebserver.gui.service.session.proctoring;
import java.util.Arrays;
import java.util.Collections;
import java.util.Optional;
import org.apache.commons.lang3.BooleanUtils;
import org.eclipse.rap.rwt.RWT;
@ -48,7 +47,6 @@ import ch.ethz.seb.sebserver.gui.service.page.PageService.PageActionBuilder;
import ch.ethz.seb.sebserver.gui.service.page.event.ActionActivationEvent;
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
import ch.ethz.seb.sebserver.gui.service.push.ServerPushContext;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetCollectingRooms;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetProctorRoomConnection;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.IsTownhallRoomAvailable;
@ -81,6 +79,7 @@ public class MonitoringProctoringService {
static final String OPEN_ROOM_SCRIPT =
"try {\n" +
"var existingWin = window.open('', '%s', 'height=%s,width=%s,location=no,scrollbars=yes,status=no,menubar=0,toolbar=no,titlebar=no,dialog=no');\n" +
"existingWin.document.title = '%s';\n" +
"if(existingWin.location.href === 'about:blank'){\n" +
" existingWin.location.href = '%s%s';\n" +
" existingWin.focus();\n" +
@ -208,7 +207,7 @@ public class MonitoringProctoringService {
if (actualRoomSize <= 0) {
return _action;
}
return showExamProctoringRoom(proctoringSettings, room, _action);
return openExamProctoringRoom(proctoringSettings, room, _action);
})
.withNameAttributes(
room.subject,
@ -239,60 +238,48 @@ public class MonitoringProctoringService {
updateTownhallButton(proctoringGUIService, pageContext);
}
public PageAction openExamCollectionProctorScreen(
final PageAction action,
final ClientConnectionData connectionData) {
private PageAction openExamProctoringRoom(
final ProctoringServiceSettings proctoringSettings,
final RemoteProctoringRoom room,
final PageAction action) {
try {
final String examId = action.getEntityKey().modelId;
final ProctoringRoomConnection proctoringConnectionData = this.pageService
.getRestService()
.getBuilder(GetProctorRoomConnection.class)
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(proctoringSettings.examId))
.withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, room.name)
.withQueryParam(ProctoringRoomConnection.ATTR_SUBJECT, Utils.encodeFormURL_UTF_8(room.subject))
.call()
.getOrThrow();
final ProctoringServiceSettings proctoringSettings = this.pageService.getRestService()
.getBuilder(GetProctoringSettings.class)
.withURIVariable(API.PARAM_MODEL_ID, examId)
ProctoringGUIService.setCurrentProctoringWindowData(
String.valueOf(proctoringSettings.examId),
proctoringConnectionData);
final String script = String.format(
OPEN_ROOM_SCRIPT,
room.name,
800,
1200,
room.name,
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
this.remoteProctoringEndpoint);
RWT.getClient()
.getService(JavaScriptExecutor.class)
.execute(script);
final boolean newWindow = this.pageService.getCurrentUser()
.getProctoringGUIService()
.registerProctoringWindow(String.valueOf(room.examId), room.name, room.name);
if (newWindow) {
this.pageService.getRestService()
.getBuilder(NotifyProctoringRoomOpened.class)
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(proctoringSettings.examId))
.withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, room.name)
.call()
.getOrThrow();
final Optional<RemoteProctoringRoom> roomOptional =
this.pageService.getRestService().getBuilder(GetCollectingRooms.class)
.withURIVariable(API.PARAM_MODEL_ID, examId)
.call()
.getOrThrow()
.stream()
.filter(room -> room.id.equals(connectionData.clientConnection.remoteProctoringRoomId))
.findFirst();
if (roomOptional.isPresent()) {
final RemoteProctoringRoom room = roomOptional.get();
final ProctoringRoomConnection proctoringConnectionData = this.pageService
.getRestService()
.getBuilder(GetProctorRoomConnection.class)
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(proctoringSettings.examId))
.withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, room.name)
.withQueryParam(ProctoringRoomConnection.ATTR_SUBJECT, Utils.encodeFormURL_UTF_8(room.subject))
.call()
.getOrThrow();
ProctoringGUIService.setCurrentProctoringWindowData(examId, proctoringConnectionData);
final String script = String.format(
MonitoringProctoringService.OPEN_ROOM_SCRIPT,
room.name,
800,
1200,
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
this.remoteProctoringEndpoint);
RWT.getClient()
.getService(JavaScriptExecutor.class)
.execute(script);
this.pageService.getCurrentUser()
.getProctoringGUIService()
.registerProctoringWindow(examId, room.name, room.name);
}
} catch (final Exception e) {
log.error("Failed to open popup for collecting room: ", e);
action.pageContext().notifyError(CLOSE_COLLECTING_ERROR, e);
.onError(error -> log.error("Failed to notify proctoring room opened: ", error));
}
return action;
@ -333,6 +320,7 @@ public class MonitoringProctoringService {
connectionToken,
420,
640,
connectionData.clientConnection.userSessionId,
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
this.remoteProctoringEndpoint);
javaScriptExecutor.execute(script);
@ -374,6 +362,7 @@ public class MonitoringProctoringService {
windowName,
800,
1200,
"Town-Hall",
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
this.remoteProctoringEndpoint);
javaScriptExecutor.execute(script);
@ -460,47 +449,4 @@ public class MonitoringProctoringService {
}
}
private PageAction showExamProctoringRoom(
final ProctoringServiceSettings proctoringSettings,
final RemoteProctoringRoom room,
final PageAction action) {
final ProctoringRoomConnection proctoringConnectionData = this.pageService
.getRestService()
.getBuilder(GetProctorRoomConnection.class)
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(proctoringSettings.examId))
.withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, room.name)
.withQueryParam(ProctoringRoomConnection.ATTR_SUBJECT, Utils.encodeFormURL_UTF_8(room.subject))
.call()
.getOrThrow();
ProctoringGUIService.setCurrentProctoringWindowData(
String.valueOf(proctoringSettings.examId),
proctoringConnectionData);
final String script = String.format(
OPEN_ROOM_SCRIPT,
room.name,
800,
1200,
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
this.remoteProctoringEndpoint);
RWT.getClient()
.getService(JavaScriptExecutor.class)
.execute(script);
this.pageService.getCurrentUser()
.getProctoringGUIService()
.registerProctoringWindow(String.valueOf(room.examId), room.name, room.name);
this.pageService.getRestService().getBuilder(NotifyProctoringRoomOpened.class)
.withURIVariable(API.PARAM_MODEL_ID, String.valueOf(proctoringSettings.examId))
.withQueryParam(ProctoringRoomConnection.ATTR_ROOM_NAME, room.name)
.call()
.onError(error -> log.error("Failed to notify proctoring room opened: ", error));
return action;
}
}

View file

@ -117,12 +117,14 @@ public class ProctoringGUIService {
this.collectingRoomsActionState.clear();
}
public void registerProctoringWindow(
public boolean registerProctoringWindow(
final String examId,
final String windowName,
final String roomName) {
this.openWindows.put(windowName, new RoomData(roomName, examId));
return this.openWindows.putIfAbsent(
windowName,
new RoomData(roomName, examId)) == null;
}
public String getTownhallWindowName(final String examId) {

View file

@ -121,9 +121,15 @@ public interface RemoteProctoringRoomDAO {
/** Get currently active break-out rooms for given connectionToken
*
* @param examId The exam identifier of the connection
* @param connectionTokens The connection token of the client connection
* @return Result refer to active break-out rooms or to an error when happened */
Result<Collection<RemoteProctoringRoom>> getBreakoutRooms(String connectionToken);
/** Get a list of client connection tokens of connections that currently are in
* break-out rooms, including the town-hall room
*
* @param examId The exam identifier of the connection
* @return Result refer to active break-out rooms or to an error when happened */
Result<Collection<String>> getConnectionsInBreakoutRooms(Long examId);
}

View file

@ -322,6 +322,24 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
.collect(Collectors.toList()));
}
@Override
@Transactional(readOnly = true)
public Result<Collection<String>> getConnectionsInBreakoutRooms(final Long examId) {
return Result.tryCatch(() -> this.remoteProctoringRoomRecordMapper
.selectByExample()
.where(RemoteProctoringRoomRecordDynamicSqlSupport.examId, isEqualTo(examId))
.and(RemoteProctoringRoomRecordDynamicSqlSupport.breakOutConnections, isNotNull())
.build()
.execute()
.stream()
.flatMap(room -> Arrays.asList(
StringUtils.split(
room.getBreakOutConnections(),
Constants.LIST_SEPARATOR_CHAR))
.stream())
.collect(Collectors.toList()));
}
private RemoteProctoringRoom toDomainModel(final RemoteProctoringRoomRecord record) {
final String breakOutConnections = record.getBreakOutConnections();
final Collection<String> connections = StringUtils.isNotBlank(breakOutConnections)

View file

@ -33,13 +33,20 @@ public interface ExamProctoringRoomService {
* @return Result refer to the resulting collection of ClientConnection or to an error when happened */
Result<Collection<ClientConnection>> getRoomConnections(Long roomId);
/** Get a collection of all ClientConnection that are currently connected to a specified collecting room.
/** Get a collection of all ClientConnection that are registered to a specified collecting room.
*
* @param examId The exam identifier of the room
* @param roomName The room name
* @return Result refer to the resulting collection of ClientConnection or to an error when happened */
Result<Collection<ClientConnection>> getCollectingRoomConnections(Long examId, String roomName);
/** Get a collection of all ClientConnection that are currently connected to a specified collecting room.
*
* @param examId The exam identifier of the room
* @param roomName The room name
* @return Result refer to the resulting collection of ClientConnection or to an error when happened */
Result<Collection<ClientConnection>> getActiveCollectingRoomConnections(Long examId, String roomName);
/** This is internally used to update client connections that are flagged for updating
* the proctoring room assignment.
* This attaches or detaches client connections from or to proctoring rooms of an exam in one batch.

View file

@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@ -18,6 +19,7 @@ import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@ -27,7 +29,6 @@ import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringRoomConnection;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringFeature;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction.InstructionType;
@ -55,19 +56,22 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
private final ExamAdminService examAdminService;
private final ExamSessionService examSessionService;
private final SEBClientInstructionService sebInstructionService;
private final boolean sendBroadcastReset;
public ExamProctoringRoomServiceImpl(
final RemoteProctoringRoomDAO remoteProctoringRoomDAO,
final ClientConnectionDAO clientConnectionDAO,
final ExamAdminService examAdminService,
final ExamSessionService examSessionService,
final SEBClientInstructionService sebInstructionService) {
final SEBClientInstructionService sebInstructionService,
@Value("${sebserver.webservice.proctoring.resetBroadcastOnLeav:true}") final boolean sendBroadcastReset) {
this.remoteProctoringRoomDAO = remoteProctoringRoomDAO;
this.clientConnectionDAO = clientConnectionDAO;
this.examAdminService = examAdminService;
this.examSessionService = examSessionService;
this.sebInstructionService = sebInstructionService;
this.sendBroadcastReset = sendBroadcastReset;
}
@Override
@ -81,8 +85,34 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
}
@Override
public Result<Collection<ClientConnection>> getCollectingRoomConnections(final Long examId, final String roomName) {
return this.clientConnectionDAO.getCollectingRoomConnections(examId, roomName);
public Result<Collection<ClientConnection>> getCollectingRoomConnections(
final Long examId,
final String roomName) {
return this.clientConnectionDAO
.getCollectingRoomConnections(examId, roomName);
}
@Override
public Result<Collection<ClientConnection>> getActiveCollectingRoomConnections(
final Long examId,
final String roomName) {
final Collection<String> currentlyInBreakoutRooms = this.remoteProctoringRoomDAO
.getConnectionsInBreakoutRooms(examId)
.getOrElse(() -> Collections.emptyList());
if (currentlyInBreakoutRooms.isEmpty()) {
return this.clientConnectionDAO
.getCollectingRoomConnections(examId, roomName);
} else {
return this.clientConnectionDAO
.getCollectingRoomConnections(examId, roomName)
.map(connections -> connections
.stream()
.filter(cc -> !currentlyInBreakoutRooms.contains(cc.connectionToken))
.collect(Collectors.toList()));
}
}
@Override
@ -357,8 +387,8 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
.notifyBreakOutRoomOpened(proctoringSettings, room)
.getOrThrow();
} else {
final Collection<ClientConnection> clientConnections = this.clientConnectionDAO
.getCollectingRoomConnections(examId, roomName)
final Collection<ClientConnection> clientConnections = this
.getActiveCollectingRoomConnections(examId, roomName)
.getOrThrow();
examProctoringService
@ -379,7 +409,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
.getOrThrow();
// Send default settings to clients if fearture is enabled
if (proctoringSettings.enabledFeatures.contains(ProctoringFeature.RESET_BROADCAST_ON_LAVE)) {
if (this.sendBroadcastReset) {
this.sendReconfigurationInstructions(
examId,
connectionTokens,
@ -410,14 +440,14 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
final ExamProctoringService examProctoringService) {
// get all connections of the room
final List<String> connectionTokens = this.getCollectingRoomConnections(examId, roomName)
final List<String> connectionTokens = this.getActiveCollectingRoomConnections(examId, roomName)
.getOrThrow()
.stream()
.map(cc -> cc.connectionToken)
.collect(Collectors.toList());
// Send default settings to clients if feature is enabled
if (proctoringSettings.enabledFeatures.contains(ProctoringFeature.RESET_BROADCAST_ON_LAVE)) {
if (this.sendBroadcastReset) {
this.sendReconfigurationInstructions(
examId,
connectionTokens,
@ -460,7 +490,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
final RemoteProctoringRoom remoteProctoringRoom) {
// Send default settings to clients if feature is enabled
if (proctoringSettings.enabledFeatures.contains(ProctoringFeature.RESET_BROADCAST_ON_LAVE)) {
if (this.sendBroadcastReset) {
this.sendReconfigurationInstructions(
examId,
remoteProctoringRoom.breakOutConnections,
@ -517,7 +547,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
} else if (!room.breakOutConnections.isEmpty()) {
connectionTokens.addAll(room.breakOutConnections);
} else {
connectionTokens.addAll(this.getCollectingRoomConnections(examId, roomName)
connectionTokens.addAll(this.getActiveCollectingRoomConnections(examId, roomName)
.getOrThrow()
.stream()
.map(cc -> cc.connectionToken)

View file

@ -29,6 +29,7 @@ import org.joda.time.DateTimeZone;
import org.joda.time.Interval;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
@ -57,7 +58,6 @@ import ch.ethz.seb.sebserver.gbl.async.CircuitBreaker;
import ch.ethz.seb.sebserver.gbl.client.ClientCredentials;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringRoomConnection;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringFeature;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
@ -126,6 +126,8 @@ public class ZoomProctoringService implements ExamProctoringService {
private final RemoteProctoringRoomDAO remoteProctoringRoomDAO;
private final AuthorizationService authorizationService;
private final SEBClientInstructionService sebInstructionService;
private final boolean enableWaitingRoom;
private final boolean sendRejoinForCollectingRoom;
public ZoomProctoringService(
final ExamSessionService examSessionService,
@ -135,7 +137,9 @@ public class ZoomProctoringService implements ExamProctoringService {
final JSONMapper jsonMapper,
final RemoteProctoringRoomDAO remoteProctoringRoomDAO,
final AuthorizationService authorizationService,
final SEBClientInstructionService sebInstructionService) {
final SEBClientInstructionService sebInstructionService,
@Value("${sebserver.webservice.proctoring.enableWaitingRoom:false}") final boolean enableWaitingRoom,
@Value("${sebserver.webservice.proctoring.sendRejoinForCollectingRoom:true}") final boolean sendRejoinForCollectingRoom) {
this.examSessionService = examSessionService;
this.clientHttpRequestFactoryService = clientHttpRequestFactoryService;
@ -146,6 +150,8 @@ public class ZoomProctoringService implements ExamProctoringService {
this.remoteProctoringRoomDAO = remoteProctoringRoomDAO;
this.authorizationService = authorizationService;
this.sebInstructionService = sebInstructionService;
this.enableWaitingRoom = enableWaitingRoom;
this.sendRejoinForCollectingRoom = sendRejoinForCollectingRoom;
}
@Override
@ -444,13 +450,13 @@ public class ZoomProctoringService implements ExamProctoringService {
return Result.tryCatch(() -> {
if (!proctoringSettings.enabledFeatures.contains(ProctoringFeature.SEND_REJOIN_COLLECTING_ROOM)) {
// do nothing if the rejoin feature is not enabled
if (!this.sendRejoinForCollectingRoom) {
// does nothing if the rejoin feature is not enabled
return;
}
if (this.remoteProctoringRoomDAO.isTownhallRoomActive(proctoringSettings.examId)) {
// do nothing if the town-hall of this exam is open. The clients will automatically join
// does nothing if the town-hall of this exam is open. The clients will automatically join
// the meeting once the town-hall has been closed
return;
}
@ -522,7 +528,7 @@ public class ZoomProctoringService implements ExamProctoringService {
subject,
duration,
meetingPwd,
proctoringSettings.enabledFeatures.contains(ProctoringFeature.WAITING_ROOM));
this.enableWaitingRoom);
final MeetingResponse meetingResponse = this.jsonMapper.readValue(
createMeeting.getBody(),

View file

@ -138,8 +138,7 @@ public interface ZoomRoomRequestResponse {
@JsonIgnoreProperties(ignoreUnknown = true)
static class Settings {
@JsonProperty final boolean host_video = false;
@JsonProperty final boolean participant_video = false;
@JsonProperty final boolean mute_upon_entry = true;
@JsonProperty final boolean mute_upon_entry = false;
@JsonProperty final boolean join_before_host;
@JsonProperty final int jbh_time = 0;
@JsonProperty final boolean use_pmi = false;

View file

@ -66,3 +66,7 @@ sebserver.webservice.lms.openedx.api.token.request.paths=/oauth2/access_token
sebserver.webservice.lms.moodle.api.token.request.paths=/login/token.php
sebserver.webservice.lms.address.alias=
sebserver.webservice.proctoring.resetBroadcastOnLeav=true
sebserver.webservice.proctoring.zoom.enableWaitingRoom=false
sebserver.webservice.proctoring.zoom.sendRejoinForCollectingRoom=true

View file

@ -1633,7 +1633,7 @@ sebserver.monitoring.exam.action.proctoring.openTownhall=Open Townhall
sebserver.monitoring.exam.action.proctoring.showTownhall=Show Townhall
sebserver.monitoring.exam.action.proctoring.closeTownhall=Close Townhall
sebserver.monitoring.exam.proctoring.room.all.name=Exam Room
sebserver.monitoring.exam.proctoring.room.all.name=Townhall Room
sebserver.monitoring.exam.proctoring.action.close=Close Window
sebserver.monitoring.exam.proctoring.action.broadcaston.audio=Start Audio Broadcast
sebserver.monitoring.exam.proctoring.action.broadcastoff.audio=End Audio Broadcast
@ -1679,6 +1679,7 @@ sebserver.monitoring.search.action=Search
sebserver.monitoring.search.list.empty=No Client Connections available
sebserver.monitoring.search.list.name=Session or User Name
sebserver.monitoring.search.list.ip=IP Address
sebserver.monitoring.search.list.status=Status
sebserver.monitoring.exam.connection.emptySelection=At first please select a Connection from the list