Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.wise.portal.dao.teacherpresentation;

import org.wise.portal.dao.SimpleDao;
import org.wise.portal.domain.group.Group;
import org.wise.portal.domain.run.Run;
import org.wise.vle.domain.teacherpresentation.TeacherPresentationConfig;

public interface TeacherPresentationConfigDao<T extends TeacherPresentationConfig>
extends SimpleDao<T> {

/**
* @return the config for the given run, period, node and component, or null if none exists
*/
TeacherPresentationConfig getConfig(Run run, Group period, String nodeId, String componentId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.wise.portal.dao.teacherpresentation.impl;

import java.util.List;

import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;

import org.springframework.stereotype.Repository;
import org.wise.portal.dao.impl.AbstractHibernateDao;
import org.wise.portal.dao.teacherpresentation.TeacherPresentationConfigDao;
import org.wise.portal.domain.group.Group;
import org.wise.portal.domain.run.Run;
import org.wise.vle.domain.teacherpresentation.TeacherPresentationConfig;

@Repository("teacherPresentationConfigDao")
public class HibernateTeacherPresentationConfigDao
extends AbstractHibernateDao<TeacherPresentationConfig>
implements TeacherPresentationConfigDao<TeacherPresentationConfig> {

@Override
protected Class<? extends TeacherPresentationConfig> getDataObjectClass() {
return TeacherPresentationConfig.class;
}

@Override
public TeacherPresentationConfig getConfig(Run run, Group period, String nodeId,
String componentId) {
CriteriaBuilder cb = getCriteriaBuilder();
CriteriaQuery<TeacherPresentationConfig> cq = cb.createQuery(TeacherPresentationConfig.class);
Root<TeacherPresentationConfig> root = cq.from(TeacherPresentationConfig.class);
cq.select(root).where(cb.equal(root.get("run"), run), cb.equal(root.get("period"), period),
cb.equal(root.get("nodeId"), nodeId), cb.equal(root.get("componentId"), componentId));
List<TeacherPresentationConfig> results = entityManager.createQuery(cq).getResultList();
return results.isEmpty() ? null : results.get(0);
}
}
2 changes: 2 additions & 0 deletions src/main/java/org/wise/portal/dao/work/StudentWorkDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,6 @@ List<StudentWork> getWorkForComponentByPeriod(Run run, Group period, String node
List<StudentWork> getStudentWork(PeerGroup peerGroup, String nodeId, String componentId);

List<StudentWork> getStudentWork(Set<Workgroup> workgroups, String nodeId, String componentId);

List<StudentWork> getStudentWorkListByIds(List<Integer> ids);
}
Original file line number Diff line number Diff line change
Expand Up @@ -212,4 +212,17 @@ public List<StudentWork> getStudentWork(Run run, Group period, String nodeId,
TypedQuery<StudentWork> query = entityManager.createQuery(cq);
return (List<StudentWork>) query.getResultList();
}

@Override
public List<StudentWork> getStudentWorkListByIds(List<Integer> ids) {
if (ids == null || ids.isEmpty()) {
return new ArrayList<>();
}
CriteriaBuilder cb = getCriteriaBuilder();
CriteriaQuery<StudentWork> cq = cb.createQuery(StudentWork.class);
Root<StudentWork> studentWorkRoot = cq.from(StudentWork.class);
cq.select(studentWorkRoot).where(studentWorkRoot.get("id").in(ids));
TypedQuery<StudentWork> query = entityManager.createQuery(cq);
return query.getResultList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package org.wise.portal.presentation.web.controllers.teacher;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
import org.wise.portal.dao.ObjectNotFoundException;
import org.wise.portal.domain.group.Group;
import org.wise.portal.domain.run.Run;
import org.wise.portal.domain.user.User;
import org.wise.portal.domain.workgroup.Workgroup;
import org.wise.portal.service.run.RunService;
import org.wise.portal.service.teacherpresentation.DiscussionItem;
import org.wise.portal.service.teacherpresentation.IdeasItem;
import org.wise.portal.service.teacherpresentation.IdeasResponse;
import org.wise.portal.service.teacherpresentation.TeacherPresentationConfigService;
import org.wise.portal.service.user.UserService;
import org.wise.portal.service.workgroup.WorkgroupService;
import org.wise.vle.domain.teacherpresentation.TeacherPresentationConfig;

/**
* REST API for TeacherPresentation: the teacher's selected student work, student name display
* setting, prompt and reflection answers for a component in a period.
*/
@RestController
@RequestMapping("/api/teacher/presentation-config")
@PreAuthorize("hasRole('ROLE_TEACHER')")
public class TeacherPresentationConfigAPIController {

@Autowired
private TeacherPresentationConfigService teacherPresentationConfigService;

@Autowired
private RunService runService;

@Autowired
private UserService userService;

@Autowired
private WorkgroupService workgroupService;

public record Item(Integer studentWorkId, String ideaId, List<IdeasResponse> responses) {
public Item(Integer studentWorkId) {
this(studentWorkId, null, null);
}

public Item(String ideaId, List<IdeasResponse> responses) {
this(null, ideaId, responses);
}
}

public record ConfigRequest(Long runId, Long periodId, String nodeId, String componentId,
String componentType, String type, List<Item> items, String studentNamesDisplay,
Boolean showComponentPrompt, String prompt) {
}

public record ConfigResponse(Long id, Long runId, Long periodId, String nodeId,
String componentId, String componentType, String type, List<?> items, String studentNamesDisplay,
Boolean showComponentPrompt, String prompt, Long updatedByWorkgroupId, Long updatedAt) {
}

@GetMapping
ConfigResponse getConfig(Authentication auth, @RequestParam Long runId,
@RequestParam Long periodId, @RequestParam String nodeId,
@RequestParam String componentId) throws ObjectNotFoundException {
Run run = runService.retrieveById(runId);
User user = userService.retrieveUserByUsername(auth.getName());
if (!runService.isAllowedToViewStudentWork(run, user)) {
throw new AccessDeniedException("Not permitted");
}
Group period = getPeriod(run, periodId);
TeacherPresentationConfig config = teacherPresentationConfigService.getConfig(run, period,
nodeId, componentId);
if (config == null) {
return new ConfigResponse(null, runId, periodId, nodeId, componentId, null,
null, new ArrayList<>(),
TeacherPresentationConfig.NAMES_HIDE, true, null, null, null);
}
return toResponse(config);
}

@PutMapping
ConfigResponse saveConfig(Authentication auth, @RequestBody ConfigRequest request)
throws ObjectNotFoundException {
Run run = runService.retrieveById(request.runId());
User user = userService.retrieveUserByUsername(auth.getName());
if (!runService.isAllowedToGradeStudentWork(run, user)) {
throw new AccessDeniedException("Not permitted");
}
Group period = getPeriod(run, request.periodId());
String type = request.type() != null ? request.type()
: TeacherPresentationConfig.TYPE_DISCUSSION;
List<?> itemsToSave;
if (TeacherPresentationConfig.TYPE_IDEAS.equals(type)) {
List<IdeasItem> ideasItems = new ArrayList<>();
if (request.items() != null) {
for (Item item : request.items()) {
if (item != null) {
ideasItems.add(new IdeasItem(item.ideaId(), item.responses()));
}
}
}
itemsToSave = ideasItems;
} else {
List<DiscussionItem> discussionItems = new ArrayList<>();
if (request.items() != null) {
for (Item item : request.items()) {
if (item != null) {
discussionItems.add(new DiscussionItem(item.studentWorkId()));
}
}
}
itemsToSave = discussionItems;
}
try {
TeacherPresentationConfig config = teacherPresentationConfigService.saveConfig(run, period,
request.nodeId(), request.componentId(), request.componentType(), type, itemsToSave,
request.studentNamesDisplay(), request.showComponentPrompt(), request.prompt(),
getTeacherWorkgroup(run, user));
return toResponse(config);
} catch (IllegalArgumentException e) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage());
}
}

private Group getPeriod(Run run, Long periodId) {
for (Group period : run.getPeriods()) {
if (period.getId().equals(periodId)) {
return period;
}
}
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Period does not belong to the run");
}

private Workgroup getTeacherWorkgroup(Run run, User user) {
List<Workgroup> workgroups = workgroupService.getWorkgroupListByRunAndUser(run, user);
return workgroups.isEmpty() ? null : workgroups.get(0);
}

private ConfigResponse toResponse(TeacherPresentationConfig config) {
List<?> items = teacherPresentationConfigService.getItems(config);
Workgroup updatedBy = config.getUpdatedByWorkgroup();
return new ConfigResponse(config.getId(), config.getRun().getId(), config.getPeriod().getId(),
config.getNodeId(), config.getComponentId(), config.getComponentType(),
config.getType(), items,
config.getStudentNamesDisplay(), config.isShowComponentPrompt(), config.getPrompt(),
updatedBy == null ? null : updatedBy.getId(), config.getUpdatedAt().getTime());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package org.wise.portal.service.teacherpresentation;

public record DiscussionItem(Integer studentWorkId) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.wise.portal.service.teacherpresentation;

import java.util.List;

public record IdeasItem(String ideaId, List<IdeasResponse> responses) {
public IdeasItem {
if (responses == null) {
responses = List.of();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.wise.portal.service.teacherpresentation;

/**
* A student response selected for presentation under an idea category. Dialog Guidance work is
* additive (each save contains the whole chat), so a response is identified by workgroupId and
* responseTimestamp (the time of the chat message). responseTimestamp is null for single response
* work, which is identified by studentWorkId. text is a snapshot of the response.
*/
public record IdeasResponse(Integer studentWorkId, Long workgroupId, Long responseTimestamp,
String text) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.wise.portal.service.teacherpresentation;

import java.util.List;

import org.wise.portal.domain.group.Group;
import org.wise.portal.domain.run.Run;
import org.wise.portal.domain.workgroup.Workgroup;
import org.wise.vle.domain.teacherpresentation.TeacherPresentationConfig;

public interface TeacherPresentationConfigService {

int MAX_ITEMS = 500;
int MAX_PROMPT_LENGTH = 1000;

/**
* @return the config, or null if the teacher has not saved one yet
*/
TeacherPresentationConfig getConfig(Run run, Group period, String nodeId, String componentId);

List<Integer> getStudentWorkIds(TeacherPresentationConfig config);

List<?> getItems(TeacherPresentationConfig config);

/**
* Creates or updates the config for the run, period, node and component with type.
*
* @param type one of Discussion or Ideas.
* @param items list of DiscussionItem or IdeasItem.
* @param prompt the teacher's prompt. null or blank resets to the component default.
* @param studentNamesDisplay one of show, hide or anonymize. null keeps the current value (or
* hide for a new config).
* @throws IllegalArgumentException if the input is invalid
*/
TeacherPresentationConfig saveConfig(Run run, Group period, String nodeId, String componentId,
String componentType, String type, List<?> items, String studentNamesDisplay,
Boolean showComponentPrompt, String prompt, Workgroup teacherWorkgroup);

default TeacherPresentationConfig saveConfig(Run run, Group period, String nodeId,
String componentId, String componentType, String type, List<?> items,
String studentNamesDisplay, String prompt, Workgroup teacherWorkgroup) {
return saveConfig(run, period, nodeId, componentId, componentType, type, items,
studentNamesDisplay, null, prompt, teacherWorkgroup);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.wise.portal.service.teacherpresentation;

import java.util.List;

import org.wise.portal.domain.group.Group;
import org.wise.portal.domain.run.Run;

import com.fasterxml.jackson.databind.ObjectMapper;

/**
* Strategy interface defining serialization, deserialization, validation, and ID extraction
* for different teacher presentation item types (e.g. Discussion, Ideas).
*
* @param <T> the typed item domain class
*/
public interface TeacherPresentationItemStrategy<T> {

/**
* Returns the unique presentation config type string handled by this strategy
* (e.g. "Discussion", "Ideas").
*/
String getType();

/**
* Deserializes the JSON items string into a typed list of items.
*
* @param json the serialized JSON items string
* @param objectMapper the Jackson object mapper
* @return list of parsed items, or empty list on null/empty/invalid input
*/
List<T> parseItems(String json, ObjectMapper objectMapper);

/**
* Serializes a list of items into a JSON string.
*
* @param items the list of typed items to serialize
* @param objectMapper the Jackson object mapper
* @return serialized JSON string
*/
String serializeItems(List<T> items, ObjectMapper objectMapper);

/**
* Validates and normalizes raw input items (e.g. from service or controller).
*
* @param run the active run
* @param period the active period group
* @param rawItems the raw input items list
* @return list of normalized typed items
* @throws IllegalArgumentException if items fail validation rules
*/
List<T> validateAndNormalize(Run run, Group period, List<?> rawItems);

/**
* Extracts student work IDs referenced by the given items.
*
* @param items the list of typed items
* @return list of student work IDs
*/
List<Integer> extractStudentWorkIds(List<T> items);
}
Loading
Loading