fixed proctoring room issues

This commit is contained in:
anhefti 2020-10-19 13:42:53 +02:00
parent daebd9b5f7
commit 5b9b336886
9 changed files with 241 additions and 117 deletions

View file

@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.gbl;
import java.util.Collection;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.RGBA;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.springframework.core.ParameterizedTypeReference;
@ -124,6 +125,7 @@ public final class Constants {
public static final RGB WHITE_RGB = new RGB(255, 255, 255);
public static final RGB BLACK_RGB = new RGB(0, 0, 0);
public static final RGBA GREY_DISABLED = new RGBA(150, 150, 150, 50);
public static final String IMPORTED_PASSWORD_MARKER = "_IMPORTED_PASSWORD";

View file

@ -286,28 +286,30 @@ public class MonitoringClientConnection implements TemplateComposer {
.publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER) &&
connectionData.clientConnection.status == ConnectionStatus.ACTIVE);
final ProctoringSettings procotringSettings = restService
.getBuilder(GetProctoringSettings.class)
.withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId)
.call()
.onError(error -> log.error("Failed to get ProctoringSettings", error))
.getOr(null);
// TODO if (connectionData.clientConnection.status == ConnectionStatus.ACTIVE) {
if (connectionData.clientConnection.status != ConnectionStatus.DISABLED) {
final ProctoringSettings procotringSettings = restService
.getBuilder(GetProctoringSettings.class)
.withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId)
.call()
.onError(error -> log.error("Failed to get ProctoringSettings", error))
.getOr(null);
if (procotringSettings != null && procotringSettings.enableProctoring) {
actionBuilder
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_PROCTORING)
.withEntityKey(parentEntityKey)
.withExec(action -> this.openSingleProctorScreen(action, connectionData))
.noEventPropagation()
.publish()
if (procotringSettings != null && procotringSettings.enableProctoring) {
actionBuilder
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_PROCTORING)
.withEntityKey(parentEntityKey)
.withExec(action -> this.openSingleProctorScreen(action, connectionData))
.noEventPropagation()
.publish()
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_EXAM_ROOM_PROCTORING)
.withEntityKey(parentEntityKey)
.withExec(action -> this.openExamCollectionProctorScreen(action, connectionData))
.noEventPropagation()
.publish();
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_EXAM_ROOM_PROCTORING)
.withEntityKey(parentEntityKey)
.withExec(action -> this.openExamCollectionProctorScreen(action, connectionData))
.noEventPropagation()
.publish();
}
}
}
private PageAction openExamCollectionProctorScreen(

View file

@ -20,9 +20,12 @@ import java.util.function.Function;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.slf4j.Logger;
@ -31,6 +34,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Domain;
@ -49,6 +53,7 @@ import ch.ethz.seb.sebserver.gbl.util.Tuple;
import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.gui.GuiServiceInfo;
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
import ch.ethz.seb.sebserver.gui.content.action.ActionPane;
import ch.ethz.seb.sebserver.gui.service.ResourceService;
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
@ -361,13 +366,13 @@ public class MonitoringRunningExam implements TemplateComposer {
room.subject,
room.roomSize,
proctoringSettings.collectingRoomSize)));
processProctorRoomActionActivation(treeItem, room, pageContext);
} else {
// create new action
final PageAction action =
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_VIEW_PROCTOR_ROOM)
.withEntityKey(entityKey)
.withExec(a -> showExamProctoringRoom(proctoringSettings, room, a))
.withExec(a -> showExamProctoringRoom(proctoringSettings, room, rooms, a))
.withNameAttributes(
room.subject,
room.roomSize,
@ -376,42 +381,89 @@ public class MonitoringRunningExam implements TemplateComposer {
.create();
this.pageService.publishAction(action, treeItem -> rooms.put(room, treeItem));
// check and add show connections in room action adaptation
if (!rooms.isEmpty()) {
final TreeItem treeItem = rooms.values().iterator().next();
final Tree tree = treeItem.getParent();
if (tree.getData(SHOW_CONNECTION_ACTION_APPLIED) == null) {
tree.addListener(SWT.Selection, event -> {
final TreeItem item = (TreeItem) event.item;
treeItem.getParent().deselectAll();
if (event.button == 3) {
rooms.entrySet()
.stream()
.filter(e -> e.getValue().equals(item))
.findFirst()
.ifPresent(e -> {
final PageContext pc = pageContext.copy()
.clearAttributes()
.withEntityKey(new EntityKey(e.getKey().getName(),
EntityType.REMOTE_PROCTORING_ROOM))
.withParentEntityKey(entityKey);
this.proctorRoomConnectionsPopup.show(pc, e.getKey().getSubject());
});
}
});
tree.setData(SHOW_CONNECTION_ACTION_APPLIED, true);
}
}
addRoomConnectionsPopupListener(entityKey, pageContext, rooms);
rooms.entrySet().stream()
.filter(entry -> entry.getKey().equals(room))
.findFirst()
.ifPresent(entry -> processProctorRoomActionActivation(
entry.getValue(),
room,
pageContext));
}
});
}
private void processProctorRoomActionActivation(
final TreeItem treeItem,
final RemoteProctoringRoom room,
final PageContext pageContext) {
try {
final Display display = pageContext.getRoot().getDisplay();
final PageAction action = (PageAction) treeItem.getData(ActionPane.ACTION_EVENT_CALL_KEY);
final Image image = room.roomSize > 0
? action.definition.icon.getImage(display)
: action.definition.icon.getGreyedImage(display);
treeItem.setImage(image);
treeItem.setForeground(room.roomSize > 0 ? null : new Color(display, Constants.GREY_DISABLED));
} catch (final Exception e) {
log.warn("Failed to set Proctor-Room-Activation: ", e.getMessage());
}
}
private void addRoomConnectionsPopupListener(
final EntityKey entityKey,
final PageContext pageContext,
final Map<RemoteProctoringRoom, TreeItem> rooms) {
if (!rooms.isEmpty()) {
final TreeItem treeItem = rooms.values().iterator().next();
final Tree tree = treeItem.getParent();
if (tree.getData(SHOW_CONNECTION_ACTION_APPLIED) == null) {
tree.addListener(SWT.Selection, event -> {
final TreeItem item = (TreeItem) event.item;
item.getParent().deselectAll();
if (event.button == 3) {
rooms.entrySet()
.stream()
.filter(e -> e.getValue().equals(item))
.findFirst()
.ifPresent(e -> {
if (e.getKey().roomSize > 0) {
final PageContext pc = pageContext.copy()
.clearAttributes()
.withEntityKey(new EntityKey(e.getKey().getName(),
EntityType.REMOTE_PROCTORING_ROOM))
.withParentEntityKey(entityKey);
this.proctorRoomConnectionsPopup.show(pc, e.getKey().getSubject());
}
});
}
});
tree.setData(SHOW_CONNECTION_ACTION_APPLIED, true);
}
}
}
private int getActualRoomSize(final RemoteProctoringRoom room, final Map<RemoteProctoringRoom, TreeItem> rooms) {
return rooms.entrySet().stream()
.filter(entry -> entry.getKey().equals(room))
.findFirst()
.map(entry -> entry.getKey().roomSize)
.orElseGet(() -> 1);
}
private PageAction showExamProctoringRoom(
final ProctoringSettings proctoringSettings,
final RemoteProctoringRoom room,
final Map<RemoteProctoringRoom, TreeItem> rooms,
final PageAction action) {
final int actualRoomSize = getActualRoomSize(room, rooms);
if (actualRoomSize <= 0) {
return action;
}
final SEBProctoringConnectionData proctoringConnectionData = this.pageService
.getRestService()
.getBuilder(GetProctorRoomConnectionData.class)

View file

@ -47,7 +47,7 @@ import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
@Component
public class ActionPane implements TemplateComposer {
private static final String ACTION_EVENT_CALL_KEY = "ACTION_EVENT_CALL";
public static final String ACTION_EVENT_CALL_KEY = "ACTION_EVENT_CALL";
private static final LocTextKey TITLE_KEY = new LocTextKey("sebserver.actionpane.title");
private final PageService pageService;
@ -226,7 +226,7 @@ public class ActionPane implements TemplateComposer {
final Tree actions = this.widgetFactory.treeLocalized(
composite,
SWT.SINGLE | SWT.FULL_SELECTION | SWT.NO_SCROLL);
actions.setData(RWT.CUSTOM_VARIANT, "actions");
actions.setData(RWT.CUSTOM_VARIANT, CustomVariant.ACTIONS.key);
final GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, false);
actions.setLayoutData(gridData);
final Template template = new Template();

View file

@ -99,7 +99,6 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
browser.setLayoutData(gridData);
browser.setUrl(url);
//browser.layout();
browser.setBackground(new Color(parent.getDisplay(), 100, 100, 100));
final Composite footer = new Composite(content, SWT.NONE | SWT.NO_SCROLL);
@ -118,12 +117,14 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
.closeRoom(proctoringWindowData.connectionData.roomName));
final BroadcastActionState broadcastActionState = new BroadcastActionState();
final String connectionTokens = getConnectionTokens(proctoringWindowData);
final Button broadcastAudioAction = widgetFactory.buttonLocalized(footer, BROADCAST_AUDIO_ON_TEXT_KEY);
broadcastAudioAction.setLayoutData(new RowData(150, 30));
broadcastAudioAction.addListener(SWT.Selection, event -> toggleBroadcastAudio(
proctoringWindowData.examId,
proctoringWindowData.connectionData.roomName,
connectionTokens,
broadcastAudioAction));
broadcastAudioAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState);
@ -132,6 +133,7 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
broadcastVideoAction.addListener(SWT.Selection, event -> toggleBroadcastVideo(
proctoringWindowData.examId,
proctoringWindowData.connectionData.roomName,
connectionTokens,
broadcastVideoAction,
broadcastAudioAction));
broadcastVideoAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState);
@ -141,12 +143,26 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
chatAction.addListener(SWT.Selection, event -> toggleChat(
proctoringWindowData.examId,
proctoringWindowData.connectionData.roomName,
connectionTokens,
chatAction));
chatAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState);
}
private void toggleBroadcastAudio(final String examId, final String roomName, final Button broadcastAction) {
private String getConnectionTokens(final ProctoringWindowData proctoringWindowData) {
final String connectionTokens = this.pageService
.getCurrentUser()
.getProctoringGUIService()
.getRoomConnectionTokens(proctoringWindowData.connectionData.roomName);
return connectionTokens == null ? "" : connectionTokens;
}
private void toggleBroadcastAudio(
final String examId,
final String roomName,
final String connectionTokens,
final Button broadcastAction) {
final BroadcastActionState state =
(BroadcastActionState) broadcastAction.getData(BroadcastActionState.KEY_NAME);
if (state.audio) {
@ -154,6 +170,7 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
this.pageService.getRestService().getBuilder(SendProctoringBroadcastOffInstruction.class)
.withURIVariable(API.PARAM_MODEL_ID, examId)
.withFormParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, roomName)
.withFormParam(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionTokens)
.withFormParam(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_AUDIO,
Constants.TRUE_STRING)
@ -164,6 +181,7 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
this.pageService.getRestService().getBuilder(SendProctoringBroadcastOnInstruction.class)
.withURIVariable(API.PARAM_MODEL_ID, examId)
.withFormParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, roomName)
.withFormParam(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionTokens)
.withFormParam(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_AUDIO,
Constants.TRUE_STRING)
@ -176,6 +194,7 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
private void toggleBroadcastVideo(
final String examId,
final String roomName,
final String connectionTokens,
final Button videoAction,
final Button audioAction) {
final BroadcastActionState state =
@ -187,6 +206,7 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
this.pageService.getRestService().getBuilder(SendProctoringBroadcastOffInstruction.class)
.withURIVariable(API.PARAM_MODEL_ID, examId)
.withFormParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, roomName)
.withFormParam(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionTokens)
.withFormParam(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_AUDIO,
Constants.TRUE_STRING)
@ -201,6 +221,7 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
this.pageService.getRestService().getBuilder(SendProctoringBroadcastOnInstruction.class)
.withURIVariable(API.PARAM_MODEL_ID, examId)
.withFormParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, roomName)
.withFormParam(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionTokens)
.withFormParam(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_AUDIO,
Constants.TRUE_STRING)
@ -214,7 +235,12 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
state.audio = state.video;
}
private void toggleChat(final String examId, final String roomName, final Button broadcastAction) {
private void toggleChat(
final String examId,
final String roomName,
final String connectionTokens,
final Button broadcastAction) {
final BroadcastActionState state =
(BroadcastActionState) broadcastAction.getData(BroadcastActionState.KEY_NAME);
if (state.chat) {
@ -222,6 +248,7 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
this.pageService.getRestService().getBuilder(SendProctoringBroadcastOffInstruction.class)
.withURIVariable(API.PARAM_MODEL_ID, examId)
.withFormParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, roomName)
.withFormParam(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionTokens)
.withFormParam(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_ALLOW_CHAT,
Constants.TRUE_STRING)
@ -232,6 +259,7 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
this.pageService.getRestService().getBuilder(SendProctoringBroadcastOnInstruction.class)
.withURIVariable(API.PARAM_MODEL_ID, examId)
.withFormParam(Domain.REMOTE_PROCTORING_ROOM.ATTR_ID, roomName)
.withFormParam(API.EXAM_API_SEB_CONNECTION_TOKEN, connectionTokens)
.withFormParam(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_ALLOW_CHAT,
Constants.TRUE_STRING)

View file

@ -61,13 +61,24 @@ public class ProctoringGUIService {
return this.rooms.keySet();
}
public String getRoomConnectionTokens(final String roomName) {
if (this.rooms.containsKey(roomName)) {
return StringUtils.join(this.rooms.get(roomName).connections, Constants.COMMA);
} else {
return null;
}
}
public static ProctoringWindowData getCurrentProctoringWindowData() {
return (ProctoringWindowData) RWT.getUISession()
.getHttpSession()
.getAttribute(SESSION_ATTR_PROCTORING_DATA);
}
public static void setCurrentProctoringWindowData(final String examId, final SEBProctoringConnectionData data) {
public static void setCurrentProctoringWindowData(
final String examId,
final SEBProctoringConnectionData data) {
RWT.getUISession().getHttpSession().setAttribute(
SESSION_ATTR_PROCTORING_DATA,
new ProctoringWindowData(examId, data));
@ -220,8 +231,9 @@ public class ProctoringGUIService {
public final String examId;
public final SEBProctoringConnectionData connectionData;
protected ProctoringWindowData(final String examId, final SEBProctoringConnectionData connectionData) {
super();
protected ProctoringWindowData(
final String examId,
final SEBProctoringConnectionData connectionData) {
this.examId = examId;
this.connectionData = connectionData;
}

View file

@ -174,6 +174,8 @@ public class WidgetFactory {
TEXT_ACTION("action"),
TEXT_READONLY("readonlyText"),
ACTIONS("actions"),
FORM_CENTER("form-center"),
SELECTION("selection"),
SELECTED("selected"),

View file

@ -72,36 +72,6 @@ public class ExamJITSIProctoringService implements ExamProctoringService {
return null;
}
// @Override
// public Result<SEBProctoringConnectionData> createProctorPrivateRoomConnection(
// final ProctoringSettings proctoringSettings,
// final String connectionToken) {
//
// return Result.tryCatch(() -> {
//
// final ClientConnectionData clientConnection = this.examSessionService.getConnectionData(connectionToken)
// .getOrThrow();
//
// final long expTime = forExam(proctoringSettings);
// final Encoder urlEncoder = Base64.getUrlEncoder().withoutPadding();
// final String roomName = urlEncoder.encodeToString(
// Utils.toByteArray(clientConnection.clientConnection.connectionToken));
//
// return createProctoringConnectionData(
// proctoringSettings.serverType,
// connectionToken,
// proctoringSettings.serverURL,
// proctoringSettings.appKey,
// proctoringSettings.getAppSecret(),
// this.authorizationService.getUserService().getCurrentUser().getUsername(),
// "seb-server",
// roomName,
// clientConnection.clientConnection.userSessionId,
// expTime)
// .getOrThrow();
// });
// }
@Override
public Result<SEBProctoringConnectionData> createProctorPublicRoomConnection(
final ProctoringSettings proctoringSettings,

View file

@ -388,7 +388,10 @@ public class ExamMonitoringController {
@PathVariable(name = API.PARAM_MODEL_ID) final Long examId,
@RequestParam(
name = Domain.REMOTE_PROCTORING_ROOM.ATTR_ID,
required = true) final String roomName,
required = false) final String roomName,
@RequestParam(
name = API.EXAM_API_SEB_CONNECTION_TOKEN,
required = false) final String connectionTokens,
@RequestParam(
name = ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_AUDIO,
required = false) final Boolean sendReceiveAudio,
@ -429,21 +432,46 @@ public class ExamMonitoringController {
return;
}
this.examProcotringRoomService.getRoomConnections(examId, roomName)
.getOrThrow()
.stream()
.forEach(connection -> {
this.sebInstructionService.registerInstruction(
examId,
InstructionType.SEB_RECONFIGURE_SETTINGS,
attributes,
connection.connectionToken,
true)
.onError(error -> log.error(
"Failed to register reconfiguring instruction for connection: {}",
connection.connectionToken,
error));
});
if (StringUtils.isNotBlank(connectionTokens)) {
final boolean single = connectionTokens.contains(Constants.LIST_SEPARATOR);
(single
? Arrays.asList(StringUtils.split(connectionTokens, Constants.LIST_SEPARATOR))
: Arrays.asList(connectionTokens))
.stream()
.forEach(connectionToken -> {
this.sebInstructionService.registerInstruction(
examId,
InstructionType.SEB_RECONFIGURE_SETTINGS,
attributes,
connectionToken,
true)
.onError(error -> log.error(
"Failed to register reconfiguring instruction for connection: {}",
connectionToken,
error));
});
} else if (StringUtils.isNotBlank(roomName)) {
this.examProcotringRoomService.getRoomConnections(examId, roomName)
.getOrThrow()
.stream()
.forEach(connection -> {
this.sebInstructionService.registerInstruction(
examId,
InstructionType.SEB_RECONFIGURE_SETTINGS,
attributes,
connection.connectionToken,
true)
.onError(error -> log.error(
"Failed to register reconfiguring instruction for connection: {}",
connection.connectionToken,
error));
});
} else {
throw new RuntimeException("API attribute validation error: missing "
+ Domain.REMOTE_PROCTORING_ROOM.ATTR_ID + " and/or" +
API.EXAM_API_SEB_CONNECTION_TOKEN + " attribute");
}
}
@RequestMapping(
@ -461,6 +489,9 @@ public class ExamMonitoringController {
@RequestParam(
name = Domain.REMOTE_PROCTORING_ROOM.ATTR_ID,
required = true) final String roomName,
@RequestParam(
name = API.EXAM_API_SEB_CONNECTION_TOKEN,
required = true) final String connectionTokens,
@RequestParam(
name = ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_AUDIO,
required = false) final Boolean sendReceiveAudio,
@ -501,21 +532,46 @@ public class ExamMonitoringController {
return;
}
this.examProcotringRoomService.getRoomConnections(examId, roomName)
.getOrThrow()
.stream()
.forEach(connection -> {
this.sebInstructionService.registerInstruction(
examId,
InstructionType.SEB_RECONFIGURE_SETTINGS,
attributes,
connection.connectionToken,
true)
.onError(error -> log.error(
"Failed to register reconfiguring instruction for connection: {}",
connection.connectionToken,
error));
});
if (StringUtils.isNotBlank(connectionTokens)) {
final boolean single = connectionTokens.contains(Constants.LIST_SEPARATOR);
(single
? Arrays.asList(StringUtils.split(connectionTokens, Constants.LIST_SEPARATOR))
: Arrays.asList(connectionTokens))
.stream()
.forEach(connectionToken -> {
this.sebInstructionService.registerInstruction(
examId,
InstructionType.SEB_RECONFIGURE_SETTINGS,
attributes,
connectionToken,
true)
.onError(error -> log.error(
"Failed to register reconfiguring instruction for connection: {}",
connectionToken,
error));
});
} else if (StringUtils.isNotBlank(roomName)) {
this.examProcotringRoomService.getRoomConnections(examId, roomName)
.getOrThrow()
.stream()
.forEach(connection -> {
this.sebInstructionService.registerInstruction(
examId,
InstructionType.SEB_RECONFIGURE_SETTINGS,
attributes,
connection.connectionToken,
true)
.onError(error -> log.error(
"Failed to register reconfiguring instruction for connection: {}",
connection.connectionToken,
error));
});
} else {
throw new RuntimeException("API attribute validation error: missing "
+ Domain.REMOTE_PROCTORING_ROOM.ATTR_ID + " and/or" +
API.EXAM_API_SEB_CONNECTION_TOKEN + " attribute");
}
}
@RequestMapping(