Skip to content

Commit

Permalink
SEBSERV-560 SEBSERV-563 implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
anhefti committed Jul 16, 2024
1 parent f2f8a56 commit 303b3ac
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -93,19 +103,22 @@ 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;

public FinishedExam(
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;

Expand Down Expand Up @@ -209,6 +222,33 @@ public void compose(final PageContext pageContext) {
.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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
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;
Expand Down Expand Up @@ -391,7 +390,7 @@ private FullPageMonitoringGUIUpdate createProctoringActions(
.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();

Expand Down Expand Up @@ -684,7 +683,7 @@ public void update(final MonitoringFilter monitoringStatus) {


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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,13 +215,8 @@ private void updateScreenProctoringAction(
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));
Expand Down Expand Up @@ -308,7 +303,7 @@ private void showCollectingRoomPopup(
this.proctorRoomConnectionsPopup.show(pc, collectingRoom.subject);
}

private PageAction openScreenProctoringTab(
public PageAction openScreenProctoringTab(
final ScreenProctoringSettings settings,
final ScreenProctoringGroup group,
final PageAction _action) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -142,7 +141,8 @@ public Result<ScreenProctoringGroup> reservePlaceInCollectingGroup(final Long ex
.findFirst();

if (room.isPresent()) {
return updateCollectingGroup(room.get());
return room.get();
//return updateCollectingGroup(room.get());
} else {
throw new AllGroupsFullException();
}
Expand Down Expand Up @@ -234,6 +234,31 @@ public Result<Collection<EntityKey>> deleteGroups(final Long examId) {
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(),
Expand All @@ -244,22 +269,22 @@ private ScreenProctoringGroup toDomainModel(final ScreenProctoringGroopRecord re
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ default int sessionUpdateTaskProcessingOrder() {
@Override
default void processSessionUpdateTask() {
updateClientConnections();
updateActiveGroups();
}

boolean isScreenProctoringEnabled(Long examId);
Expand Down Expand Up @@ -98,6 +99,10 @@ default void processSessionUpdateTask() {
* 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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ enum SPSUserRole {
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";
Expand Down Expand Up @@ -151,7 +153,7 @@ interface SESSION {
}

@JsonIgnoreProperties(ignoreUnknown = true)
static final class ExamUpdate {
final class ExamUpdate {
@JsonProperty(EXAM.ATTR_NAME)
final String name;
@JsonProperty(EXAM.ATTR_DESCRIPTION)
Expand Down Expand Up @@ -187,6 +189,27 @@ public ExamUpdate(
}
}

@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;
Expand Down Expand Up @@ -749,6 +772,35 @@ void deleteExamOnScreenProctoring(final Exam exam) {
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) {
Expand Down Expand Up @@ -1187,39 +1239,6 @@ private ScreenProctoringServiceOAuthTemplate getAPITemplate(final Long examId) {

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) {
Expand Down
Loading

0 comments on commit 303b3ac

Please sign in to comment.