diff --git a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ProctoringServiceSettings.java b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ProctoringServiceSettings.java index df0458c9..31c9949e 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ProctoringServiceSettings.java +++ b/src/main/java/ch/ethz/seb/sebserver/gbl/model/exam/ProctoringServiceSettings.java @@ -8,6 +8,8 @@ package ch.ethz.seb.sebserver.gbl.model.exam; +import java.util.EnumSet; + import org.apache.commons.lang3.BooleanUtils; import org.hibernate.validator.constraints.URL; @@ -29,12 +31,20 @@ public class ProctoringServiceSettings implements Entity { ZOOM } + public enum ProctoringFeature { + TOWN_HALL, + ONE_TO_ONE, + BROADCAST, + ENABLE_CHAT + } + public static final String ATTR_ENABLE_PROCTORING = "enableProctoring"; public static final String ATTR_SERVER_TYPE = "serverType"; public static final String ATTR_SERVER_URL = "serverURL"; public static final String ATTR_APP_KEY = "appKey"; public static final String ATTR_APP_SECRET = "appSecret"; public static final String ATTR_COLLECTING_ROOM_SIZE = "collectingRoomSize"; + public static final String ATTR_ENABLED_FEATURES = "enabledFeatures"; public static final String ATTR_COLLECT_ALL_ROOM_NAME = "collectAllRoomName"; public static final String ATTR_SERVICE_IN_USE = "serviceInUse"; @@ -60,6 +70,9 @@ public class ProctoringServiceSettings implements Entity { @JsonProperty(ATTR_COLLECTING_ROOM_SIZE) public final Integer collectingRoomSize; + @JsonProperty(ATTR_ENABLED_FEATURES) + public final EnumSet enabledFeatures; + @JsonProperty(ATTR_SERVICE_IN_USE) public final Boolean serviceInUse; @@ -70,6 +83,7 @@ public class ProctoringServiceSettings implements Entity { @JsonProperty(ATTR_SERVER_TYPE) final ProctoringServerType serverType, @JsonProperty(ATTR_SERVER_URL) final String serverURL, @JsonProperty(ATTR_COLLECTING_ROOM_SIZE) final Integer collectingRoomSize, + @JsonProperty(ATTR_ENABLED_FEATURES) final EnumSet enabledFeatures, @JsonProperty(ATTR_SERVICE_IN_USE) final Boolean serviceInUse, @JsonProperty(ATTR_APP_KEY) final String appKey, @JsonProperty(ATTR_APP_SECRET) final CharSequence appSecret) { @@ -79,9 +93,11 @@ public class ProctoringServiceSettings implements Entity { this.serverType = (serverType != null) ? serverType : ProctoringServerType.JITSI_MEET; this.serverURL = serverURL; this.collectingRoomSize = (collectingRoomSize != null) ? collectingRoomSize : 20; + this.enabledFeatures = enabledFeatures != null ? enabledFeatures : EnumSet.allOf(ProctoringFeature.class); this.serviceInUse = serviceInUse; this.appKey = appKey; this.appSecret = appSecret; + } @Override @@ -119,6 +135,10 @@ public class ProctoringServiceSettings implements Entity { return this.collectingRoomSize; } + public EnumSet getEnabledFeatures() { + return this.enabledFeatures; + } + public String getAppKey() { return this.appKey; } @@ -162,7 +182,7 @@ public class ProctoringServiceSettings implements Entity { @Override public String toString() { final StringBuilder builder = new StringBuilder(); - builder.append("ProctoringSettings [examId="); + builder.append("ProctoringServiceSettings [examId="); builder.append(this.examId); builder.append(", enableProctoring="); builder.append(this.enableProctoring); @@ -170,8 +190,16 @@ public class ProctoringServiceSettings implements Entity { builder.append(this.serverType); builder.append(", serverURL="); builder.append(this.serverURL); + builder.append(", appKey="); + builder.append(this.appKey); + builder.append(", appSecret="); + builder.append(this.appSecret); builder.append(", collectingRoomSize="); builder.append(this.collectingRoomSize); + builder.append(", enabledFeatures="); + builder.append(this.enabledFeatures); + builder.append(", serviceInUse="); + builder.append(this.serviceInUse); builder.append("]"); return builder.toString(); } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamProctoringSettings.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamProctoringSettings.java index 201df344..9dcfcb30 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamProctoringSettings.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/ExamProctoringSettings.java @@ -8,11 +8,15 @@ package ch.ethz.seb.sebserver.gui.content; +import java.util.Arrays; +import java.util.EnumSet; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; +import java.util.stream.Collectors; import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; import org.eclipse.swt.widgets.Composite; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,6 +27,7 @@ import ch.ethz.seb.sebserver.gbl.Constants; import ch.ethz.seb.sebserver.gbl.api.API; import ch.ethz.seb.sebserver.gbl.model.EntityKey; 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.profile.GuiProfile; import ch.ethz.seb.sebserver.gbl.util.Utils; @@ -67,6 +72,8 @@ public class ExamProctoringSettings { new LocTextKey("sebserver.exam.proctoring.form.appkey"); private final static LocTextKey SEB_PROCTORING_FORM_SECRET = new LocTextKey("sebserver.exam.proctoring.form.secret"); + private final static LocTextKey SEB_PROCTORING_FORM_FEATURES = + new LocTextKey("sebserver.exam.proctoring.form.features"); Function settingsFunction(final PageService pageService, final boolean modifyGrant) { @@ -131,12 +138,20 @@ public class ExamProctoringSettings { final ProctoringServerType serverType = ProctoringServerType .valueOf(form.getFieldValue(ProctoringServiceSettings.ATTR_SERVER_TYPE)); + final String features = form.getFieldValue(ProctoringServiceSettings.ATTR_ENABLED_FEATURES); + final EnumSet featureFlags = + EnumSet.copyOf(Arrays.asList(StringUtils.split(features, Constants.LIST_SEPARATOR)) + .stream() + .map(str -> ProctoringFeature.valueOf(str)) + .collect(Collectors.toSet())); + examProctoring = new ProctoringServiceSettings( Long.parseLong(entityKey.modelId), enabled, serverType, form.getFieldValue(ProctoringServiceSettings.ATTR_SERVER_URL), Integer.parseInt(form.getFieldValue(ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE)), + featureFlags, false, form.getFieldValue(ProctoringServiceSettings.ATTR_APP_KEY), form.getFieldValue(ProctoringServiceSettings.ATTR_APP_SECRET)); @@ -239,15 +254,6 @@ public class ExamProctoringSettings { ProctoringServiceSettings.ATTR_SERVER_URL, SEB_PROCTORING_FORM_URL, proctoringSettings.serverURL)) - .withDefaultSpanInput(1) - - .addField(FormBuilder.text( - ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE, - SEB_PROCTORING_FORM_ROOM_SIZE, - String.valueOf(proctoringSettings.getCollectingRoomSize())) - .asNumber(numString -> Long.parseLong(numString))) - .withDefaultSpanInput(5) - .withDefaultSpanEmptyCell(4) .addField(FormBuilder.text( ProctoringServiceSettings.ATTR_APP_KEY, @@ -262,6 +268,22 @@ public class ExamProctoringSettings { ? String.valueOf(proctoringSettings.appSecret) : null)) + .withDefaultSpanInput(1) + .addField(FormBuilder.text( + ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE, + SEB_PROCTORING_FORM_ROOM_SIZE, + String.valueOf(proctoringSettings.getCollectingRoomSize())) + .asNumber(numString -> Long.parseLong(numString))) + .withEmptyCellSeparation(true) + .withDefaultSpanEmptyCell(4) + .withDefaultSpanInput(5) + + .addField(FormBuilder.multiCheckboxSelection( + ProctoringServiceSettings.ATTR_ENABLED_FEATURES, + SEB_PROCTORING_FORM_FEATURES, + StringUtils.join(proctoringSettings.enabledFeatures, Constants.LIST_SEPARATOR), + resourceService::examProctoringFeaturesResources)) + .build(); if (proctoringSettings.serviceInUse) { diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java index 3a7f8988..f9bcb8f7 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringClientConnection.java @@ -27,6 +27,7 @@ import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; 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.ConnectionStatus; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent; @@ -367,27 +368,31 @@ public class MonitoringClientConnection implements TemplateComposer { connectionData.clientConnection.status.indicatorActiveStatus); if (connectionData.clientConnection.status == ConnectionStatus.ACTIVE) { - final ProctoringServiceSettings procotringSettings = restService + final ProctoringServiceSettings proctoringSettings = restService .getBuilder(GetProctoringSettings.class) .withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId) .call() .onError(error -> log.error("Failed to get ProctoringServiceSettings", error)) .getOr(null); - if (procotringSettings != null && procotringSettings.enableProctoring) { + if (proctoringSettings != null && proctoringSettings.enableProctoring) { final ProctoringGUIService proctoringGUIService = this.resourceService .getCurrentUser() .getProctoringGUIService(); - actionBuilder - .newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_PROCTORING) - .withEntityKey(parentEntityKey) - .withExec(action -> this.monitoringProctoringService.openOneToOneRoom( - action, - connectionData, - proctoringGUIService)) - .noEventPropagation() - .publish() + if (proctoringSettings.enabledFeatures.contains(ProctoringFeature.ONE_TO_ONE)) { + actionBuilder + .newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_PROCTORING) + .withEntityKey(parentEntityKey) + .withExec(action -> this.monitoringProctoringService.openOneToOneRoom( + action, + connectionData, + proctoringGUIService)) + .noEventPropagation() + .publish(); + } + + actionBuilder .newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_EXAM_ROOM_PROCTORING) .withEntityKey(parentEntityKey) .withExec(action -> this.monitoringProctoringService.openExamCollectionProctorScreen( diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java index 6bc2fde3..89e92c1d 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java @@ -34,6 +34,7 @@ import ch.ethz.seb.sebserver.gbl.model.EntityKey; import ch.ethz.seb.sebserver.gbl.model.exam.Exam; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; 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.ConnectionStatus; import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData; import ch.ethz.seb.sebserver.gbl.model.user.UserRole; @@ -260,22 +261,24 @@ public class MonitoringRunningExam implements TemplateComposer { .getOr(null); if (proctoringSettings != null && proctoringSettings.enableProctoring) { + if (proctoringSettings.enabledFeatures.contains(ProctoringFeature.TOWN_HALL)) { - actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM) - .withEntityKey(entityKey) - .withExec(action -> this.monitoringProctoringService.toggleTownhallRoom(proctoringGUIService, - action)) - .noEventPropagation() - .publish(); + actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM) + .withEntityKey(entityKey) + .withExec(action -> this.monitoringProctoringService.toggleTownhallRoom(proctoringGUIService, + action)) + .noEventPropagation() + .publish(); - if (this.monitoringProctoringService.isTownhallRoomActive(entityKey.modelId)) { - this.pageService.firePageEvent( - new ActionActivationEvent( - true, - new Tuple<>( - ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM, - ActionDefinition.MONITOR_EXAM_CLOSE_TOWNHALL_PROCTOR_ROOM)), - pageContext); + if (this.monitoringProctoringService.isTownhallRoomActive(entityKey.modelId)) { + this.pageService.firePageEvent( + new ActionActivationEvent( + true, + new Tuple<>( + ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM, + ActionDefinition.MONITOR_EXAM_CLOSE_TOWNHALL_PROCTOR_ROOM)), + pageContext); + } } this.monitoringProctoringService.initCollectingRoomActions( diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java index 24a47943..d6d76114 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/ResourceService.java @@ -43,6 +43,7 @@ import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSEBRestriction.PermissionComponent; import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSEBRestriction.WhiteListPath; +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.institution.LmsSetup.LmsType; import ch.ethz.seb.sebserver.gbl.model.sebconfig.AttributeType; @@ -122,6 +123,7 @@ public class ResourceService { public static final String SEB_RESTRICTION_PERMISSIONS_PREFIX = "sebserver.exam.form.sebrestriction.permissions."; public static final String SEB_CLIENT_CONFIG_PURPOSE_PREFIX = "sebserver.clientconfig.config.purpose."; public static final String EXAM_PROCTORING_TYPE_PREFIX = "sebserver.exam.proctoring.type.servertype."; + public static final String EXAM_PROCTORING_FEATURES_PREFIX = "sebserver.exam.proctoring.form.features."; public static final String VDI_TYPE_PREFIX = "sebserver.clientconfig.form.vditype."; private static final String DISABLE_LMS_FLAG = "sebserver.gui.webservice.lms.disable."; @@ -422,6 +424,18 @@ public class ResourceService { .collect(Collectors.toList()); } + public List> examProctoringFeaturesResources() { + return Arrays.stream(ProctoringFeature.values()) + .map(type -> new Tuple3<>( + type.name(), + this.i18nSupport.getText(EXAM_PROCTORING_FEATURES_PREFIX + type.name()), + Utils.formatLineBreaks(this.i18nSupport.getText( + EXAM_PROCTORING_FEATURES_PREFIX + type.name() + Constants.TOOLTIP_TEXT_KEY_SUFFIX, + StringUtils.EMPTY)))) + .sorted(RESOURCE_COMPARATOR) + .collect(Collectors.toList()); + } + public List> vdiTypeResources() { return Arrays.stream(VDIType.values()) .map(type -> new Tuple3<>( diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/JitsiMeetProctoringView.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/JitsiMeetProctoringView.java index b52cb82a..f94c230f 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/JitsiMeetProctoringView.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/JitsiMeetProctoringView.java @@ -23,11 +23,15 @@ import org.springframework.beans.factory.annotation.Value; 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.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.profile.GuiProfile; import ch.ethz.seb.sebserver.gui.GuiServiceInfo; 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.remote.webservice.api.exam.GetProctoringSettings; import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService; import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; @@ -62,6 +66,13 @@ public class JitsiMeetProctoringView extends AbstractProctoringView { final ProctoringGUIService proctoringGUIService = this.pageService .getCurrentUser() .getProctoringGUIService(); + final ProctoringServiceSettings proctoringSettings = this.pageService + .getRestService() + .getBuilder(GetProctoringSettings.class) + .withURIVariable(API.PARAM_MODEL_ID, proctoringWindowData.examId) + .call() + .onError(error -> log.error("Failed to get ProctoringServiceSettings", error)) + .getOr(null); content.setLayout(gridLayout); final GridData headerCell = new GridData(SWT.FILL, SWT.FILL, true, true); @@ -99,31 +110,34 @@ public class JitsiMeetProctoringView extends AbstractProctoringView { closeAction.addListener(SWT.Selection, event -> closeRoom(proctoringGUIService, proctoringWindowData)); final BroadcastActionState broadcastActionState = new BroadcastActionState(); + if (proctoringSettings.enabledFeatures.contains(ProctoringFeature.BROADCAST)) { + final Button broadcastAudioAction = widgetFactory.buttonLocalized(footer, BROADCAST_AUDIO_ON_TEXT_KEY); + broadcastAudioAction.setLayoutData(new RowData()); + broadcastAudioAction.addListener(SWT.Selection, event -> toggleBroadcastAudio( + proctoringWindowData.examId, + proctoringWindowData.connectionData.roomName, + broadcastAudioAction)); + broadcastAudioAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState); - final Button broadcastAudioAction = widgetFactory.buttonLocalized(footer, BROADCAST_AUDIO_ON_TEXT_KEY); - broadcastAudioAction.setLayoutData(new RowData()); - broadcastAudioAction.addListener(SWT.Selection, event -> toggleBroadcastAudio( - proctoringWindowData.examId, - proctoringWindowData.connectionData.roomName, - broadcastAudioAction)); - broadcastAudioAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState); + final Button broadcastVideoAction = widgetFactory.buttonLocalized(footer, BROADCAST_VIDEO_ON_TEXT_KEY); + broadcastVideoAction.setLayoutData(new RowData()); + broadcastVideoAction.addListener(SWT.Selection, event -> toggleBroadcastVideo( + proctoringWindowData.examId, + proctoringWindowData.connectionData.roomName, + broadcastVideoAction, + broadcastAudioAction)); + broadcastVideoAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState); + } - final Button broadcastVideoAction = widgetFactory.buttonLocalized(footer, BROADCAST_VIDEO_ON_TEXT_KEY); - broadcastVideoAction.setLayoutData(new RowData()); - broadcastVideoAction.addListener(SWT.Selection, event -> toggleBroadcastVideo( - proctoringWindowData.examId, - proctoringWindowData.connectionData.roomName, - broadcastVideoAction, - broadcastAudioAction)); - broadcastVideoAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState); - - final Button chatAction = widgetFactory.buttonLocalized(footer, CHAT_ON_TEXT_KEY); - chatAction.setLayoutData(new RowData()); - chatAction.addListener(SWT.Selection, event -> toggleChat( - proctoringWindowData.examId, - proctoringWindowData.connectionData.roomName, - chatAction)); - chatAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState); + if (proctoringSettings.enabledFeatures.contains(ProctoringFeature.ENABLE_CHAT)) { + final Button chatAction = widgetFactory.buttonLocalized(footer, CHAT_ON_TEXT_KEY); + chatAction.setLayoutData(new RowData()); + chatAction.addListener(SWT.Selection, event -> toggleChat( + proctoringWindowData.examId, + proctoringWindowData.connectionData.roomName, + chatAction)); + chatAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState); + } } } diff --git a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ZoomProctoringView.java b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ZoomProctoringView.java index 859c238d..044ecdb8 100644 --- a/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ZoomProctoringView.java +++ b/src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/ZoomProctoringView.java @@ -23,11 +23,15 @@ import org.springframework.beans.factory.annotation.Value; 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.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.profile.GuiProfile; import ch.ethz.seb.sebserver.gui.GuiServiceInfo; 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.remote.webservice.api.exam.GetProctoringSettings; import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService; import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService.ProctoringWindowData; import ch.ethz.seb.sebserver.gui.widget.WidgetFactory; @@ -62,6 +66,13 @@ public class ZoomProctoringView extends AbstractProctoringView { final ProctoringGUIService proctoringGUIService = this.pageService .getCurrentUser() .getProctoringGUIService(); + final ProctoringServiceSettings proctoringSettings = this.pageService + .getRestService() + .getBuilder(GetProctoringSettings.class) + .withURIVariable(API.PARAM_MODEL_ID, proctoringWindowData.examId) + .call() + .onError(error -> log.error("Failed to get ProctoringServiceSettings", error)) + .getOr(null); content.setLayout(gridLayout); final GridData headerCell = new GridData(SWT.FILL, SWT.FILL, true, true); @@ -99,32 +110,33 @@ public class ZoomProctoringView extends AbstractProctoringView { closeAction.addListener(SWT.Selection, event -> closeRoom(proctoringGUIService, proctoringWindowData)); final BroadcastActionState broadcastActionState = new BroadcastActionState(); + if (proctoringSettings.enabledFeatures.contains(ProctoringFeature.BROADCAST)) { + final Button broadcastAudioAction = widgetFactory.buttonLocalized(footer, BROADCAST_AUDIO_ON_TEXT_KEY); + broadcastAudioAction.setLayoutData(new RowData()); + broadcastAudioAction.addListener(SWT.Selection, event -> toggleBroadcastAudio( + proctoringWindowData.examId, + proctoringWindowData.connectionData.roomName, + broadcastAudioAction)); + broadcastAudioAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState); - final Button broadcastAudioAction = widgetFactory.buttonLocalized(footer, BROADCAST_AUDIO_ON_TEXT_KEY); - broadcastAudioAction.setLayoutData(new RowData()); - broadcastAudioAction.addListener(SWT.Selection, event -> toggleBroadcastAudio( - proctoringWindowData.examId, - proctoringWindowData.connectionData.roomName, - broadcastAudioAction)); - broadcastAudioAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState); - - final Button broadcastVideoAction = widgetFactory.buttonLocalized(footer, BROADCAST_VIDEO_ON_TEXT_KEY); - broadcastVideoAction.setLayoutData(new RowData()); - broadcastVideoAction.addListener(SWT.Selection, event -> toggleBroadcastVideo( - proctoringWindowData.examId, - proctoringWindowData.connectionData.roomName, - broadcastVideoAction, - broadcastAudioAction)); - broadcastVideoAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState); - - final Button chatAction = widgetFactory.buttonLocalized(footer, CHAT_ON_TEXT_KEY); - chatAction.setLayoutData(new RowData()); - chatAction.addListener(SWT.Selection, event -> toggleChat( - proctoringWindowData.examId, - proctoringWindowData.connectionData.roomName, - chatAction)); - chatAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState); - + final Button broadcastVideoAction = widgetFactory.buttonLocalized(footer, BROADCAST_VIDEO_ON_TEXT_KEY); + broadcastVideoAction.setLayoutData(new RowData()); + broadcastVideoAction.addListener(SWT.Selection, event -> toggleBroadcastVideo( + proctoringWindowData.examId, + proctoringWindowData.connectionData.roomName, + broadcastVideoAction, + broadcastAudioAction)); + broadcastVideoAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState); + } + if (proctoringSettings.enabledFeatures.contains(ProctoringFeature.ENABLE_CHAT)) { + final Button chatAction = widgetFactory.buttonLocalized(footer, CHAT_ON_TEXT_KEY); + chatAction.setLayoutData(new RowData()); + chatAction.addListener(SWT.Selection, event -> toggleChat( + proctoringWindowData.examId, + proctoringWindowData.connectionData.roomName, + chatAction)); + chatAction.setData(BroadcastActionState.KEY_NAME, broadcastActionState); + } } } diff --git a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java index 377986dc..080bf53a 100644 --- a/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java +++ b/src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/exam/impl/ExamAdminServiceImpl.java @@ -10,13 +10,17 @@ package ch.ethz.seb.sebserver.webservice.servicelayer.exam.impl; import java.util.Arrays; import java.util.Collection; +import java.util.EnumSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; import org.apache.commons.lang3.BooleanUtils; 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; @@ -32,6 +36,7 @@ import ch.ethz.seb.sebserver.gbl.model.exam.Indicator; import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType; import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSEBRestriction; 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.exam.QuizData; import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup; @@ -56,6 +61,9 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring.Exa @WebServiceProfile public class ExamAdminServiceImpl implements ExamAdminService { + private static final Logger log = LoggerFactory.getLogger(ExamAdminServiceImpl.class); + + private static final Object ATTR_ENABLE_PROCTORING = null; private final ExamDAO examDAO; private final IndicatorDAO indicatorDAO; private final AdditionalAttributesDAO additionalAttributesDAO; @@ -203,6 +211,7 @@ public class ExamAdminServiceImpl implements ExamAdminService { getServerType(mapping), getString(mapping, ProctoringServiceSettings.ATTR_SERVER_URL), getCollectingRoomSize(mapping), + getEnabledFeatures(mapping), this.remoteProctoringRoomDAO.isServiceInUse(examId).getOr(true), getString(mapping, ProctoringServiceSettings.ATTR_APP_KEY), getString(mapping, ProctoringServiceSettings.ATTR_APP_SECRET)); @@ -255,6 +264,12 @@ public class ExamAdminServiceImpl implements ExamAdminService { .getOrThrow() .toString()); + this.additionalAttributesDAO.saveAdditionalAttribute( + EntityType.EXAM, + examId, + ProctoringServiceSettings.ATTR_ENABLED_FEATURES, + StringUtils.join(proctoringServiceSettings.enabledFeatures, Constants.LIST_SEPARATOR)); + return proctoringServiceSettings; }); } @@ -310,4 +325,31 @@ public class ExamAdminServiceImpl implements ExamAdminService { } } + private EnumSet getEnabledFeatures(final Map mapping) { + if (mapping.containsKey(ProctoringServiceSettings.ATTR_ENABLED_FEATURES)) { + try { + final String value = mapping.get(ProctoringServiceSettings.ATTR_ENABLED_FEATURES).getValue(); + return EnumSet.copyOf(Arrays.asList(StringUtils.split(value, Constants.LIST_SEPARATOR)) + .stream() + .map(str -> { + try { + return ProctoringFeature.valueOf(str); + } catch (final Exception e) { + log.error( + "Failed to enabled single features for proctoring settings. Skipping. {}", + e.getMessage()); + return null; + } + }) + .filter(Objects::nonNull) + .collect(Collectors.toSet())); + } catch (final Exception e) { + log.error("Failed to get enabled features for proctoring settings. Enable all. {}", e.getMessage()); + return EnumSet.allOf(ProctoringFeature.class); + } + } else { + return EnumSet.allOf(ProctoringFeature.class); + } + } + } diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 246eb3a1..8e867285 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -661,6 +661,11 @@ sebserver.exam.proctoring.form.appkey=Application Key sebserver.exam.proctoring.form.appkey.tooltip=The application key of the proctoring service server sebserver.exam.proctoring.form.secret=Secret sebserver.exam.proctoring.form.secret.tooltip=The secret used to access the proctoring service +sebserver.exam.proctoring.form.features=Enabled Features +sebserver.exam.proctoring.form.features.TOWN_HALL=Town-Hall Room +sebserver.exam.proctoring.form.features.ONE_TO_ONE=One to One Room +sebserver.exam.proctoring.form.features.BROADCAST=Broadcasting Feature +sebserver.exam.proctoring.form.features.ENABLE_CHAT=Chat Feature sebserver.exam.proctoring.type.servertype.JITSI_MEET=Jitsi Meet Server sebserver.exam.proctoring.type.servertype.JITSI_MEET.tooltip=Use a Jitsi Meet server for proctoring diff --git a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/ExamProctoringRoomServiceTest.java b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/ExamProctoringRoomServiceTest.java index a8727a76..ca0e9cee 100644 --- a/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/ExamProctoringRoomServiceTest.java +++ b/src/test/java/ch/ethz/seb/sebserver/webservice/integration/api/admin/ExamProctoringRoomServiceTest.java @@ -62,7 +62,7 @@ public class ExamProctoringRoomServiceTest extends AdministrationAPIIntegrationT this.examAdminService.saveProctoringServiceSettings( 2L, new ProctoringServiceSettings( - 2L, true, ProctoringServerType.JITSI_MEET, "http://jitsi.ch", 1, false, + 2L, true, ProctoringServerType.JITSI_MEET, "http://jitsi.ch", 1, null, false, "app-key", "app.secret")); assertTrue(this.examAdminService.isProctoringEnabled(2L).get());