fixed proctor room action update

This commit is contained in:
anhefti 2020-10-20 10:20:37 +02:00
parent 5b9b336886
commit 55cfc07a9d
4 changed files with 140 additions and 154 deletions

View file

@ -77,7 +77,7 @@ public class MonitoringClientConnection implements TemplateComposer {
// @formatter:off // @formatter:off
private static final String OPEN_SINGEL_ROOM_SCRIPT = private static final String OPEN_SINGEL_ROOM_SCRIPT =
"var existingWin = window.open('', '%s', 'height=420,width=620,location=no,scrollbars=yes,status=no,menubar=yes,toolbar=yes,titlebar=yes,dialog=yes');\n" + "var existingWin = window.open('', '%s', 'height=420,width=640,location=no,scrollbars=yes,status=no,menubar=yes,toolbar=yes,titlebar=yes,dialog=yes');\n" +
"if(existingWin.location.href === 'about:blank'){\n" + "if(existingWin.location.href === 'about:blank'){\n" +
" existingWin.location.href = '%s%s';\n" + " existingWin.location.href = '%s%s';\n" +
" existingWin.focus();\n" + " existingWin.focus();\n" +

View file

@ -49,6 +49,7 @@ import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom; import ch.ethz.seb.sebserver.gbl.model.session.RemoteProctoringRoom;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole; import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile; import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gbl.util.Pair;
import ch.ethz.seb.sebserver.gbl.util.Tuple; import ch.ethz.seb.sebserver.gbl.util.Tuple;
import ch.ethz.seb.sebserver.gbl.util.Utils; import ch.ethz.seb.sebserver.gbl.util.Utils;
import ch.ethz.seb.sebserver.gui.GuiServiceInfo; import ch.ethz.seb.sebserver.gui.GuiServiceInfo;
@ -114,6 +115,7 @@ public class MonitoringRunningExam implements TemplateComposer {
private final InstructionProcessor instructionProcessor; private final InstructionProcessor instructionProcessor;
private final GuiServiceInfo guiServiceInfo; private final GuiServiceInfo guiServiceInfo;
private final long pollInterval; private final long pollInterval;
private final long proctoringRoomUpdateInterval;
private final String remoteProctoringEndpoint; private final String remoteProctoringEndpoint;
private final ProctorRoomConnectionsPopup proctorRoomConnectionsPopup; private final ProctorRoomConnectionsPopup proctorRoomConnectionsPopup;
@ -124,7 +126,8 @@ public class MonitoringRunningExam implements TemplateComposer {
final GuiServiceInfo guiServiceInfo, final GuiServiceInfo guiServiceInfo,
final ProctorRoomConnectionsPopup proctorRoomConnectionsPopup, final ProctorRoomConnectionsPopup proctorRoomConnectionsPopup,
@Value("${sebserver.gui.webservice.poll-interval:1000}") final long pollInterval, @Value("${sebserver.gui.webservice.poll-interval:1000}") final long pollInterval,
@Value("${sebserver.gui.remote.proctoring.entrypoint:/remote-proctoring}") final String remoteProctoringEndpoint) { @Value("${sebserver.gui.remote.proctoring.entrypoint:/remote-proctoring}") final String remoteProctoringEndpoint,
@Value("${sebserver.gui.remote.proctoring.rooms.update.poll-interval:5000}") final long proctoringRoomUpdateInterval) {
this.serverPushService = serverPushService; this.serverPushService = serverPushService;
this.pageService = pageService; this.pageService = pageService;
@ -134,6 +137,7 @@ public class MonitoringRunningExam implements TemplateComposer {
this.pollInterval = pollInterval; this.pollInterval = pollInterval;
this.remoteProctoringEndpoint = remoteProctoringEndpoint; this.remoteProctoringEndpoint = remoteProctoringEndpoint;
this.proctorRoomConnectionsPopup = proctorRoomConnectionsPopup; this.proctorRoomConnectionsPopup = proctorRoomConnectionsPopup;
this.proctoringRoomUpdateInterval = proctoringRoomUpdateInterval;
} }
@Override @Override
@ -325,7 +329,7 @@ public class MonitoringRunningExam implements TemplateComposer {
.getOr(null); .getOr(null);
if (proctoringSettings != null && proctoringSettings.enableProctoring) { if (proctoringSettings != null && proctoringSettings.enableProctoring) {
final Map<RemoteProctoringRoom, TreeItem> availableRooms = new HashMap<>(); final Map<String, Pair<RemoteProctoringRoom, TreeItem>> availableRooms = new HashMap<>();
updateRoomActions( updateRoomActions(
entityKey, entityKey,
pageContext, pageContext,
@ -334,7 +338,7 @@ public class MonitoringRunningExam implements TemplateComposer {
proctoringSettings); proctoringSettings);
this.serverPushService.runServerPush( this.serverPushService.runServerPush(
new ServerPushContext(content, Utils.truePredicate()), new ServerPushContext(content, Utils.truePredicate()),
5000, this.proctoringRoomUpdateInterval,
context -> updateRoomActions( context -> updateRoomActions(
entityKey, entityKey,
pageContext, pageContext,
@ -347,7 +351,7 @@ public class MonitoringRunningExam implements TemplateComposer {
private void updateRoomActions( private void updateRoomActions(
final EntityKey entityKey, final EntityKey entityKey,
final PageContext pageContext, final PageContext pageContext,
final Map<RemoteProctoringRoom, TreeItem> rooms, final Map<String, Pair<RemoteProctoringRoom, TreeItem>> rooms,
final PageActionBuilder actionBuilder, final PageActionBuilder actionBuilder,
final ProctoringSettings proctoringSettings) { final ProctoringSettings proctoringSettings) {
@ -358,9 +362,10 @@ public class MonitoringRunningExam implements TemplateComposer {
.getOrThrow() .getOrThrow()
.stream() .stream()
.forEach(room -> { .forEach(room -> {
if (rooms.containsKey(room)) { if (rooms.containsKey(room.name)) {
// update action // update action
final TreeItem treeItem = rooms.get(room); final TreeItem treeItem = rooms.get(room.name).b;
rooms.put(room.name, new Pair<>(room, treeItem));
treeItem.setText(i18nSupport.getText(new LocTextKey( treeItem.setText(i18nSupport.getText(new LocTextKey(
ActionDefinition.MONITOR_EXAM_VIEW_PROCTOR_ROOM.title.name, ActionDefinition.MONITOR_EXAM_VIEW_PROCTOR_ROOM.title.name,
room.subject, room.subject,
@ -372,7 +377,13 @@ public class MonitoringRunningExam implements TemplateComposer {
final PageAction action = final PageAction action =
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_VIEW_PROCTOR_ROOM) actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_VIEW_PROCTOR_ROOM)
.withEntityKey(entityKey) .withEntityKey(entityKey)
.withExec(a -> showExamProctoringRoom(proctoringSettings, room, rooms, a)) .withExec(_action -> {
final int actualRoomSize = getActualRoomSize(room, rooms);
if (actualRoomSize <= 0) {
return _action;
}
return showExamProctoringRoom(proctoringSettings, room, _action);
})
.withNameAttributes( .withNameAttributes(
room.subject, room.subject,
room.roomSize, room.roomSize,
@ -380,15 +391,11 @@ public class MonitoringRunningExam implements TemplateComposer {
.noEventPropagation() .noEventPropagation()
.create(); .create();
this.pageService.publishAction(action, treeItem -> rooms.put(room, treeItem)); this.pageService.publishAction(
action,
_treeItem -> rooms.put(room.name, new Pair<>(room, _treeItem)));
addRoomConnectionsPopupListener(entityKey, pageContext, rooms); addRoomConnectionsPopupListener(entityKey, pageContext, rooms);
rooms.entrySet().stream() processProctorRoomActionActivation(rooms.get(room.name).b, room, pageContext);
.filter(entry -> entry.getKey().equals(room))
.findFirst()
.ifPresent(entry -> processProctorRoomActionActivation(
entry.getValue(),
room,
pageContext));
} }
}); });
} }
@ -414,10 +421,10 @@ public class MonitoringRunningExam implements TemplateComposer {
private void addRoomConnectionsPopupListener( private void addRoomConnectionsPopupListener(
final EntityKey entityKey, final EntityKey entityKey,
final PageContext pageContext, final PageContext pageContext,
final Map<RemoteProctoringRoom, TreeItem> rooms) { final Map<String, Pair<RemoteProctoringRoom, TreeItem>> rooms) {
if (!rooms.isEmpty()) { if (!rooms.isEmpty()) {
final TreeItem treeItem = rooms.values().iterator().next(); final TreeItem treeItem = rooms.values().iterator().next().b;
final Tree tree = treeItem.getParent(); final Tree tree = treeItem.getParent();
if (tree.getData(SHOW_CONNECTION_ACTION_APPLIED) == null) { if (tree.getData(SHOW_CONNECTION_ACTION_APPLIED) == null) {
tree.addListener(SWT.Selection, event -> { tree.addListener(SWT.Selection, event -> {
@ -426,16 +433,17 @@ public class MonitoringRunningExam implements TemplateComposer {
if (event.button == 3) { if (event.button == 3) {
rooms.entrySet() rooms.entrySet()
.stream() .stream()
.filter(e -> e.getValue().equals(item)) .filter(e -> e.getValue().b.equals(item))
.findFirst() .findFirst()
.ifPresent(e -> { .ifPresent(e -> {
if (e.getKey().roomSize > 0) { final RemoteProctoringRoom room = e.getValue().a;
if (room.roomSize > 0) {
final PageContext pc = pageContext.copy() final PageContext pc = pageContext.copy()
.clearAttributes() .clearAttributes()
.withEntityKey(new EntityKey(e.getKey().getName(), .withEntityKey(new EntityKey(room.name,
EntityType.REMOTE_PROCTORING_ROOM)) EntityType.REMOTE_PROCTORING_ROOM))
.withParentEntityKey(entityKey); .withParentEntityKey(entityKey);
this.proctorRoomConnectionsPopup.show(pc, e.getKey().getSubject()); this.proctorRoomConnectionsPopup.show(pc, room.subject);
} }
}); });
} }
@ -445,25 +453,18 @@ public class MonitoringRunningExam implements TemplateComposer {
} }
} }
private int getActualRoomSize(final RemoteProctoringRoom room, final Map<RemoteProctoringRoom, TreeItem> rooms) { private int getActualRoomSize(
return rooms.entrySet().stream() final RemoteProctoringRoom room,
.filter(entry -> entry.getKey().equals(room)) final Map<String, Pair<RemoteProctoringRoom, TreeItem>> rooms) {
.findFirst()
.map(entry -> entry.getKey().roomSize) return rooms.get(room.name).a.roomSize;
.orElseGet(() -> 1);
} }
private PageAction showExamProctoringRoom( private PageAction showExamProctoringRoom(
final ProctoringSettings proctoringSettings, final ProctoringSettings proctoringSettings,
final RemoteProctoringRoom room, final RemoteProctoringRoom room,
final Map<RemoteProctoringRoom, TreeItem> rooms,
final PageAction action) { final PageAction action) {
final int actualRoomSize = getActualRoomSize(room, rooms);
if (actualRoomSize <= 0) {
return action;
}
final SEBProctoringConnectionData proctoringConnectionData = this.pageService final SEBProctoringConnectionData proctoringConnectionData = this.pageService
.getRestService() .getRestService()
.getBuilder(GetProctorRoomConnectionData.class) .getBuilder(GetProctorRoomConnectionData.class)

View file

@ -17,6 +17,8 @@ import org.eclipse.swt.layout.RowData;
import org.eclipse.swt.layout.RowLayout; import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Composite;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -41,6 +43,8 @@ import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
@GuiProfile @GuiProfile
public class JitsiMeetProctoringView implements RemoteProctoringView { public class JitsiMeetProctoringView implements RemoteProctoringView {
private static final Logger log = LoggerFactory.getLogger(JitsiMeetProctoringView.class);
private static final LocTextKey CLOSE_WINDOW_TEXT_KEY = private static final LocTextKey CLOSE_WINDOW_TEXT_KEY =
new LocTextKey("sebserver.monitoring.exam.proctoring.action.close"); new LocTextKey("sebserver.monitoring.exam.proctoring.action.close");
private static final LocTextKey BROADCAST_AUDIO_ON_TEXT_KEY = private static final LocTextKey BROADCAST_AUDIO_ON_TEXT_KEY =
@ -92,8 +96,17 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
.getProctoringGUIService() .getProctoringGUIService()
.closeRoom(proctoringWindowData.connectionData.roomName)); .closeRoom(proctoringWindowData.connectionData.roomName));
final String url = this.guiServiceInfo.getExternalServerURIBuilder().toUriString() final String url = this.guiServiceInfo
+ this.remoteProctoringEndpoint + this.remoteProctoringViewServletEndpoint + "/"; .getExternalServerURIBuilder()
.toUriString()
+ this.remoteProctoringEndpoint
+ this.remoteProctoringViewServletEndpoint
+ Constants.SLASH;
if (log.isDebugEnabled()) {
log.debug("Open proctoring Servlet in IFrame with URL: {}", url);
}
final Browser browser = new Browser(content, SWT.NONE | SWT.NO_SCROLL); final Browser browser = new Browser(content, SWT.NONE | SWT.NO_SCROLL);
browser.setLayout(new GridLayout()); browser.setLayout(new GridLayout());
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true); final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
@ -274,7 +287,7 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
return ProctoringServerType.JITSI_MEET; return ProctoringServerType.JITSI_MEET;
} }
private static class BroadcastActionState { private static final class BroadcastActionState {
public static final String KEY_NAME = "BroadcastActionState"; public static final String KEY_NAME = "BroadcastActionState";
boolean audio = false; boolean audio = false;
boolean video = false; boolean video = false;

View file

@ -410,68 +410,13 @@ public class ExamMonitoringController {
this.authorization.checkRead( this.authorization.checkRead(
this.examSessionService.getExamDAO().byPK(examId).getOrThrow()); this.examSessionService.getExamDAO().byPK(examId).getOrThrow());
final Map<String, String> attributes = new HashMap<>(); final Map<String, String> attributes = createProctorInstructionAttributes(
if (BooleanUtils.isTrue(sendReceiveAudio)) { sendReceiveAudio,
attributes.put( sendReceiveVideo,
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_AUDIO, sendAllowChat,
Constants.TRUE_STRING); Constants.TRUE_STRING);
}
if (BooleanUtils.isTrue(sendReceiveVideo)) {
attributes.put(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_VIDEO,
Constants.TRUE_STRING);
}
if (BooleanUtils.isTrue(sendAllowChat)) {
attributes.put(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_ALLOW_CHAT,
Constants.TRUE_STRING);
}
if (attributes.isEmpty()) { sendProctoringInstructions(examId, roomName, connectionTokens, attributes);
log.warn("Missing reconfigure instruction attributes. Skip sending empty instruction to SEB clients");
return;
}
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( @RequestMapping(
@ -510,68 +455,18 @@ public class ExamMonitoringController {
this.authorization.checkRead( this.authorization.checkRead(
this.examSessionService.getExamDAO().byPK(examId).getOrThrow()); this.examSessionService.getExamDAO().byPK(examId).getOrThrow());
final Map<String, String> attributes = new HashMap<>(); final Map<String, String> attributes = createProctorInstructionAttributes(
if (BooleanUtils.isTrue(sendReceiveAudio)) { sendReceiveAudio,
attributes.put( sendReceiveVideo,
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_AUDIO, sendAllowChat,
Constants.FALSE_STRING); Constants.FALSE_STRING);
}
if (BooleanUtils.isTrue(sendReceiveVideo)) {
attributes.put(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_VIDEO,
Constants.FALSE_STRING);
}
if (BooleanUtils.isTrue(sendAllowChat)) {
attributes.put(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_ALLOW_CHAT,
Constants.FALSE_STRING);
}
if (attributes.isEmpty()) { if (attributes.isEmpty()) {
log.warn("Missing reconfigure instruction attributes. Skip sending empty instruction to SEB clients"); log.warn("Missing reconfigure instruction attributes. Skip sending empty instruction to SEB clients");
return; return;
} }
if (StringUtils.isNotBlank(connectionTokens)) { sendProctoringInstructions(examId, roomName, connectionTokens, attributes);
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( @RequestMapping(
@ -695,6 +590,83 @@ public class ExamMonitoringController {
.getOrThrow(); .getOrThrow();
} }
private void sendProctoringInstructions(
final Long examId,
final String roomName,
final String connectionTokens,
final Map<String, String> attributes) {
if (attributes.isEmpty()) {
log.warn("Missing reconfigure instruction attributes. Skip sending empty instruction to SEB clients");
return;
}
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");
}
}
private Map<String, String> createProctorInstructionAttributes(
final Boolean sendReceiveAudio,
final Boolean sendReceiveVideo,
final Boolean sendAllowChat,
final String flagValue) {
final Map<String, String> attributes = new HashMap<>();
if (BooleanUtils.isTrue(sendReceiveAudio)) {
attributes.put(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_AUDIO,
flagValue);
}
if (BooleanUtils.isTrue(sendReceiveVideo)) {
attributes.put(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_RECEIVE_VIDEO,
flagValue);
}
if (BooleanUtils.isTrue(sendAllowChat)) {
attributes.put(
ClientInstruction.SEB_INSTRUCTION_ATTRIBUTES.SEB_RECONFIGURE_SETTINGS.JITSI_ALLOW_CHAT,
flagValue);
}
return attributes;
}
//**** Proctoring //**** Proctoring
//*********************************************************************************************** //***********************************************************************************************