SEBSERV-560 SEBSERV-563 implementation
This commit is contained in:
parent
f2f8a561a8
commit
303b3ac548
8 changed files with 167 additions and 68 deletions
|
@ -8,11 +8,21 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.gui.content.monitoring;
|
||||
|
||||
import static ch.ethz.seb.sebserver.gbl.model.user.UserFeatures.Feature.EXAM_SCREEN_PROCTORING;
|
||||
import static ch.ethz.seb.sebserver.gbl.model.user.UserFeatures.Feature.MONITORING_RUNNING_EXAM_SCREEN_PROCTORING;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ScreenProctoringSettings;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ScreenProctoringGroup;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetScreenProctoringGroups;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.proctoring.MonitoringProctoringService;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.eclipse.rap.rwt.RWT;
|
||||
import org.eclipse.rap.rwt.client.service.UrlLauncher;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
|
@ -93,6 +103,7 @@ public class FinishedExam implements TemplateComposer {
|
|||
private final RestService restService;
|
||||
private final I18nSupport i18nSupport;
|
||||
private final DownloadService downloadService;
|
||||
private final MonitoringProctoringService monitoringProctoringService;
|
||||
private final String exportFileName;
|
||||
private final int pageSize;
|
||||
|
||||
|
@ -100,12 +111,14 @@ public class FinishedExam implements TemplateComposer {
|
|||
final ServerPushService serverPushService,
|
||||
final PageService pageService,
|
||||
final DownloadService downloadService,
|
||||
final MonitoringProctoringService monitoringProctoringService,
|
||||
@Value("${sebserver.gui.seb.client.logs.export.filename:SEBClientLogs}") final String exportFileName,
|
||||
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.restService = pageService.getRestService();
|
||||
this.downloadService = downloadService;
|
||||
this.monitoringProctoringService = monitoringProctoringService;
|
||||
this.exportFileName = exportFileName;
|
||||
this.pageSize = pageSize;
|
||||
|
||||
|
@ -209,6 +222,33 @@ public class FinishedExam implements TemplateComposer {
|
|||
.withExec(this::exportCSV)
|
||||
.noEventPropagation()
|
||||
.publish();
|
||||
|
||||
// screen proctoring link
|
||||
final ScreenProctoringSettings screenProctoringSettings = new ScreenProctoringSettings(exam);
|
||||
final boolean screenProctoringEnabled =
|
||||
currentUser.isFeatureEnabled(MONITORING_RUNNING_EXAM_SCREEN_PROCTORING)
|
||||
&& BooleanUtils.toBoolean(screenProctoringSettings.enableScreenProctoring);
|
||||
if (screenProctoringEnabled) {
|
||||
this.pageService
|
||||
.getRestService()
|
||||
.getBuilder(GetScreenProctoringGroups.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, exam.getModelId())
|
||||
.call()
|
||||
.onError(error -> log.error("Failed to get screen proctoring group data:", error))
|
||||
.getOr(Collections.emptyList())
|
||||
.forEach(group -> {
|
||||
actionBuilder
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_VIEW_SCREEN_PROCTOR_GROUP)
|
||||
.withEntityKey(exam.getEntityKey())
|
||||
.withExec(_action -> monitoringProctoringService.openScreenProctoringTab(
|
||||
screenProctoringSettings,
|
||||
group,
|
||||
_action))
|
||||
.withNameAttributes(group.name, group.size)
|
||||
.noEventPropagation()
|
||||
.publish();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private PageAction exportCSV(final PageAction action) {
|
||||
|
|
|
@ -22,7 +22,6 @@ import java.util.function.Consumer;
|
|||
import java.util.function.Function;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.AllowedSEBVersion;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserFeatures;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.text.StringEscapeUtils;
|
||||
import org.eclipse.swt.SWT;
|
||||
|
@ -391,7 +390,7 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
.getBuilder(GetScreenProctoringGroups.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.call()
|
||||
.onError(error -> log.error("Failed to get collecting room data:", error))
|
||||
.onError(error -> log.error("\"Failed to get screen proctoring group data:", error))
|
||||
.getOr(Collections.emptyList())
|
||||
: Collections.emptyList();
|
||||
|
||||
|
@ -684,7 +683,7 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
|
||||
|
||||
if (!this.actionItemPerClientGroup.isEmpty()) {
|
||||
this.actionItemPerClientGroup.entrySet().stream().forEach(entry -> {
|
||||
this.actionItemPerClientGroup.entrySet().forEach(entry -> {
|
||||
final int numOfConnections = monitoringStatus.getNumOfConnections(entry.getKey());
|
||||
if (numOfConnections >= 0) {
|
||||
final TreeItem treeItem = entry.getValue();
|
||||
|
|
|
@ -215,13 +215,8 @@ public class MonitoringProctoringService {
|
|||
this.pageService.publishAction(
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_VIEW_SCREEN_PROCTOR_GROUP)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(_action -> openScreenProctoringTab(
|
||||
settings,
|
||||
group,
|
||||
_action))
|
||||
.withNameAttributes(
|
||||
group.name,
|
||||
group.size)
|
||||
.withExec(_action -> openScreenProctoringTab(settings, group, _action))
|
||||
.withNameAttributes(group.name, group.size)
|
||||
.noEventPropagation()
|
||||
.create(),
|
||||
_treeItem -> proctoringGUIService.registerScreeProctoringGroupAction(group, _treeItem));
|
||||
|
@ -308,7 +303,7 @@ public class MonitoringProctoringService {
|
|||
this.proctorRoomConnectionsPopup.show(pc, collectingRoom.subject);
|
||||
}
|
||||
|
||||
private PageAction openScreenProctoringTab(
|
||||
public PageAction openScreenProctoringTab(
|
||||
final ScreenProctoringSettings settings,
|
||||
final ScreenProctoringGroup group,
|
||||
final PageAction _action) {
|
||||
|
|
|
@ -54,9 +54,7 @@ public interface ScreenProctoringGroupDAO {
|
|||
*
|
||||
* @param examId the exam identifier
|
||||
* @param maxSize the maximum size of connection collected in one collecting group. Size of 0 means no limit.
|
||||
* @param newGroupFunction Function to create data for a new collecting group if needed.
|
||||
* @return Result refer to the collecting group record of place or to an error when happened
|
||||
* @throws If the Result contains a AllGroupsFullException, there must be created a new Group first */
|
||||
* @return Result refer to the collecting group record of place or to an error when happened*/
|
||||
Result<ScreenProctoringGroup> reservePlaceInCollectingGroup(Long examId, int maxSize);
|
||||
|
||||
Result<ScreenProctoringGroup> releasePlaceInCollectingGroup(Long examId, Long groupId);
|
||||
|
@ -80,4 +78,5 @@ public interface ScreenProctoringGroupDAO {
|
|||
* @return Result refer to a collection of entity keys for all delete group records or to an error when happened */
|
||||
Result<Collection<EntityKey>> deleteGroups(Long examId);
|
||||
|
||||
void updateGroupSize(String groupUUID, Integer activeCount, Integer totalCount);
|
||||
}
|
||||
|
|
|
@ -8,8 +8,7 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.webservice.servicelayer.dao.impl;
|
||||
|
||||
import static org.mybatis.dynamic.sql.SqlBuilder.isEqualTo;
|
||||
import static org.mybatis.dynamic.sql.SqlBuilder.isIn;
|
||||
import static org.mybatis.dynamic.sql.SqlBuilder.*;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
@ -142,7 +141,8 @@ public class ScreenProctoringGroupDAOImpl implements ScreenProctoringGroupDAO {
|
|||
.findFirst();
|
||||
|
||||
if (room.isPresent()) {
|
||||
return updateCollectingGroup(room.get());
|
||||
return room.get();
|
||||
//return updateCollectingGroup(room.get());
|
||||
} else {
|
||||
throw new AllGroupsFullException();
|
||||
}
|
||||
|
@ -234,6 +234,31 @@ public class ScreenProctoringGroupDAOImpl implements ScreenProctoringGroupDAO {
|
|||
return tryCatch.onError(TransactionHandler::rollback);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void updateGroupSize(
|
||||
final String groupUUID,
|
||||
final Integer activeCount,
|
||||
final Integer totalCount) {
|
||||
|
||||
try {
|
||||
|
||||
UpdateDSL.updateWithMapper(
|
||||
this.screenProctoringGroopRecordMapper::update,
|
||||
ScreenProctoringGroopRecordDynamicSqlSupport.screenProctoringGroopRecord)
|
||||
.set(ScreenProctoringGroopRecordDynamicSqlSupport.size)
|
||||
.equalTo(activeCount)
|
||||
.where(ScreenProctoringGroopRecordDynamicSqlSupport.uuid, isEqualTo(groupUUID))
|
||||
.and(ScreenProctoringGroopRecordDynamicSqlSupport.size, isNotEqualTo(activeCount))
|
||||
.build()
|
||||
.execute();
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.warn("Failed to update SPS group size: {}", e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private ScreenProctoringGroup toDomainModel(final ScreenProctoringGroopRecord record) {
|
||||
return new ScreenProctoringGroup(
|
||||
record.getId(),
|
||||
|
@ -244,22 +269,22 @@ public class ScreenProctoringGroupDAOImpl implements ScreenProctoringGroupDAO {
|
|||
record.getData());
|
||||
}
|
||||
|
||||
private ScreenProctoringGroopRecord updateCollectingGroup(
|
||||
final ScreenProctoringGroopRecord screenProctoringGroopRecord) {
|
||||
|
||||
final Long id = screenProctoringGroopRecord.getId();
|
||||
|
||||
UpdateDSL.updateWithMapper(
|
||||
this.screenProctoringGroopRecordMapper::update,
|
||||
ScreenProctoringGroopRecordDynamicSqlSupport.screenProctoringGroopRecord)
|
||||
.set(ScreenProctoringGroopRecordDynamicSqlSupport.size)
|
||||
.equalTo(screenProctoringGroopRecord.getSize() + 1)
|
||||
.where(ScreenProctoringGroopRecordDynamicSqlSupport.id, isEqualTo(id))
|
||||
.build()
|
||||
.execute();
|
||||
|
||||
return this.screenProctoringGroopRecordMapper.selectByPrimaryKey(id);
|
||||
}
|
||||
// private ScreenProctoringGroopRecord updateCollectingGroup(
|
||||
// final ScreenProctoringGroopRecord screenProctoringGroupRecord) {
|
||||
//
|
||||
// final Long id = screenProctoringGroupRecord.getId();
|
||||
//
|
||||
// UpdateDSL.updateWithMapper(
|
||||
// this.screenProctoringGroopRecordMapper::update,
|
||||
// ScreenProctoringGroopRecordDynamicSqlSupport.screenProctoringGroopRecord)
|
||||
// .set(ScreenProctoringGroopRecordDynamicSqlSupport.size)
|
||||
// .equalTo(screenProctoringGroupRecord.getSize() + 1)
|
||||
// .where(ScreenProctoringGroopRecordDynamicSqlSupport.id, isEqualTo(id))
|
||||
// .build()
|
||||
// .execute();
|
||||
//
|
||||
// return this.screenProctoringGroopRecordMapper.selectByPrimaryKey(id);
|
||||
// }
|
||||
|
||||
public static final class AllGroupsFullException extends RuntimeException {
|
||||
private static final long serialVersionUID = 3283129187819160485L;
|
||||
|
|
|
@ -33,6 +33,7 @@ public interface ScreenProctoringService extends SessionUpdateTask {
|
|||
@Override
|
||||
default void processSessionUpdateTask() {
|
||||
updateClientConnections();
|
||||
updateActiveGroups();
|
||||
}
|
||||
|
||||
boolean isScreenProctoringEnabled(Long examId);
|
||||
|
@ -98,6 +99,10 @@ public interface ScreenProctoringService extends SessionUpdateTask {
|
|||
* SPS connection instruction to SEB client to connect and start sending screenshots. */
|
||||
void updateClientConnections();
|
||||
|
||||
/** This goes through all running exams with screen proctoring enabled and updates the group attributes
|
||||
* (mainly the number of active clients in the group) by call ing SPS API and store newest data. */
|
||||
void updateActiveGroups();
|
||||
|
||||
@Async(AsyncServiceSpringConfig.EXECUTOR_BEAN_NAME)
|
||||
void synchronizeSPSUser(final String userUUID);
|
||||
|
||||
|
|
|
@ -82,6 +82,8 @@ class ScreenProctoringAPIBinding {
|
|||
String TOKEN_ENDPOINT = "/oauth/token";
|
||||
String TEST_ENDPOINT = "/admin-api/v1/proctoring/group";
|
||||
|
||||
String GROUP_COUNT_ENDPOINT = "/admin-api/v1/proctoring/active_counts";
|
||||
|
||||
String USER_ACCOUNT_ENDPOINT = "/admin-api/v1/useraccount/";
|
||||
String USERSYNC_SEBSERVER_ENDPOINT = USER_ACCOUNT_ENDPOINT + "usersync/sebserver";
|
||||
String ENTITY_PRIVILEGES_ENDPOINT = USER_ACCOUNT_ENDPOINT + "entityprivilege";
|
||||
|
@ -151,7 +153,7 @@ class ScreenProctoringAPIBinding {
|
|||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
static final class ExamUpdate {
|
||||
final class ExamUpdate {
|
||||
@JsonProperty(EXAM.ATTR_NAME)
|
||||
final String name;
|
||||
@JsonProperty(EXAM.ATTR_DESCRIPTION)
|
||||
|
@ -187,6 +189,27 @@ class ScreenProctoringAPIBinding {
|
|||
}
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
static final class GroupSessionCount {
|
||||
@JsonProperty("uuid")
|
||||
public final String groupUUID;
|
||||
@JsonProperty("activeCount")
|
||||
public final Integer activeCount;
|
||||
@JsonProperty("totalCount")
|
||||
public final Integer totalCount;
|
||||
|
||||
@JsonCreator
|
||||
public GroupSessionCount(
|
||||
@JsonProperty("uuid") final String groupUUID,
|
||||
@JsonProperty("activeCount") final Integer activeCount,
|
||||
@JsonProperty("totalCount") final Integer totalCount) {
|
||||
|
||||
this.groupUUID = groupUUID;
|
||||
this.activeCount = activeCount;
|
||||
this.totalCount = totalCount;
|
||||
}
|
||||
}
|
||||
|
||||
private final UserDAO userDAO;
|
||||
private final Cryptor cryptor;
|
||||
private final AsyncService asyncService;
|
||||
|
@ -749,6 +772,35 @@ class ScreenProctoringAPIBinding {
|
|||
return;
|
||||
}
|
||||
|
||||
public Collection<GroupSessionCount> getActiveGroupSessionCounts() {
|
||||
try {
|
||||
|
||||
final ScreenProctoringServiceOAuthTemplate apiTemplate = this.getAPITemplate(null);
|
||||
|
||||
final String uri = UriComponentsBuilder
|
||||
.fromUriString(apiTemplate.spsAPIAccessData.getSpsServiceURL())
|
||||
.path(SPS_API.GROUP_COUNT_ENDPOINT)
|
||||
.build()
|
||||
.toUriString();
|
||||
|
||||
|
||||
final ResponseEntity<String> exchange = apiTemplate.exchange(uri, HttpMethod.POST);
|
||||
if (exchange.getStatusCode() != HttpStatus.OK) {
|
||||
log.error("Failed to request active group session counts: {}", exchange);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return this.jsonMapper.readValue(
|
||||
exchange.getBody(),
|
||||
new TypeReference<Collection<GroupSessionCount>>() {
|
||||
});
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to get active group session counts: {}", e.getMessage());
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private void synchronizeUserAccount(
|
||||
final String userUUID,
|
||||
final ScreenProctoringServiceOAuthTemplate apiTemplate) {
|
||||
|
@ -1187,39 +1239,6 @@ class ScreenProctoringAPIBinding {
|
|||
|
||||
return apiTemplateExam;
|
||||
}
|
||||
|
||||
// if (this.apiTemplate == null || !this.apiTemplate.isValid(examId)) {
|
||||
// if (examId != null) {
|
||||
//
|
||||
// if (log.isDebugEnabled()) {
|
||||
// log.debug("Create new ScreenProctoringServiceOAuthTemplate for exam: {}", examId);
|
||||
// }
|
||||
//
|
||||
// final ScreenProctoringSettings settings = this.proctoringSettingsDAO
|
||||
// .getScreenProctoringSettings(new EntityKey(examId, EntityType.EXAM))
|
||||
// .getOrThrow();
|
||||
// this.testConnection(settings).getOrThrow();
|
||||
// this.apiTemplate = new ScreenProctoringServiceOAuthTemplate(this, settings);
|
||||
//
|
||||
// } else if (this.webserviceInfo.getScreenProctoringServiceBundle().bundled) {
|
||||
//
|
||||
// if (log.isDebugEnabled()) {
|
||||
// log.debug("Create new ScreenProctoringServiceOAuthTemplate for exam: {}", examId);
|
||||
// }
|
||||
//
|
||||
// final WebserviceInfo.ScreenProctoringServiceBundle bundle = this.webserviceInfo
|
||||
// .getScreenProctoringServiceBundle();
|
||||
//
|
||||
// this.testConnection(bundle).getOrThrow();
|
||||
// this.apiTemplate = new ScreenProctoringServiceOAuthTemplate(this, bundle);
|
||||
//
|
||||
//
|
||||
// } else {
|
||||
// throw new IllegalStateException("No SPS API access information found!");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return this.apiTemplate;
|
||||
}
|
||||
|
||||
private static List<String> getSupporterIds(final Exam exam) {
|
||||
|
|
|
@ -18,7 +18,6 @@ import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
|||
import ch.ethz.seb.sebserver.webservice.WebserviceInfo;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.impl.LmsSetupChangeEvent;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.session.*;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -244,6 +243,24 @@ public class ScreenProctoringServiceImpl implements ScreenProctoringService {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateActiveGroups() {
|
||||
try {
|
||||
|
||||
screenProctoringAPIBinding
|
||||
.getActiveGroupSessionCounts()
|
||||
.forEach(groupCount -> {
|
||||
screenProctoringGroupDAO.updateGroupSize(
|
||||
groupCount.groupUUID,
|
||||
groupCount.activeCount,
|
||||
groupCount.totalCount);
|
||||
});
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.warn("Failed to update actual group session counts.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyExamSaved(final Exam exam) {
|
||||
if (!this.isScreenProctoringEnabled(exam.id)) {
|
||||
|
|
Loading…
Reference in a new issue