Allows admins to manage grading report templates #14

Merged
niat8586 merged 41 commits from 3482-new-grading-criteria into develop 2024-10-30 10:05:23 +01:00
13 changed files with 283 additions and 6 deletions
Showing only changes of commit 1672171f46 - Show all commits

View File

@ -3,6 +3,7 @@ package se.su.dsv.scipro.grading;
import com.google.inject.Key;
import com.google.inject.PrivateModule;
import com.google.inject.name.Names;
import se.su.dsv.scipro.report.GradingReportServiceImpl;
public class GradingModule extends PrivateModule {
@Override
@ -29,5 +30,8 @@ public class GradingModule extends PrivateModule {
bind(NationalSubjectCategoryRepository.class).to(NationalSubjectCategoryRepositoryImpl.class);
bind(NationalSubjectCategoryService.class).to(NationalSubjectCategoryServiceImpl.class);
expose(NationalSubjectCategoryService.class);
bind(GradingReportTemplateService.class).to(GradingReportServiceImpl.class);
expose(GradingReportTemplateService.class);
}
}

View File

@ -0,0 +1,12 @@
package se.su.dsv.scipro.grading;
import se.su.dsv.scipro.report.GradingReportTemplate;
import se.su.dsv.scipro.system.ProjectType;
import java.util.List;
public interface GradingReportTemplateService {
List<ProjectType> getProjectTypes();
GradingReportTemplate getCurrentTemplate(ProjectType projectType);
}

View File

@ -4,18 +4,23 @@ import com.google.common.eventbus.EventBus;
import com.google.inject.persist.Transactional;
import se.su.dsv.scipro.finalseminar.FinalSeminarOpposition;
import se.su.dsv.scipro.grading.GradingBasis;
import se.su.dsv.scipro.grading.GradingReportTemplateService;
import se.su.dsv.scipro.grading.ThesisSubmissionHistoryService;
import se.su.dsv.scipro.project.Project;
import se.su.dsv.scipro.system.Language;
import se.su.dsv.scipro.system.ProjectModule;
import se.su.dsv.scipro.system.ProjectType;
import se.su.dsv.scipro.system.ProjectTypeService;
import se.su.dsv.scipro.system.User;
import se.su.dsv.scipro.util.Either;
import jakarta.inject.Inject;
import java.time.Clock;
import java.time.Instant;
import java.time.LocalDate;
import java.util.*;
public class GradingReportServiceImpl implements GradingReportService {
public class GradingReportServiceImpl implements GradingReportTemplateService, GradingReportService {
public static final String OPPOSITION_SWEDISH = "Ö1 Oppositionsrapport";
public static final String OPPOSITION_ENGLISH = "Ö1 Opposition report";
@ -24,6 +29,7 @@ public class GradingReportServiceImpl implements GradingReportService {
private final Clock clock;
private final SupervisorGradingReportRepository supervisorGradingReportRepository;
private final GradingReportTemplateRepo gradingReportTemplateRepo;
private final ProjectTypeService projectTypeService;
@Inject
public GradingReportServiceImpl(
@ -31,13 +37,15 @@ public class GradingReportServiceImpl implements GradingReportService {
ThesisSubmissionHistoryService thesisSubmissionHistoryService,
Clock clock,
SupervisorGradingReportRepository supervisorGradingReportRepository,
GradingReportTemplateRepo gradingReportTemplateRepo)
GradingReportTemplateRepo gradingReportTemplateRepo,
ProjectTypeService projectTypeService)
{
this.eventBus = eventBus;
this.thesisSubmissionHistoryService = thesisSubmissionHistoryService;
this.clock = clock;
this.supervisorGradingReportRepository = supervisorGradingReportRepository;
this.gradingReportTemplateRepo = gradingReportTemplateRepo;
this.projectTypeService = projectTypeService;
}
@Override
@ -194,4 +202,14 @@ public class GradingReportServiceImpl implements GradingReportService {
private static boolean isBlank(final String str) {
return str == null || str.isBlank();
}
@Override
public List<ProjectType> getProjectTypes() {
return projectTypeService.findWithModule(ProjectModule.GRADING);
}
@Override
public GradingReportTemplate getCurrentTemplate(ProjectType projectType) {
return gradingReportTemplateRepo.getCurrentTemplate(projectType, LocalDate.now(clock));
}
}

View File

@ -76,6 +76,14 @@ public class GradingReportTemplate extends DomainObject {
return this.id;
}
public LocalDate getValidFrom() {
return validFrom;
}
public void setValidFrom(LocalDate validFrom) {
this.validFrom = validFrom;
}
@Override
public boolean equals(final Object o) {
if (o == this) return true;

View File

@ -2,7 +2,12 @@ package se.su.dsv.scipro.report;
import org.springframework.data.jpa.repository.JpaRepository;
import se.su.dsv.scipro.project.Project;
import se.su.dsv.scipro.system.ProjectType;
import java.time.LocalDate;
public interface GradingReportTemplateRepo extends JpaRepository<GradingReportTemplate, Long> {
GradingReportTemplate getTemplate(Project project);
GradingReportTemplate getCurrentTemplate(ProjectType projectType, LocalDate now);
}

View File

@ -8,6 +8,7 @@ import se.su.dsv.scipro.system.GenericRepo;
import jakarta.inject.Inject;
import jakarta.inject.Provider;
import jakarta.persistence.EntityManager;
import se.su.dsv.scipro.system.ProjectType;
import java.time.LocalDate;
@ -19,13 +20,18 @@ public class GradingReportTemplateRepoImpl extends GenericRepo<GradingReportTemp
@Override
public GradingReportTemplate getTemplate(Project project) {
return getCurrentTemplate(project.getProjectType(), project.getStartDate());
}
@Override
public GradingReportTemplate getCurrentTemplate(ProjectType projectType, LocalDate now) {
QGradingReportTemplate template = QGradingReportTemplate.gradingReportTemplate;
// find the latest template that is valid for the project
JPQLQuery<LocalDate> validFrom = JPAExpressions
.select(template.validFrom.max())
.from(template)
.where(template.projectType.eq(project.getProjectType())
.and(template.validFrom.loe(project.getStartDate())));
return findOne(template.projectType.eq(project.getProjectType()).and(template.validFrom.eq(validFrom)));
.where(template.projectType.eq(projectType)
.and(template.validFrom.loe(now)));
return findOne(template.projectType.eq(projectType).and(template.validFrom.eq(validFrom)));
}
}

View File

@ -14,6 +14,8 @@ import org.apache.wicket.util.convert.converter.LocalDateConverter;
import org.apache.wicket.util.convert.converter.LocalDateTimeConverter;
import se.su.dsv.scipro.activityplan.*;
import se.su.dsv.scipro.admin.pages.*;
import se.su.dsv.scipro.admin.pages.grading.AdminGradingTemplatePage;
import se.su.dsv.scipro.admin.pages.grading.AdminGradingTemplatesOverviewPage;
import se.su.dsv.scipro.admin.pages.settings.*;
import se.su.dsv.scipro.applicationperiod.AdminEditApplicationPeriodExemptionsPage;
import se.su.dsv.scipro.applicationperiod.AdminEditApplicationPeriodPage;
@ -282,6 +284,8 @@ public class SciProApplication extends LifecycleManagedWebApplication {
mountPage("admin/project/survey", AdminSurveyPage.class);
mountPage("admin/project/reviewer", AdminAssignReviewerPage.class);
mountPage("admin/project/reviewer/capacity", AdminReviewerCapacityManagementPage.class);
mountPage("admin/project/grading/template", AdminGradingTemplatesOverviewPage.class);
mountPage("admin/project/grading/template/view", AdminGradingTemplatePage.class);
mountPage("admin/edit", AdminEditProjectPage.class);
mountPage("admin/finalseminars", AdminFinalSeminarPage.class);
mountPage("admin/finalseminars/exemptions", AdminFinalSeminarExemptionPage.class);

View File

@ -1,6 +1,8 @@
package se.su.dsv.scipro.admin.pages;
import se.su.dsv.scipro.activityplan.AdminActivityPlanTemplatesPage;
import se.su.dsv.scipro.admin.pages.grading.AdminGradingTemplatesOverviewPage;
import se.su.dsv.scipro.admin.pages.grading.MenuHighlightGradingTemplates;
import se.su.dsv.scipro.checklists.AdminChecklistPage;
import se.su.dsv.scipro.components.AbstractMenuPanel;
import se.su.dsv.scipro.components.menuhighlighting.MenuHighlightAdminActivityPlanTemplates;
@ -28,7 +30,8 @@ public class AbstractAdminProjectPage extends AbstractAdminPage {
new MenuItem("Checklist templates", AdminChecklistPage.class, MenuHighlightAdminChecklist.class),
new MenuItem("Non work days", NonWorkDaysPage.class),
new MenuItem("Survey", AdminSurveyPage.class),
new MenuItem("Reviewer targets", AdminReviewerCapacityManagementPage.class)
new MenuItem("Reviewer targets", AdminReviewerCapacityManagementPage.class),
new MenuItem("Grading templates", AdminGradingTemplatesOverviewPage.class, MenuHighlightGradingTemplates.class)
);
}

View File

@ -0,0 +1,19 @@
package se.su.dsv.scipro.admin.pages.grading;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import se.su.dsv.scipro.admin.pages.AbstractAdminProjectPage;
import se.su.dsv.scipro.report.GradingReportTemplate;
public class AdminGradingTemplatePage extends AbstractAdminProjectPage implements MenuHighlightGradingTemplates {
private static final String TEMPLATE_ID_PARAMETER = "id";
public AdminGradingTemplatePage(PageParameters parameters) {
long templateId = parameters.get(TEMPLATE_ID_PARAMETER).toLong();
}
public static PageParameters getPageParameters(GradingReportTemplate gradingReportTemplate) {
PageParameters pageParameters = new PageParameters();
pageParameters.add(TEMPLATE_ID_PARAMETER, gradingReportTemplate.getId());
return pageParameters;
}
}

View File

@ -0,0 +1,125 @@
<?xml version="1.0" encoding="UTF-8"?>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
<head>
<wicket:remove>
<link rel="stylesheet" type="text/css" href="../../../../../../../../webapp/css/scipro_m.css">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
</wicket:remove>
</head>
<body>
<div class="container-fluid">
<wicket:extend>
<h1>Grading report template management</h1>
<p>
Here you can manage the grading templates that are used to grade projects.
</p>
<p>
A template consists of multiple criteria, each with a description and the maximum points that can be scored on it.
The point sum total conversion to grade letters is also defined in the template.
</p>
<p>
Each template has a period during which it is in use. The period is defined by a start date,
and it remains current until a new template takes over. There will only ever be one template
current for any given date.
The template that is selected to be used for grading a project is the one that was current the date
the project started.
</p>
<p>
Once a template has become current it is <strong><em>no longer possible</em></strong> to edit it.
</p>
<div class="btn-group mb-3" role="group">
<button type="button" class="btn btn-outline-primary">Create new template</button>
<div class="btn-group" role="group">
<button type="button" class="btn btn-outline-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
</button>
<ul class="dropdown-menu">
<li><button class="dropdown-item" href="#">Create new based on another template</button></li>
</ul>
</div>
</div>
<div class="mb-3" wicket:id="project_types">
<h2 wicket:id="name">Bachelor</h2>
<div class="hstack gap-3 flex-wrap align-items-start" wicket:id="templates">
<div class="card">
<div class="card-header bg-info-subtle">
Upcoming template
</div>
<div class="card-body">
<dl>
<dt>Valid from</dt>
<dd>2024-10-31</dd>
<dt>Note</dt>
<dd>Added a new criteria U14</dd>
</dl>
<span class="card-text">
<a href="#">View / Edit</a>
</span>
</div>
</div>
<div class="card">
<div class="card-header bg-success-subtle">
Current template
</div>
<div class="card-body">
<dl>
<dt>Valid from</dt>
<dd wicket:id="valid_from">2024-01-01</dd>
<wicket:enclosure>
<dt>Note</dt>
<dd wicket:id="note">Change ethics criteria to include AI</dd>
</wicket:enclosure>
</dl>
<span class="card-text">
<a href="#" wicket:id="view_template">View</a>
</span>
</div>
</div>
<div class="card">
<div class="card-header">
Older templates
</div>
<div class="card-body hstack gap-2">
<div class="mw-100">
<dl>
<dt>Valid</dt>
<dd>2023-01-01 to 2023-12-41</dd>
<dt>Note</dt>
<dd>Change ethics criteria to include AI</dd>
</dl>
<span class="card-text">
<a href="#">View</a>
</span>
</div>
<div class="vr"></div>
<div>
<dl>
<dt>Valid</dt>
<dd>2001-01-01 to 2022-12-31</dd>
<dt>Note</dt>
<dd>Change ethics criteria to include AI</dd>
</dl>
<span class="card-text">
<a href="#">View</a>
</span>
</div>
</div>
</div>
</div>
</div>
</wicket:extend>
</div>
</body>
</html>

View File

@ -0,0 +1,48 @@
package se.su.dsv.scipro.admin.pages.grading;
import jakarta.inject.Inject;
import org.apache.wicket.markup.html.GenericWebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.Model;
import se.su.dsv.scipro.admin.pages.AbstractAdminProjectPage;
import se.su.dsv.scipro.components.NonEmptyLabel;
import se.su.dsv.scipro.grading.GradingReportTemplateService;
import se.su.dsv.scipro.report.GradingReportTemplate;
import se.su.dsv.scipro.system.ProjectType;
import java.util.List;
public class AdminGradingTemplatesOverviewPage extends AbstractAdminProjectPage {
@Inject
private GradingReportTemplateService gradingReportTemplateService;
public AdminGradingTemplatesOverviewPage() {
LoadableDetachableModel<List<ProjectType>> projectTypes =
LoadableDetachableModel.of(gradingReportTemplateService::getProjectTypes);
add(new ListView<>("project_types", projectTypes) {
@Override
protected void populateItem(ListItem<ProjectType> item) {
item.add(new Label("name", item.getModel().map(ProjectType::getName)));
item.add(new TemplatesPanel("templates", item.getModel()));
}
});
}
private class TemplatesPanel extends GenericWebMarkupContainer<ProjectType> {
public TemplatesPanel(String id, IModel<ProjectType> model) {
super(id, model);
IModel<GradingReportTemplate> currentTemplate = model.map(gradingReportTemplateService::getCurrentTemplate);
add(new Label("valid_from", currentTemplate.map(GradingReportTemplate::getValidFrom)));
add(new NonEmptyLabel("note", Model.of("")));
add(new BookmarkablePageLink<>("view_template", AdminGradingTemplatePage.class, AdminGradingTemplatePage.getPageParameters(currentTemplate.getObject())));
}
}
}

View File

@ -0,0 +1,6 @@
package se.su.dsv.scipro.admin.pages.grading;
import se.su.dsv.scipro.components.menuhighlighting.MenuHighlight;
public interface MenuHighlightGradingTemplates extends MenuHighlight {
}

View File

@ -0,0 +1,19 @@
package se.su.dsv.scipro.components;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.model.IModel;
/**
* A basic {@link Label} that hides itself if the rendered model object {@link String#isBlank() is blank}
*/
public class NonEmptyLabel extends Label {
public NonEmptyLabel(String id, IModel<?> model) {
super(id, model);
}
@Override
protected void onConfigure() {
super.onConfigure();
setVisible(!getDefaultModelObjectAsString().isBlank());
}
}