Merge remote-tracking branch 'origin/dev-1.2' into development
Conflicts: src/main/java/ch/ethz/seb/sebserver/gui/content/ExamForm.java src/main/java/ch/ethz/seb/sebserver/gui/content/ExamProctoringSettings.java
This commit is contained in:
commit
36f546960b
11 changed files with 129 additions and 68 deletions
|
@ -49,6 +49,7 @@ public class ProctoringServiceSettings implements Entity {
|
||||||
public static final String ATTR_ENABLED_FEATURES = "enabledFeatures";
|
public static final String ATTR_ENABLED_FEATURES = "enabledFeatures";
|
||||||
public static final String ATTR_COLLECT_ALL_ROOM_NAME = "collectAllRoomName";
|
public static final String ATTR_COLLECT_ALL_ROOM_NAME = "collectAllRoomName";
|
||||||
public static final String ATTR_SERVICE_IN_USE = "serviceInUse";
|
public static final String ATTR_SERVICE_IN_USE = "serviceInUse";
|
||||||
|
public static final String ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM = "useZoomAppClientForCollectingRoom";
|
||||||
|
|
||||||
@JsonProperty(Domain.EXAM.ATTR_ID)
|
@JsonProperty(Domain.EXAM.ATTR_ID)
|
||||||
public final Long examId;
|
public final Long examId;
|
||||||
|
@ -84,6 +85,9 @@ public class ProctoringServiceSettings implements Entity {
|
||||||
@JsonProperty(ATTR_SERVICE_IN_USE)
|
@JsonProperty(ATTR_SERVICE_IN_USE)
|
||||||
public final Boolean serviceInUse;
|
public final Boolean serviceInUse;
|
||||||
|
|
||||||
|
@JsonProperty(ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM)
|
||||||
|
public final Boolean useZoomAppClientForCollectingRoom;
|
||||||
|
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
public ProctoringServiceSettings(
|
public ProctoringServiceSettings(
|
||||||
@JsonProperty(Domain.EXAM.ATTR_ID) final Long examId,
|
@JsonProperty(Domain.EXAM.ATTR_ID) final Long examId,
|
||||||
|
@ -96,7 +100,8 @@ public class ProctoringServiceSettings implements Entity {
|
||||||
@JsonProperty(ATTR_APP_KEY) final String appKey,
|
@JsonProperty(ATTR_APP_KEY) final String appKey,
|
||||||
@JsonProperty(ATTR_APP_SECRET) final CharSequence appSecret,
|
@JsonProperty(ATTR_APP_SECRET) final CharSequence appSecret,
|
||||||
@JsonProperty(ATTR_SDK_KEY) final String sdkKey,
|
@JsonProperty(ATTR_SDK_KEY) final String sdkKey,
|
||||||
@JsonProperty(ATTR_SDK_SECRET) final CharSequence sdkSecret) {
|
@JsonProperty(ATTR_SDK_SECRET) final CharSequence sdkSecret,
|
||||||
|
@JsonProperty(ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM) final Boolean useZoomAppClientForCollectingRoom) {
|
||||||
|
|
||||||
this.examId = examId;
|
this.examId = examId;
|
||||||
this.enableProctoring = BooleanUtils.isTrue(enableProctoring);
|
this.enableProctoring = BooleanUtils.isTrue(enableProctoring);
|
||||||
|
@ -109,7 +114,7 @@ public class ProctoringServiceSettings implements Entity {
|
||||||
this.appSecret = appSecret;
|
this.appSecret = appSecret;
|
||||||
this.sdkKey = sdkKey;
|
this.sdkKey = sdkKey;
|
||||||
this.sdkSecret = sdkSecret;
|
this.sdkSecret = sdkSecret;
|
||||||
|
this.useZoomAppClientForCollectingRoom = BooleanUtils.toBoolean(useZoomAppClientForCollectingRoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -171,6 +176,10 @@ public class ProctoringServiceSettings implements Entity {
|
||||||
return this.serviceInUse;
|
return this.serviceInUse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean getUseZoomAppClientForCollectingRoom() {
|
||||||
|
return this.useZoomAppClientForCollectingRoom;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
final int prime = 31;
|
final int prime = 31;
|
||||||
|
|
|
@ -389,7 +389,7 @@ public class ExamForm implements TemplateComposer {
|
||||||
.withExec(this.examCreateClientConfigPopup.exportFunction(
|
.withExec(this.examCreateClientConfigPopup.exportFunction(
|
||||||
exam.institutionId,
|
exam.institutionId,
|
||||||
exam.getName()))
|
exam.getName()))
|
||||||
.publishIf(() -> writeGrant && readonly)
|
.publishIf(() -> editable && readonly)
|
||||||
|
|
||||||
.newAction(ActionDefinition.EXAM_MODIFY_SEB_RESTRICTION_DETAILS)
|
.newAction(ActionDefinition.EXAM_MODIFY_SEB_RESTRICTION_DETAILS)
|
||||||
.withEntityKey(entityKey)
|
.withEntityKey(entityKey)
|
||||||
|
@ -415,13 +415,13 @@ public class ExamForm implements TemplateComposer {
|
||||||
.withEntityKey(entityKey)
|
.withEntityKey(entityKey)
|
||||||
.withExec(this.examProctoringSettings.settingsFunction(this.pageService, modifyGrant))
|
.withExec(this.examProctoringSettings.settingsFunction(this.pageService, modifyGrant))
|
||||||
.noEventPropagation()
|
.noEventPropagation()
|
||||||
.publishIf(() -> proctoringEnabled && readonly)
|
.publishIf(() -> editable && proctoringEnabled && readonly)
|
||||||
|
|
||||||
.newAction(ActionDefinition.EXAM_PROCTORING_OFF)
|
.newAction(ActionDefinition.EXAM_PROCTORING_OFF)
|
||||||
.withEntityKey(entityKey)
|
.withEntityKey(entityKey)
|
||||||
.withExec(this.examProctoringSettings.settingsFunction(this.pageService, modifyGrant))
|
.withExec(this.examProctoringSettings.settingsFunction(this.pageService, modifyGrant))
|
||||||
.noEventPropagation()
|
.noEventPropagation()
|
||||||
.publishIf(() -> !proctoringEnabled && readonly)
|
.publishIf(() -> editable && !proctoringEnabled && readonly)
|
||||||
|
|
||||||
.newAction(ActionDefinition.EXAM_DELETE)
|
.newAction(ActionDefinition.EXAM_DELETE)
|
||||||
.withEntityKey(entityKey)
|
.withEntityKey(entityKey)
|
||||||
|
|
|
@ -76,6 +76,8 @@ public class ExamProctoringSettings {
|
||||||
new LocTextKey("sebserver.exam.proctoring.form.sdkkey");
|
new LocTextKey("sebserver.exam.proctoring.form.sdkkey");
|
||||||
private final static LocTextKey SEB_PROCTORING_FORM_SDKSECRET =
|
private final static LocTextKey SEB_PROCTORING_FORM_SDKSECRET =
|
||||||
new LocTextKey("sebserver.exam.proctoring.form.sdksecret");
|
new LocTextKey("sebserver.exam.proctoring.form.sdksecret");
|
||||||
|
private final static LocTextKey SEB_PROCTORING_FORM_USE_ZOOM_APP_CLIENT =
|
||||||
|
new LocTextKey("sebserver.exam.proctoring.form.useZoomAppClient");
|
||||||
|
|
||||||
private final static LocTextKey SEB_PROCTORING_FORM_FEATURES =
|
private final static LocTextKey SEB_PROCTORING_FORM_FEATURES =
|
||||||
new LocTextKey("sebserver.exam.proctoring.form.features");
|
new LocTextKey("sebserver.exam.proctoring.form.features");
|
||||||
|
@ -162,7 +164,9 @@ public class ExamProctoringSettings {
|
||||||
form.getFieldValue(ProctoringServiceSettings.ATTR_APP_KEY),
|
form.getFieldValue(ProctoringServiceSettings.ATTR_APP_KEY),
|
||||||
form.getFieldValue(ProctoringServiceSettings.ATTR_APP_SECRET),
|
form.getFieldValue(ProctoringServiceSettings.ATTR_APP_SECRET),
|
||||||
form.getFieldValue(ProctoringServiceSettings.ATTR_SDK_KEY),
|
form.getFieldValue(ProctoringServiceSettings.ATTR_SDK_KEY),
|
||||||
form.getFieldValue(ProctoringServiceSettings.ATTR_SDK_SECRET));
|
form.getFieldValue(ProctoringServiceSettings.ATTR_SDK_SECRET),
|
||||||
|
BooleanUtils.toBoolean(form.getFieldValue(
|
||||||
|
ProctoringServiceSettings.ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM)));
|
||||||
|
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
log.error("Unexpected error while trying to get settings from form: ", e);
|
log.error("Unexpected error while trying to get settings from form: ", e);
|
||||||
|
@ -232,8 +236,6 @@ public class ExamProctoringSettings {
|
||||||
.copyOf(content)
|
.copyOf(content)
|
||||||
.clearEntityKeys();
|
.clearEntityKeys();
|
||||||
|
|
||||||
final boolean isZoom = proctoringSettings.serverType == ProctoringServerType.ZOOM;
|
|
||||||
|
|
||||||
final FormHandle<ProctoringServiceSettings> formHandle = this.pageService.formBuilder(
|
final FormHandle<ProctoringServiceSettings> formHandle = this.pageService.formBuilder(
|
||||||
formContext)
|
formContext)
|
||||||
.withDefaultSpanInput(5)
|
.withDefaultSpanInput(5)
|
||||||
|
@ -258,8 +260,7 @@ public class ExamProctoringSettings {
|
||||||
ProctoringServiceSettings.ATTR_SERVER_TYPE,
|
ProctoringServiceSettings.ATTR_SERVER_TYPE,
|
||||||
SEB_PROCTORING_FORM_TYPE,
|
SEB_PROCTORING_FORM_TYPE,
|
||||||
proctoringSettings.serverType.name(),
|
proctoringSettings.serverType.name(),
|
||||||
resourceService::examProctoringTypeResources)
|
resourceService::examProctoringTypeResources))
|
||||||
.withSelectionListener(this::serviceSelection))
|
|
||||||
|
|
||||||
.addField(FormBuilder.text(
|
.addField(FormBuilder.text(
|
||||||
ProctoringServiceSettings.ATTR_SERVER_URL,
|
ProctoringServiceSettings.ATTR_SERVER_URL,
|
||||||
|
@ -282,8 +283,7 @@ public class ExamProctoringSettings {
|
||||||
.addField(FormBuilder.text(
|
.addField(FormBuilder.text(
|
||||||
ProctoringServiceSettings.ATTR_SDK_KEY,
|
ProctoringServiceSettings.ATTR_SDK_KEY,
|
||||||
SEB_PROCTORING_FORM_SDKKEY,
|
SEB_PROCTORING_FORM_SDKKEY,
|
||||||
proctoringSettings.sdkKey)
|
proctoringSettings.sdkKey))
|
||||||
.visibleIf(isZoom))
|
|
||||||
.withEmptyCellSeparation(false)
|
.withEmptyCellSeparation(false)
|
||||||
|
|
||||||
.addField(FormBuilder.password(
|
.addField(FormBuilder.password(
|
||||||
|
@ -291,8 +291,7 @@ public class ExamProctoringSettings {
|
||||||
SEB_PROCTORING_FORM_SDKSECRET,
|
SEB_PROCTORING_FORM_SDKSECRET,
|
||||||
(proctoringSettings.sdkSecret != null)
|
(proctoringSettings.sdkSecret != null)
|
||||||
? String.valueOf(proctoringSettings.sdkSecret)
|
? String.valueOf(proctoringSettings.sdkSecret)
|
||||||
: null)
|
: null))
|
||||||
.visibleIf(isZoom))
|
|
||||||
|
|
||||||
.withDefaultSpanInput(1)
|
.withDefaultSpanInput(1)
|
||||||
.addField(FormBuilder.text(
|
.addField(FormBuilder.text(
|
||||||
|
@ -304,6 +303,14 @@ public class ExamProctoringSettings {
|
||||||
.withDefaultSpanEmptyCell(4)
|
.withDefaultSpanEmptyCell(4)
|
||||||
.withDefaultSpanInput(5)
|
.withDefaultSpanInput(5)
|
||||||
|
|
||||||
|
.addField(FormBuilder.checkbox(
|
||||||
|
ProctoringServiceSettings.ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM,
|
||||||
|
SEB_PROCTORING_FORM_USE_ZOOM_APP_CLIENT,
|
||||||
|
String.valueOf(proctoringSettings.useZoomAppClientForCollectingRoom)))
|
||||||
|
.withDefaultSpanInput(5)
|
||||||
|
.withEmptyCellSeparation(true)
|
||||||
|
.withDefaultSpanEmptyCell(1)
|
||||||
|
|
||||||
.addField(FormBuilder.multiCheckboxSelection(
|
.addField(FormBuilder.multiCheckboxSelection(
|
||||||
ProctoringServiceSettings.ATTR_ENABLED_FEATURES,
|
ProctoringServiceSettings.ATTR_ENABLED_FEATURES,
|
||||||
SEB_PROCTORING_FORM_FEATURES,
|
SEB_PROCTORING_FORM_FEATURES,
|
||||||
|
@ -319,14 +326,6 @@ public class ExamProctoringSettings {
|
||||||
|
|
||||||
return () -> formHandle;
|
return () -> formHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void serviceSelection(final Form form) {
|
|
||||||
final boolean isZoom = ProctoringServerType.ZOOM.name()
|
|
||||||
.equals(form.getFieldValue(ProctoringServiceSettings.ATTR_SERVER_TYPE));
|
|
||||||
|
|
||||||
form.setFieldVisible(isZoom, ProctoringServiceSettings.ATTR_SDK_KEY);
|
|
||||||
form.setFieldVisible(isZoom, ProctoringServiceSettings.ATTR_SDK_SECRET);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.commons.lang3.BooleanUtils;
|
import org.apache.commons.lang3.BooleanUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.eclipse.rap.rwt.RWT;
|
import org.eclipse.rap.rwt.RWT;
|
||||||
import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
|
import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
|
||||||
import org.eclipse.swt.graphics.Color;
|
import org.eclipse.swt.graphics.Color;
|
||||||
|
@ -83,14 +84,19 @@ public class MonitoringProctoringService {
|
||||||
static final String OPEN_ROOM_SCRIPT =
|
static final String OPEN_ROOM_SCRIPT =
|
||||||
"try {\n" +
|
"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" +
|
"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" +
|
"try {\n" +
|
||||||
"if(existingWin.location.href === 'about:blank'){\n" +
|
"if(existingWin.location.href === 'about:blank'){\n" +
|
||||||
|
" existingWin.document.title = '%s';\n" +
|
||||||
" existingWin.location.href = '%s%s';\n" +
|
" existingWin.location.href = '%s%s';\n" +
|
||||||
" existingWin.focus();\n" +
|
" existingWin.focus();\n" +
|
||||||
"} else {\n" +
|
"} else {\n" +
|
||||||
" existingWin.focus();\n" +
|
" existingWin.focus();\n" +
|
||||||
"}" +
|
"}" +
|
||||||
"}\n" +
|
"} catch(secErr) {\n" +
|
||||||
|
" alert(\"Unexpected Javascript Error happened: \" + secErr);\n"+
|
||||||
|
" existingWin.focus();\n" +
|
||||||
|
"}" +
|
||||||
|
"}" +
|
||||||
"catch(err) {\n" +
|
"catch(err) {\n" +
|
||||||
" alert(\"Unexpected Javascript Error happened: \" + err);\n"+
|
" alert(\"Unexpected Javascript Error happened: \" + err);\n"+
|
||||||
"}";
|
"}";
|
||||||
|
@ -288,35 +294,68 @@ public class MonitoringProctoringService {
|
||||||
String.valueOf(proctoringSettings.examId),
|
String.valueOf(proctoringSettings.examId),
|
||||||
proctoringConnectionData);
|
proctoringConnectionData);
|
||||||
|
|
||||||
final String script = String.format(
|
if (proctoringSettings.useZoomAppClientForCollectingRoom &&
|
||||||
OPEN_ROOM_SCRIPT,
|
StringUtils.isNotBlank(extractZoomStartLink(room))) {
|
||||||
room.name,
|
|
||||||
800,
|
|
||||||
1200,
|
|
||||||
room.name,
|
|
||||||
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
|
|
||||||
this.remoteProctoringEndpoint);
|
|
||||||
|
|
||||||
RWT.getClient()
|
final String startLink = extractZoomStartLink(room);
|
||||||
.getService(JavaScriptExecutor.class)
|
final String script = String.format(
|
||||||
.execute(script);
|
OPEN_ROOM_SCRIPT,
|
||||||
|
room.name,
|
||||||
|
800,
|
||||||
|
1200,
|
||||||
|
room.name,
|
||||||
|
startLink,
|
||||||
|
"");
|
||||||
|
|
||||||
final boolean newWindow = this.pageService.getCurrentUser()
|
RWT.getClient()
|
||||||
.getProctoringGUIService()
|
.getService(JavaScriptExecutor.class)
|
||||||
.registerProctoringWindow(String.valueOf(room.examId), room.name, room.name);
|
.execute(script);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
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()
|
||||||
|
.onError(error -> log.error("Failed to notify proctoring room opened: ", error));
|
||||||
|
}
|
||||||
|
|
||||||
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()
|
|
||||||
.onError(error -> log.error("Failed to notify proctoring room opened: ", error));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String extractZoomStartLink(final RemoteProctoringRoom room) {
|
||||||
|
try {
|
||||||
|
final Map<String, String> data =
|
||||||
|
this.jsonMapper.readValue(room.additionalRoomData, new TypeReference<Map<String, String>>() {
|
||||||
|
});
|
||||||
|
return data.get("start_url");
|
||||||
|
} catch (final Exception e) {
|
||||||
|
log.error("Failed to extract Zoom start link: ", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public PageAction openOneToOneRoom(
|
public PageAction openOneToOneRoom(
|
||||||
final PageAction action,
|
final PageAction action,
|
||||||
final ClientConnectionData connectionData,
|
final ClientConnectionData connectionData,
|
||||||
|
@ -349,9 +388,9 @@ public class MonitoringProctoringService {
|
||||||
final JavaScriptExecutor javaScriptExecutor = RWT.getClient().getService(JavaScriptExecutor.class);
|
final JavaScriptExecutor javaScriptExecutor = RWT.getClient().getService(JavaScriptExecutor.class);
|
||||||
final String script = String.format(
|
final String script = String.format(
|
||||||
MonitoringProctoringService.OPEN_ROOM_SCRIPT,
|
MonitoringProctoringService.OPEN_ROOM_SCRIPT,
|
||||||
connectionToken,
|
connectionData.clientConnection.userSessionId,
|
||||||
420,
|
800,
|
||||||
640,
|
1200,
|
||||||
connectionData.clientConnection.userSessionId,
|
connectionData.clientConnection.userSessionId,
|
||||||
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
|
this.guiServiceInfo.getExternalServerURIBuilder().toUriString(),
|
||||||
this.remoteProctoringEndpoint);
|
this.remoteProctoringEndpoint);
|
||||||
|
|
|
@ -849,7 +849,7 @@ public class ExamDAOImpl implements ExamDAO {
|
||||||
// No course access on the LMS. This means we can't get any quizzes from this LMSSetup at the moment
|
// No course access on the LMS. This means we can't get any quizzes from this LMSSetup at the moment
|
||||||
// All exams are marked as corrupt because of LMS Setup failure
|
// All exams are marked as corrupt because of LMS Setup failure
|
||||||
|
|
||||||
log.warn("Failed to get quizzes form LMS Setup. No access to LMS {}", lmsSetup);
|
log.warn("Failed to get quizzes form LMS Setup. No access to LMS {}", lmsSetup.lmsSetup());
|
||||||
|
|
||||||
return recordMapping.entrySet()
|
return recordMapping.entrySet()
|
||||||
.stream()
|
.stream()
|
||||||
|
|
|
@ -215,7 +215,8 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
||||||
getString(mapping, ProctoringServiceSettings.ATTR_APP_KEY),
|
getString(mapping, ProctoringServiceSettings.ATTR_APP_KEY),
|
||||||
getString(mapping, ProctoringServiceSettings.ATTR_APP_SECRET),
|
getString(mapping, ProctoringServiceSettings.ATTR_APP_SECRET),
|
||||||
getString(mapping, ProctoringServiceSettings.ATTR_SDK_KEY),
|
getString(mapping, ProctoringServiceSettings.ATTR_SDK_KEY),
|
||||||
getString(mapping, ProctoringServiceSettings.ATTR_SDK_SECRET));
|
getString(mapping, ProctoringServiceSettings.ATTR_SDK_SECRET),
|
||||||
|
getBoolean(mapping, ProctoringServiceSettings.ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,6 +288,12 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
||||||
ProctoringServiceSettings.ATTR_ENABLED_FEATURES,
|
ProctoringServiceSettings.ATTR_ENABLED_FEATURES,
|
||||||
StringUtils.join(proctoringServiceSettings.enabledFeatures, Constants.LIST_SEPARATOR));
|
StringUtils.join(proctoringServiceSettings.enabledFeatures, Constants.LIST_SEPARATOR));
|
||||||
|
|
||||||
|
this.additionalAttributesDAO.saveAdditionalAttribute(
|
||||||
|
EntityType.EXAM,
|
||||||
|
examId,
|
||||||
|
ProctoringServiceSettings.ATTR_USE_ZOOM_APP_CLIENT_COLLECTING_ROOM,
|
||||||
|
String.valueOf(proctoringServiceSettings.useZoomAppClientForCollectingRoom));
|
||||||
|
|
||||||
return proctoringServiceSettings;
|
return proctoringServiceSettings;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -334,6 +341,14 @@ public class ExamAdminServiceImpl implements ExamAdminService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Boolean getBoolean(final Map<String, AdditionalAttributeRecord> mapping, final String name) {
|
||||||
|
if (mapping.containsKey(name)) {
|
||||||
|
return BooleanUtils.toBooleanObject(mapping.get(name).getValue());
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Integer getCollectingRoomSize(final Map<String, AdditionalAttributeRecord> mapping) {
|
private Integer getCollectingRoomSize(final Map<String, AdditionalAttributeRecord> mapping) {
|
||||||
if (mapping.containsKey(ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE)) {
|
if (mapping.containsKey(ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE)) {
|
||||||
return Integer.valueOf(mapping.get(ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE).getValue());
|
return Integer.valueOf(mapping.get(ProctoringServiceSettings.ATTR_COLLECTING_ROOM_SIZE).getValue());
|
||||||
|
|
|
@ -133,7 +133,7 @@ class ExamSessionControlTask implements DisposableBean {
|
||||||
.getOrThrow()
|
.getOrThrow()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(exam -> exam.startTime.minus(this.examTimePrefix).isBefore(now))
|
.filter(exam -> exam.startTime.minus(this.examTimePrefix).isBefore(now))
|
||||||
.map(exam -> this.examUpdateHandler.setRunning(exam, updateId))
|
.flatMap(exam -> Result.skipOnError(this.examUpdateHandler.setRunning(exam, updateId)))
|
||||||
.collect(Collectors.toMap(Exam::getId, Exam::getName));
|
.collect(Collectors.toMap(Exam::getId, Exam::getName));
|
||||||
|
|
||||||
if (!updated.isEmpty()) {
|
if (!updated.isEmpty()) {
|
||||||
|
@ -158,10 +158,8 @@ class ExamSessionControlTask implements DisposableBean {
|
||||||
.getOrThrow()
|
.getOrThrow()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(exam -> exam.endTime != null && exam.endTime.plus(this.examTimeSuffix).isBefore(now))
|
.filter(exam -> exam.endTime != null && exam.endTime.plus(this.examTimeSuffix).isBefore(now))
|
||||||
.map(exam -> this.examUpdateHandler.setFinished(exam, updateId))
|
.flatMap(exam -> Result.skipOnError(this.examUpdateHandler.setFinished(exam, updateId)))
|
||||||
.map(this.examProcotringRoomService::disposeRoomsForExam)
|
.flatMap(exam -> Result.skipOnError(this.examProcotringRoomService.disposeRoomsForExam(exam)))
|
||||||
.filter(result -> !result.hasError())
|
|
||||||
.map(Result::get)
|
|
||||||
.collect(Collectors.toMap(Exam::getId, Exam::getName));
|
.collect(Collectors.toMap(Exam::getId, Exam::getName));
|
||||||
|
|
||||||
if (!updated.isEmpty()) {
|
if (!updated.isEmpty()) {
|
||||||
|
|
|
@ -64,14 +64,15 @@ class ExamUpdateHandler {
|
||||||
final DateTime now = DateTime.now(DateTimeZone.UTC);
|
final DateTime now = DateTime.now(DateTimeZone.UTC);
|
||||||
if (exam.getStatus() == ExamStatus.UP_COMING
|
if (exam.getStatus() == ExamStatus.UP_COMING
|
||||||
&& exam.endTime.plus(this.examTimeSuffix).isBefore(now)) {
|
&& exam.endTime.plus(this.examTimeSuffix).isBefore(now)) {
|
||||||
return setRunning(exam, this.createUpdateId());
|
return setRunning(exam, this.createUpdateId())
|
||||||
|
.getOr(exam);
|
||||||
} else {
|
} else {
|
||||||
return exam;
|
return exam;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Exam setRunning(final Exam exam, final String updateId) {
|
Result<Exam> setRunning(final Exam exam, final String updateId) {
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.debug("Update exam as running: {}", exam);
|
log.debug("Update exam as running: {}", exam);
|
||||||
}
|
}
|
||||||
|
@ -85,11 +86,10 @@ class ExamUpdateHandler {
|
||||||
.flatMap(this.sebRestrictionService::applySEBClientRestriction)
|
.flatMap(this.sebRestrictionService::applySEBClientRestriction)
|
||||||
.flatMap(e -> this.examDAO.releaseLock(e.id, updateId))
|
.flatMap(e -> this.examDAO.releaseLock(e.id, updateId))
|
||||||
.onError(error -> this.examDAO.forceUnlock(exam.id)
|
.onError(error -> this.examDAO.forceUnlock(exam.id)
|
||||||
.onError(unlockError -> log.error("Failed to force unlock update look for exam: {}", exam.id)))
|
.onError(unlockError -> log.error("Failed to force unlock update look for exam: {}", exam.id)));
|
||||||
.getOrThrow();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Exam setFinished(final Exam exam, final String updateId) {
|
Result<Exam> setFinished(final Exam exam, final String updateId) {
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.debug("Update exam as finished: {}", exam);
|
log.debug("Update exam as finished: {}", exam);
|
||||||
}
|
}
|
||||||
|
@ -102,8 +102,7 @@ class ExamUpdateHandler {
|
||||||
updateId))
|
updateId))
|
||||||
.flatMap(this.sebRestrictionService::releaseSEBClientRestriction)
|
.flatMap(this.sebRestrictionService::releaseSEBClientRestriction)
|
||||||
.flatMap(e -> this.examDAO.releaseLock(e.id, updateId))
|
.flatMap(e -> this.examDAO.releaseLock(e.id, updateId))
|
||||||
.onError(error -> this.examDAO.forceUnlock(exam.id))
|
.onError(error -> this.examDAO.forceUnlock(exam.id));
|
||||||
.getOrThrow();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,7 +89,7 @@ public class LmsSetupController extends ActivatableEntityController<LmsSetup, Lm
|
||||||
@PathVariable final Long modelId) {
|
@PathVariable final Long modelId) {
|
||||||
|
|
||||||
this.authorization.check(
|
this.authorization.check(
|
||||||
PrivilegeType.MODIFY,
|
PrivilegeType.READ,
|
||||||
EntityType.LMS_SETUP,
|
EntityType.LMS_SETUP,
|
||||||
institutionId);
|
institutionId);
|
||||||
|
|
||||||
|
|
|
@ -665,10 +665,10 @@ sebserver.exam.proctoring.form.appkey=App Key
|
||||||
sebserver.exam.proctoring.form.appkey.tooltip=The application key of the proctoring service server
|
sebserver.exam.proctoring.form.appkey.tooltip=The application key of the proctoring service server
|
||||||
sebserver.exam.proctoring.form.secret=App Secret
|
sebserver.exam.proctoring.form.secret=App Secret
|
||||||
sebserver.exam.proctoring.form.secret.tooltip=The secret used to access the proctoring service
|
sebserver.exam.proctoring.form.secret.tooltip=The secret used to access the proctoring service
|
||||||
sebserver.exam.proctoring.form.sdkkey=SDK Key (MacOS/iOS)
|
sebserver.exam.proctoring.form.sdkkey=SDK Key (Zoom - MacOS/iOS)
|
||||||
sebserver.exam.proctoring.form.sdkkey.tooltip=The SDK key and secret are used for live proctoring with SEB clients for iOS and/or MacOS
|
sebserver.exam.proctoring.form.sdkkey.tooltip=The SDK key and secret are used for live proctoring with SEB clients for iOS and/or MacOS<br/>This is only relevant for proctoring with Zoom service.
|
||||||
sebserver.exam.proctoring.form.sdksecret=SDK Secret (MacOS/iOS)
|
sebserver.exam.proctoring.form.sdksecret=SDK Secret (Zoom - MacOS/iOS)
|
||||||
sebserver.exam.proctoring.form.sdksecret.tooltip=The SDK key and secret are used for live proctoring with SEB clients for iOS and/or MacOS
|
sebserver.exam.proctoring.form.sdksecret.tooltip=The SDK key and secret are used for live proctoring with SEB clients for iOS and/or MacOS<br/>This is only relevant for proctoring with Zoom service.
|
||||||
sebserver.exam.proctoring.form.features=Enabled Features
|
sebserver.exam.proctoring.form.features=Enabled Features
|
||||||
sebserver.exam.proctoring.form.features.TOWN_HALL=Town-Hall Room
|
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.ONE_TO_ONE=One to One Room
|
||||||
|
@ -677,6 +677,8 @@ sebserver.exam.proctoring.form.features.ENABLE_CHAT=Chat Feature
|
||||||
sebserver.exam.proctoring.form.features.WAITING_ROOM=Enable waiting room for collecting rooms
|
sebserver.exam.proctoring.form.features.WAITING_ROOM=Enable waiting room for collecting rooms
|
||||||
sebserver.exam.proctoring.form.features.SEND_REJOIN_COLLECTING_ROOM=Force rejoin for collecting rooms
|
sebserver.exam.proctoring.form.features.SEND_REJOIN_COLLECTING_ROOM=Force rejoin for collecting rooms
|
||||||
sebserver.exam.proctoring.form.features.RESET_BROADCAST_ON_LAVE=Reset broadcast on leave
|
sebserver.exam.proctoring.form.features.RESET_BROADCAST_ON_LAVE=Reset broadcast on leave
|
||||||
|
sebserver.exam.proctoring.form.useZoomAppClient=Use Zoom App-Client
|
||||||
|
sebserver.exam.proctoring.form.useZoomAppClient.tooltip=If this is set SEB Server opens a start link for the meeting instead of a new popup-window with the Zoom Web Client.<br/>A Zoom App Client must already be installed on the proctor's device or can be installed by following the instructions shown in the browser window.
|
||||||
|
|
||||||
sebserver.exam.proctoring.type.servertype.JITSI_MEET=Jitsi Meet Server
|
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
|
sebserver.exam.proctoring.type.servertype.JITSI_MEET.tooltip=Use a Jitsi Meet server for proctoring
|
||||||
|
|
|
@ -63,7 +63,7 @@ public class ExamProctoringRoomServiceTest extends AdministrationAPIIntegrationT
|
||||||
2L,
|
2L,
|
||||||
new ProctoringServiceSettings(
|
new ProctoringServiceSettings(
|
||||||
2L, true, ProctoringServerType.JITSI_MEET, "http://jitsi.ch", 1, null, false,
|
2L, true, ProctoringServerType.JITSI_MEET, "http://jitsi.ch", 1, null, false,
|
||||||
"app-key", "app.secret", "sdk-key", "sdk.secret"));
|
"app-key", "app.secret", "sdk-key", "sdk.secret", false));
|
||||||
|
|
||||||
assertTrue(this.examAdminService.isProctoringEnabled(2L).get());
|
assertTrue(this.examAdminService.isProctoringEnabled(2L).get());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue