fix quit instruction, added default indicator, connection status filter

This commit is contained in:
anhefti 2020-02-10 15:04:40 +01:00
parent 92663c18e8
commit b94e445257
8 changed files with 305 additions and 80 deletions

View file

@ -41,6 +41,7 @@ import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.model.Domain;
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
@ -85,10 +86,13 @@ public final class ClientConnectionTable {
private final Table table;
private final ColorData colorData;
private final EnumSet<ConnectionStatus> statusFilter;
private String statusFilterParam = "";
private boolean statusFilterChanged = false;
private int tableWidth;
private boolean needsSort = false;
private LinkedHashMap<Long, UpdatableTableItem> tableMapping;
private final Set<Long> toDelete = new HashSet<>();
private final MultiValueMap<String, Long> sessionIds;
private final Color darkFontColor;
@ -180,39 +184,6 @@ public final class ClientConnectionTable {
saveStatusFilter();
}
private void saveStatusFilter() {
try {
this.resourceService
.getCurrentUser()
.putAttribute(
USER_SESSION_STATUS_FILTER_ATTRIBUTE,
StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR));
} catch (final Exception e) {
log.warn("Failed to save status filter to user session");
}
}
private void loadStatusFilter() {
try {
final String attribute = this.resourceService
.getCurrentUser()
.getAttribute(USER_SESSION_STATUS_FILTER_ATTRIBUTE);
if (attribute != null) {
this.statusFilter.clear();
Arrays.asList(StringUtils.split(attribute, Constants.LIST_SEPARATOR))
.forEach(name -> this.statusFilter.add(ConnectionStatus.valueOf(name)));
} else {
this.statusFilter.clear();
this.statusFilter.add(ConnectionStatus.DISABLED);
}
} catch (final Exception e) {
log.warn("Failed to load status filter to user session");
this.statusFilter.clear();
this.statusFilter.add(ConnectionStatus.DISABLED);
}
}
public void withDefaultAction(final PageAction pageAction, final PageService pageService) {
this.table.addListener(SWT.MouseDoubleClick, event -> {
final Tuple<String> selection = getSingleSelection();
@ -300,7 +271,12 @@ public final class ClientConnectionTable {
}
public void updateValues() {
if (this.statusFilterChanged) {
this.toDelete.clear();
this.toDelete.addAll(this.tableMapping.keySet());
}
this.restCallBuilder
.withHeader(API.EXAM_MONITORING_STATE_FILTER, this.statusFilterParam)
.call()
.get(error -> {
log.error("Error poll connection data: ", error);
@ -312,7 +288,21 @@ public final class ClientConnectionTable {
data.getConnectionId(),
connectionId -> new UpdatableTableItem(connectionId));
tableItem.push(data);
if (this.statusFilterChanged) {
this.toDelete.remove(data.getConnectionId());
}
});
if (this.statusFilterChanged && !this.toDelete.isEmpty()) {
this.toDelete.stream().forEach(id -> {
final UpdatableTableItem item = this.tableMapping.remove(id);
final List<Long> list = this.sessionIds.get(item.connectionData.clientConnection.userSessionId);
if (list != null) {
list.remove(id);
}
});
this.statusFilterChanged = false;
}
}
public void updateGUI() {
@ -370,6 +360,45 @@ public final class ClientConnectionTable {
(e1, e2) -> e1, LinkedHashMap::new));
}
private void saveStatusFilter() {
try {
this.resourceService
.getCurrentUser()
.putAttribute(
USER_SESSION_STATUS_FILTER_ATTRIBUTE,
StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR));
} catch (final Exception e) {
log.warn("Failed to save status filter to user session");
} finally {
this.statusFilterParam = StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR);
this.statusFilterChanged = true;
}
}
private void loadStatusFilter() {
try {
final String attribute = this.resourceService
.getCurrentUser()
.getAttribute(USER_SESSION_STATUS_FILTER_ATTRIBUTE);
if (attribute != null) {
this.statusFilter.clear();
Arrays.asList(StringUtils.split(attribute, Constants.LIST_SEPARATOR))
.forEach(name -> this.statusFilter.add(ConnectionStatus.valueOf(name)));
} else {
this.statusFilter.clear();
this.statusFilter.add(ConnectionStatus.DISABLED);
}
} catch (final Exception e) {
log.warn("Failed to load status filter to user session");
this.statusFilter.clear();
this.statusFilter.add(ConnectionStatus.DISABLED);
} finally {
this.statusFilterParam = StringUtils.join(this.statusFilter, Constants.LIST_SEPARATOR);
this.statusFilterChanged = true;
}
}
private final class UpdatableTableItem implements Comparable<UpdatableTableItem> {
final Long connectionId;
@ -567,8 +596,12 @@ public final class ClientConnectionTable {
this.connectionData = connectionData;
if (!this.duplicateChecked && StringUtils.isNotBlank(connectionData.clientConnection.userSessionId)) {
ClientConnectionTable.this.sessionIds.add(connectionData.clientConnection.userSessionId,
if (!this.duplicateChecked &&
this.connectionData.clientConnection.status != ConnectionStatus.DISABLED &&
StringUtils.isNotBlank(connectionData.clientConnection.userSessionId)) {
ClientConnectionTable.this.sessionIds.add(
connectionData.clientConnection.userSessionId,
this.connectionId);
this.duplicateChecked = true;
}

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.webservice.servicelayer.exam;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.util.Result;
public interface ExamAdminService {
/** Adds a default indicator that is defined by configuration to a given exam.
*
* @param exam The Exam to add the default indicator
* @return the Exam with added default indicator */
Result<Exam> addDefaultIndicator(final Exam exam);
/** Applies all additional SEB restriction attributes that are defined by the
* type of the LMS of a given Exam to this given Exam.
*
* @param exam the Exam to apply all additional SEB restriction attributes
* @return the Exam */
Result<Exam> applyAdditionalSEBRestrictions(final Exam exam);
}

View file

@ -0,0 +1,130 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.webservice.servicelayer.exam;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.EntityType;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
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.Indicator.IndicatorType;
import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSebRestriction;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.IndicatorDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SebRestrictionService;
@Lazy
@Service
@WebServiceProfile
public class ExamAdminServiceImpl implements ExamAdminService {
private final ExamDAO examDAO;
private final IndicatorDAO indicatorDAO;
private final AdditionalAttributesDAO additionalAttributesDAO;
private final LmsAPIService lmsAPIService;
private final JSONMapper jsonMapper;
private final String defaultIndicatorName;
private final String defaultIndicatorType;
private final String defaultIndicatorColor;
private final String defaultIndicatorThresholds;
protected ExamAdminServiceImpl(
final ExamDAO examDAO,
final IndicatorDAO indicatorDAO,
final AdditionalAttributesDAO additionalAttributesDAO,
final LmsAPIService lmsAPIService,
final JSONMapper jsonMapper,
@Value("${sebserver.webservice.api.exam.indicator.name:Ping}") final String defaultIndicatorName,
@Value("${sebserver.webservice.api.exam.indicator.type:LAST_PING}") final String defaultIndicatorType,
@Value("${sebserver.webservice.api.exam.indicator.color:b4b4b4}") final String defaultIndicatorColor,
@Value("${sebserver.webservice.api.exam.indicator.thresholds:[{\"value\":2000.0,\"color\":\"22b14c\"},{\"value\":5000.0,\"color\":\"ff7e00\"},{\"value\":10000.0,\"color\":\"ed1c24\"}]}") final String defaultIndicatorThresholds) {
this.examDAO = examDAO;
this.indicatorDAO = indicatorDAO;
this.additionalAttributesDAO = additionalAttributesDAO;
this.lmsAPIService = lmsAPIService;
this.jsonMapper = jsonMapper;
this.defaultIndicatorName = defaultIndicatorName;
this.defaultIndicatorType = defaultIndicatorType;
this.defaultIndicatorColor = defaultIndicatorColor;
this.defaultIndicatorThresholds = defaultIndicatorThresholds;
}
@Override
public Result<Exam> addDefaultIndicator(final Exam exam) {
return Result.tryCatch(() -> {
final Collection<Indicator.Threshold> thresholds = this.jsonMapper.readValue(
this.defaultIndicatorThresholds,
new TypeReference<Collection<Indicator.Threshold>>() {
});
final Indicator indicator = new Indicator(
null,
exam.id,
this.defaultIndicatorName,
IndicatorType.valueOf(this.defaultIndicatorType),
this.defaultIndicatorColor,
thresholds);
this.indicatorDAO.createNew(indicator)
.getOrThrow();
return this.examDAO
.byPK(exam.id)
.getOrThrow();
});
}
@Override
public Result<Exam> applyAdditionalSEBRestrictions(final Exam exam) {
return Result.tryCatch(() -> {
final LmsSetup lmsSetup = this.lmsAPIService.getLmsSetup(exam.lmsSetupId)
.getOrThrow();
if (lmsSetup.lmsType == LmsType.OPEN_EDX) {
final List<String> permissions = Arrays.asList(
OpenEdxSebRestriction.PermissionComponent.ALWAYS_ALLOW_STUFF.key,
OpenEdxSebRestriction.PermissionComponent.CHECK_CONFIG_KEY.key);
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM,
exam.id,
SebRestrictionService.SEB_RESTRICTION_ADDITIONAL_PROPERTY_NAME_PREFIX +
OpenEdxSebRestriction.ATTR_PERMISSION_COMPONENTS,
StringUtils.join(permissions, Constants.LIST_SEPARATOR_CHAR))
.getOrThrow();
}
return this.examDAO
.byPK(exam.id)
.getOrThrow();
});
}
}

View file

@ -28,6 +28,7 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
@ -180,7 +181,7 @@ final class MoodleRestTemplateFactory {
private CharSequence accessToken = null;
private final Map<String, String> tokenReqURIVars;
private final HttpEntity<?> tokenReqEntity = new HttpEntity<>(null);
private final HttpEntity<?> tokenReqEntity = new HttpEntity<>(new LinkedMultiValueMap<>());
protected MoodleAPIRestTemplate(
@ -272,7 +273,7 @@ final class MoodleRestTemplateFactory {
functionReqEntity = new HttpEntity<>(body, headers);
} else {
functionReqEntity = new HttpEntity<>(null);
functionReqEntity = new HttpEntity<>(new LinkedMultiValueMap<>());
}
final ResponseEntity<String> response = super.exchange(

View file

@ -15,6 +15,7 @@ import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
@ -133,20 +134,19 @@ public class SebInstructionServiceImpl implements SebInstructionService {
.append(Constants.COLON)
.append(Constants.DOUBLE_QUOTE)
.append(clientInstruction.getType())
.append(Constants.DOUBLE_QUOTE)
.append(Constants.COMMA)
.append(Constants.DOUBLE_QUOTE)
.append(JSON_ATTR)
.append(Constants.DOUBLE_QUOTE)
.append(Constants.COLON);
if (attributes == null || attributes.isEmpty()) {
sBuilder.append(Constants.NULL);
.append(Constants.DOUBLE_QUOTE);
} else {
sBuilder.append(Constants.CURLY_BRACE_OPEN)
if (StringUtils.isNotBlank(attributes)) {
sBuilder.append(Constants.COMMA)
.append(Constants.DOUBLE_QUOTE)
.append(JSON_ATTR)
.append(Constants.DOUBLE_QUOTE)
.append(Constants.COLON)
.append(Constants.CURLY_BRACE_OPEN)
.append(attributes)
.append(Constants.CURLY_BRACE_CLOSE);
}
return sBuilder
.append(Constants.CURLY_BRACE_CLOSE)
.toString();

View file

@ -10,7 +10,6 @@ package ch.ethz.seb.sebserver.webservice.weblayer.api;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@ -37,7 +36,6 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.api.API;
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
@ -49,12 +47,10 @@ import ch.ethz.seb.sebserver.gbl.model.Domain.EXAM;
import ch.ethz.seb.sebserver.gbl.model.Page;
import ch.ethz.seb.sebserver.gbl.model.PageSortOrder;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.OpenEdxSebRestriction;
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
import ch.ethz.seb.sebserver.gbl.model.exam.SebRestriction;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.Features;
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
import ch.ethz.seb.sebserver.gbl.util.Result;
@ -64,11 +60,11 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.Authorization
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.impl.SEBServerUser;
import ch.ethz.seb.sebserver.webservice.servicelayer.bulkaction.BulkActionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.AdditionalAttributesDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.ExamDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserDAO;
import ch.ethz.seb.sebserver.webservice.servicelayer.exam.ExamAdminService;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.LmsAPIService;
import ch.ethz.seb.sebserver.webservice.servicelayer.lms.SebRestrictionService;
import ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.ExamConfigService;
@ -84,7 +80,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
private final ExamDAO examDAO;
private final UserDAO userDAO;
private final AdditionalAttributesDAO additionalAttributesDAO;
private final ExamAdminService examAdminService;
private final LmsAPIService lmsAPIService;
private final ExamConfigService sebExamConfigService;
private final ExamSessionService examSessionService;
@ -99,10 +95,10 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
final BeanValidationService beanValidationService,
final LmsAPIService lmsAPIService,
final UserDAO userDAO,
final ExamAdminService examAdminService,
final ExamConfigService sebExamConfigService,
final ExamSessionService examSessionService,
final SebRestrictionService sebRestrictionService,
final AdditionalAttributesDAO additionalAttributesDAO) {
final SebRestrictionService sebRestrictionService) {
super(authorization,
bulkActionService,
@ -113,7 +109,7 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
this.examDAO = examDAO;
this.userDAO = userDAO;
this.additionalAttributesDAO = additionalAttributesDAO;
this.examAdminService = examAdminService;
this.lmsAPIService = lmsAPIService;
this.sebExamConfigService = sebExamConfigService;
this.examSessionService = examSessionService;
@ -337,29 +333,9 @@ public class ExamAdministrationController extends EntityController<Exam, Exam> {
@Override
protected Result<Exam> notifyCreated(final Exam entity) {
return Result.tryCatch(() -> {
final LmsSetup lmsSetup = this.lmsAPIService.getLmsSetup(entity.lmsSetupId)
.getOrThrow();
// if we have an Open edX LMS involved, add additional initial SEB restriction attributes
if (lmsSetup.lmsType == LmsType.OPEN_EDX) {
final List<String> permissions = Arrays.asList(
OpenEdxSebRestriction.PermissionComponent.ALWAYS_ALLOW_STUFF.key,
OpenEdxSebRestriction.PermissionComponent.CHECK_CONFIG_KEY.key);
this.additionalAttributesDAO.saveAdditionalAttribute(
EntityType.EXAM,
entity.id,
SebRestrictionService.SEB_RESTRICTION_ADDITIONAL_PROPERTY_NAME_PREFIX +
OpenEdxSebRestriction.ATTR_PERMISSION_COMPONENTS,
StringUtils.join(permissions, Constants.LIST_SEPARATOR_CHAR))
.getOrThrow();
}
return entity;
});
return this.examAdminService
.addDefaultIndicator(entity)
.flatMap(this.examAdminService::applyAdditionalSEBRestrictions);
}
@Override

View file

@ -187,7 +187,7 @@ public class ExamMonitoringController {
examId,
filterStates.isEmpty()
? Objects::nonNull
: conn -> conn != null && filterStates.contains(conn.clientConnection.status))
: conn -> conn != null && !filterStates.contains(conn.clientConnection.status))
.getOrThrow();
}

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package ch.ethz.seb.sebserver.gbl.model.exam;
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import org.junit.Test;
import com.fasterxml.jackson.core.type.TypeReference;
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.Threshold;
public class IndicatorTest {
@Test
public void testDefault() throws IOException {
final Indicator indicator = new Indicator(
null,
1L,
"Ping",
IndicatorType.LAST_PING,
"b4b4b4",
Arrays.asList(
new Indicator.Threshold(2000d, "22b14c"),
new Indicator.Threshold(5000d, "ff7e00"),
new Indicator.Threshold(10000d, "ed1c24")));
final JSONMapper mapper = new JSONMapper();
final String jsonString = mapper.writeValueAsString(indicator);
assertEquals(
"{\"examId\":1,\"name\":\"Ping\",\"type\":\"LAST_PING\",\"color\":\"b4b4b4\",\"thresholds\":[{\"value\":2000.0,\"color\":\"22b14c\"},{\"value\":5000.0,\"color\":\"ff7e00\"}",
jsonString);
final String threholds =
"[{\"value\":2000.0,\"color\":\"22b14c\"},{\"value\":5000.0,\"color\":\"ff7e00\"},{\"value\":10000.0,\"color\":\"ed1c24\"}]";
final Collection<Threshold> values = mapper.readValue(threholds, new TypeReference<Collection<Threshold>>() {
});
assertEquals(
"[Threshold [value=2000.0, color=22b14c], Threshold [value=5000.0, color=ff7e00], Threshold [value=10000.0, color=ed1c24]]",
values.toString());
}
}