Merge pull request 'Fix some assesment-related bugs' (#7) from fix/ids_constraints into main

Reviewed-on: jay-tux/grader#7
This commit is contained in:
2025-06-11 10:30:48 +02:00
8 changed files with 116 additions and 106 deletions

View File

@ -56,6 +56,7 @@ object GroupAssignments : UUIDTable("grpAssgmts") {
val name = varchar("name", 50) val name = varchar("name", 50)
val assignment = text("assignment") val assignment = text("assignment")
val deadline = datetime("deadline") val deadline = datetime("deadline")
val globalCriterion = reference("global_crit", GroupAssignmentCriteria.id)
} }
object GroupAssignmentCriteria : UUIDTable("grpAsCr") { object GroupAssignmentCriteria : UUIDTable("grpAsCr") {
@ -70,6 +71,7 @@ object SoloAssignments : UUIDTable("soloAssgmts") {
val name = varchar("name", 50) val name = varchar("name", 50)
val assignment = text("assignment") val assignment = text("assignment")
val deadline = datetime("deadline") val deadline = datetime("deadline")
val globalCriterion = reference("global_crit", SoloAssignmentCriteria.id)
} }
object SoloAssignmentCriteria : UUIDTable("soloAsCr") { object SoloAssignmentCriteria : UUIDTable("soloAsCr") {
@ -86,7 +88,7 @@ object PeerEvaluations : UUIDTable("peerEvals") {
object GroupFeedbacks : CompositeIdTable("grpFdbks") { object GroupFeedbacks : CompositeIdTable("grpFdbks") {
val assignmentId = reference("group_assignment_id", GroupAssignments.id) val assignmentId = reference("group_assignment_id", GroupAssignments.id)
val criterionId = reference("criterion_id", GroupAssignmentCriteria.id).nullable() val criterionId = reference("criterion_id", GroupAssignmentCriteria.id)
val groupId = reference("group_id", Groups.id) val groupId = reference("group_id", Groups.id)
val feedback = text("feedback") val feedback = text("feedback")
val grade = varchar("grade", 32) val grade = varchar("grade", 32)
@ -96,7 +98,7 @@ object GroupFeedbacks : CompositeIdTable("grpFdbks") {
object IndividualFeedbacks : CompositeIdTable("indivFdbks") { object IndividualFeedbacks : CompositeIdTable("indivFdbks") {
val assignmentId = reference("group_assignment_id", GroupAssignments.id) val assignmentId = reference("group_assignment_id", GroupAssignments.id)
val criterionId = reference("criterion_id", GroupAssignmentCriteria.id).nullable() val criterionId = reference("criterion_id", GroupAssignmentCriteria.id)
val groupId = reference("group_id", Groups.id) val groupId = reference("group_id", Groups.id)
val studentId = reference("student_id", Students.id) val studentId = reference("student_id", Students.id)
val feedback = text("feedback") val feedback = text("feedback")
@ -107,7 +109,7 @@ object IndividualFeedbacks : CompositeIdTable("indivFdbks") {
object SoloFeedbacks : CompositeIdTable("soloFdbks") { object SoloFeedbacks : CompositeIdTable("soloFdbks") {
val assignmentId = reference("solo_assignment_id", SoloAssignments.id) val assignmentId = reference("solo_assignment_id", SoloAssignments.id)
val criterionId = reference("criterion_id", SoloAssignmentCriteria.id).nullable() val criterionId = reference("criterion_id", SoloAssignmentCriteria.id)
val studentId = reference("student_id", Students.id) val studentId = reference("student_id", Students.id)
val feedback = text("feedback") val feedback = text("feedback")
val grade = varchar("grade", 32) val grade = varchar("grade", 32)

View File

@ -3,7 +3,10 @@ package com.jaytux.grader.data
import MigrationUtils import MigrationUtils
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.update
object Database { object Database {
val db by lazy { val db by lazy {

View File

@ -60,6 +60,7 @@ class GroupAssignment(id: EntityID<UUID>) : Entity<UUID>(id) {
var name by GroupAssignments.name var name by GroupAssignments.name
var assignment by GroupAssignments.assignment var assignment by GroupAssignments.assignment
var deadline by GroupAssignments.deadline var deadline by GroupAssignments.deadline
var globalCriterion by GroupAssignmentCriterion referencedOn GroupAssignments.globalCriterion
val criteria by GroupAssignmentCriterion referrersOn GroupAssignmentCriteria.assignmentId val criteria by GroupAssignmentCriterion referrersOn GroupAssignmentCriteria.assignmentId
} }
@ -98,6 +99,7 @@ class SoloAssignment(id: EntityID<UUID>) : Entity<UUID>(id) {
var name by SoloAssignments.name var name by SoloAssignments.name
var assignment by SoloAssignments.assignment var assignment by SoloAssignments.assignment
var deadline by SoloAssignments.deadline var deadline by SoloAssignments.deadline
var globalCriterion by SoloAssignmentCriterion referencedOn SoloAssignments.globalCriterion
val criteria by SoloAssignmentCriterion referrersOn SoloAssignmentCriteria.assignmentId val criteria by SoloAssignmentCriterion referrersOn SoloAssignmentCriteria.assignmentId
} }

View File

@ -12,11 +12,9 @@ import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.layout import androidx.compose.ui.layout.layout
import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.jaytux.grader.data.GroupAssignment
import com.jaytux.grader.data.GroupAssignmentCriterion import com.jaytux.grader.data.GroupAssignmentCriterion
import com.jaytux.grader.data.SoloAssignmentCriterion import com.jaytux.grader.data.SoloAssignmentCriterion
import com.jaytux.grader.data.Student import com.jaytux.grader.data.Student
@ -26,7 +24,6 @@ import com.jaytux.grader.viewmodel.SoloAssignmentState
import com.mohamedrejeb.richeditor.model.rememberRichTextState import com.mohamedrejeb.richeditor.model.rememberRichTextState
import com.mohamedrejeb.richeditor.ui.material3.OutlinedRichTextEditor import com.mohamedrejeb.richeditor.ui.material3.OutlinedRichTextEditor
import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalDateTime
import org.jetbrains.exposed.sql.transactions.inTopLevelTransaction
@Composable @Composable
fun GroupAssignmentView(state: GroupAssignmentState) { fun GroupAssignmentView(state: GroupAssignmentState) {
@ -126,14 +123,7 @@ fun groupTaskWidget(
Row { Row {
DateTimePicker(deadline, onSetDeadline) DateTimePicker(deadline, onSetDeadline)
} }
RichTextStyleRow(state = updTask) RichTextField(updTask, Modifier.fillMaxWidth().weight(1f)) { Text("Task") }
OutlinedRichTextEditor(
state = updTask,
modifier = Modifier.fillMaxWidth().weight(1f),
singleLine = false,
minLines = 5,
label = { Text("Task") }
)
CancelSaveRow( CancelSaveRow(
true, true,
{ updTask.setMarkdown(taskMD) }, { updTask.setMarkdown(taskMD) },
@ -188,7 +178,7 @@ fun groupTaskWidget(
@Composable @Composable
fun groupFeedback(state: GroupAssignmentState, fdbk: GroupAssignmentState.LocalGFeedback) { fun groupFeedback(state: GroupAssignmentState, fdbk: GroupAssignmentState.LocalGFeedback) {
val (group, feedback, individual) = fdbk val (group, feedback, individual) = fdbk
var idx by remember(fdbk) { mutableStateOf(0) } var studentIdx by remember(fdbk) { mutableStateOf(0) }
var critIdx by remember(fdbk) { mutableStateOf(0) } var critIdx by remember(fdbk) { mutableStateOf(0) }
val criteria by state.criteria.entities val criteria by state.criteria.entities
val suggestions by state.autofill.entities val suggestions by state.autofill.entities
@ -198,8 +188,8 @@ fun groupFeedback(state: GroupAssignmentState, fdbk: GroupAssignmentState.LocalG
LazyColumn(Modifier.fillMaxHeight().padding(10.dp)) { LazyColumn(Modifier.fillMaxHeight().padding(10.dp)) {
item { item {
Surface( Surface(
Modifier.fillMaxWidth().clickable { idx = 0 }, Modifier.fillMaxWidth().clickable { studentIdx = 0 },
tonalElevation = if (idx == 0) 50.dp else 0.dp, tonalElevation = if (studentIdx == 0) 50.dp else 0.dp,
shape = MaterialTheme.shapes.medium shape = MaterialTheme.shapes.medium
) { ) {
Text("Group feedback", Modifier.padding(5.dp), fontStyle = FontStyle.Italic) Text("Group feedback", Modifier.padding(5.dp), fontStyle = FontStyle.Italic)
@ -209,8 +199,8 @@ fun groupFeedback(state: GroupAssignmentState, fdbk: GroupAssignmentState.LocalG
itemsIndexed(individual.toList()) { i, (student, details) -> itemsIndexed(individual.toList()) { i, (student, details) ->
val (role, _) = details val (role, _) = details
Surface( Surface(
Modifier.fillMaxWidth().clickable { idx = i + 1 }, Modifier.fillMaxWidth().clickable { studentIdx = i + 1 },
tonalElevation = if (idx == i + 1) 50.dp else 0.dp, tonalElevation = if (studentIdx == i + 1) 50.dp else 0.dp,
shape = MaterialTheme.shapes.medium shape = MaterialTheme.shapes.medium
) { ) {
Text("${student.name} (${role ?: "no role"})", Modifier.padding(5.dp)) Text("${student.name} (${role ?: "no role"})", Modifier.padding(5.dp))
@ -219,45 +209,25 @@ fun groupFeedback(state: GroupAssignmentState, fdbk: GroupAssignmentState.LocalG
} }
} }
val updateGrade = { grade: String -> val onSave = { grade: String, fdbk: String ->
if(idx == 0) { when {
state.upsertGroupFeedback(group, feedback.global?.feedback ?: "", grade) studentIdx == 0 && critIdx == 0 -> state.upsertGroupFeedback(group, fdbk, grade)
} studentIdx == 0 && critIdx != 0 -> state.upsertGroupFeedback(group, fdbk, grade, criteria[critIdx - 1])
else { studentIdx != 0 && critIdx == 0 -> state.upsertIndividualFeedback(individual[studentIdx - 1].first, group, fdbk, grade)
val ind = individual[idx - 1] else -> state.upsertIndividualFeedback(individual[studentIdx - 1].first, group, fdbk, grade, criteria[critIdx - 1])
val glob = ind.second.second.global
state.upsertIndividualFeedback(ind.first, group, glob?.feedback ?: "", grade)
}
}
val updateFeedback = { fdbk: String ->
if(idx == 0) {
if(critIdx == 0) {
state.upsertGroupFeedback(group, fdbk, feedback.global?.grade ?: "", null)
}
else {
val current = feedback.byCriterion[critIdx - 1]
state.upsertGroupFeedback(group, fdbk, current.entry?.grade ?: "", current.criterion)
}
}
else {
val ind = individual[idx - 1]
if(critIdx == 0) {
val entry = ind.second.second
state.upsertIndividualFeedback(ind.first, group, fdbk, entry.global?.grade ?: "", null)
}
else {
val entry = ind.second.second.byCriterion[critIdx - 1]
state.upsertIndividualFeedback(ind.first, group, fdbk, entry.entry?.grade ?: "", entry.criterion)
}
} }
} }
groupFeedbackPane( groupFeedbackPane(
criteria, critIdx, { critIdx = it }, feedback.global, criteria, critIdx, { critIdx = it },
if(critIdx == 0) feedback.global else feedback.byCriterion[critIdx - 1].entry, when {
suggestions, updateGrade, updateFeedback, Modifier.weight(0.75f).padding(10.dp), studentIdx == 0 && critIdx == 0 -> feedback.global
key = idx to critIdx studentIdx == 0 && critIdx != 0 -> feedback.byCriterion[critIdx - 1].entry
studentIdx != 0 && critIdx == 0 -> individual[studentIdx - 1].second.second.global
else -> individual[studentIdx - 1].second.second.byCriterion[critIdx - 1].entry
},
suggestions, onSave, Modifier.weight(0.75f).padding(10.dp),
key = studentIdx to critIdx
) )
} }
} }
@ -267,25 +237,27 @@ fun groupFeedbackPane(
criteria: List<GroupAssignmentCriterion>, criteria: List<GroupAssignmentCriterion>,
currentCriterion: Int, currentCriterion: Int,
onSelectCriterion: (Int) -> Unit, onSelectCriterion: (Int) -> Unit,
globFeedback: GroupAssignmentState.FeedbackEntry?, rawFeedback: GroupAssignmentState.FeedbackEntry?,
criterionFeedback: GroupAssignmentState.FeedbackEntry?,
autofill: List<String>, autofill: List<String>,
onSetGrade: (String) -> Unit, onSave: (String, String) -> Unit,
onSetFeedback: (String) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
key: Any? = null key: Any? = null
) { ) {
var grade by remember(globFeedback, key) { mutableStateOf(globFeedback?.grade ?: "") } var grade by remember(rawFeedback, key) { mutableStateOf(rawFeedback?.grade ?: "") }
var feedback by remember(currentCriterion, criteria, criterionFeedback, key) { mutableStateOf(TextFieldValue(criterionFeedback?.feedback ?: "")) } val feedback = rememberRichTextState()
LaunchedEffect(currentCriterion, criteria, rawFeedback, key) {
feedback.setMarkdown(rawFeedback?.feedback ?: "")
}
Column(modifier) { Column(modifier) {
Row { Row {
Text("Overall grade: ", Modifier.align(Alignment.CenterVertically)) Text("Overall grade: ", Modifier.align(Alignment.CenterVertically))
OutlinedTextField(grade, { grade = it }, Modifier.weight(0.2f)) OutlinedTextField(grade, { grade = it }, Modifier.weight(0.2f))
Spacer(Modifier.weight(0.6f)) Spacer(Modifier.weight(0.6f))
Button( Button(
{ onSetGrade(grade); onSetFeedback(feedback.text) }, { onSave(grade, feedback.toMarkdown()) },
Modifier.weight(0.2f).align(Alignment.CenterVertically), Modifier.weight(0.2f).align(Alignment.CenterVertically)
enabled = grade.isNotBlank() || feedback.text.isNotBlank()
) { ) {
Text("Save") Text("Save")
} }
@ -297,11 +269,7 @@ fun groupFeedbackPane(
} }
} }
Spacer(Modifier.height(5.dp)) Spacer(Modifier.height(5.dp))
AutocompleteLineField( RichTextField(feedback, Modifier.fillMaxWidth().weight(1f)) { Text("Feedback") }
feedback, { feedback = it }, Modifier.fillMaxWidth().weight(1f), { Text("Feedback") }
) { filter ->
autofill.filter { x -> x.trim().startsWith(filter.trim()) }
}
} }
} }
@ -480,16 +448,19 @@ fun soloFeedbackPane(
key: Any? = null key: Any? = null
) { ) {
var grade by remember(globFeedback, key) { mutableStateOf(globFeedback?.grade ?: "") } var grade by remember(globFeedback, key) { mutableStateOf(globFeedback?.grade ?: "") }
var feedback by remember(currentCriterion, criteria, key) { mutableStateOf(TextFieldValue(criterionFeedback?.feedback ?: "")) } val feedback = rememberRichTextState()
LaunchedEffect(currentCriterion, criteria, criterionFeedback, key) {
feedback.setMarkdown(criterionFeedback?.feedback ?: "")
}
Column(modifier) { Column(modifier) {
Row { Row {
Text("Overall grade: ", Modifier.align(Alignment.CenterVertically)) Text("Overall grade: ", Modifier.align(Alignment.CenterVertically))
OutlinedTextField(grade, { grade = it }, Modifier.weight(0.2f)) OutlinedTextField(grade, { grade = it }, Modifier.weight(0.2f))
Spacer(Modifier.weight(0.6f)) Spacer(Modifier.weight(0.6f))
Button( Button(
{ onSetGrade(grade); onSetFeedback(feedback.text) }, { onSetGrade(grade); onSetFeedback(feedback.toMarkdown()) },
Modifier.weight(0.2f).align(Alignment.CenterVertically), Modifier.weight(0.2f).align(Alignment.CenterVertically)
enabled = grade.isNotBlank() || feedback.text.isNotBlank()
) { ) {
Text("Save") Text("Save")
} }
@ -501,11 +472,7 @@ fun soloFeedbackPane(
} }
} }
Spacer(Modifier.height(5.dp)) Spacer(Modifier.height(5.dp))
AutocompleteLineField( RichTextField(feedback, Modifier.fillMaxWidth().weight(1f)) { Text("Feedback") }
feedback, { feedback = it }, Modifier.fillMaxWidth().weight(1f), { Text("Feedback") }
) { filter ->
autofill.filter { x -> x.trim().startsWith(filter.trim()) }
}
} }
} }

View File

@ -28,6 +28,7 @@ import androidx.compose.ui.unit.sp
import com.jaytux.grader.loadClipboard import com.jaytux.grader.loadClipboard
import com.jaytux.grader.toClipboard import com.jaytux.grader.toClipboard
import com.mohamedrejeb.richeditor.model.RichTextState import com.mohamedrejeb.richeditor.model.RichTextState
import com.mohamedrejeb.richeditor.ui.material.OutlinedRichTextEditor
@Composable @Composable
fun RichTextStyleRow( fun RichTextStyleRow(
@ -238,3 +239,17 @@ fun RichTextStyleButton(
) )
} }
} }
@Composable
fun RichTextField(
state: RichTextState,
modifier: Modifier = Modifier,
buttonsModifier: Modifier = Modifier,
outerModifier: Modifier = Modifier,
label: @Composable (() -> Unit)? = null
) = Column(outerModifier) {
RichTextStyleRow(buttonsModifier, state)
OutlinedRichTextEditor(
state = state, modifier = modifier, singleLine = false, minLines = 5, label = label
)
}

View File

@ -34,6 +34,7 @@ import androidx.compose.ui.window.*
import com.jaytux.grader.data.Course import com.jaytux.grader.data.Course
import com.jaytux.grader.data.Edition import com.jaytux.grader.data.Edition
import com.jaytux.grader.viewmodel.PeerEvaluationState import com.jaytux.grader.viewmodel.PeerEvaluationState
import com.mohamedrejeb.richeditor.model.RichTextState
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.datetime.* import kotlinx.datetime.*
@ -211,7 +212,8 @@ fun PaneHeader(name: String, type: String, courseEdition: Pair<Course, Edition>)
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun AutocompleteLineField( fun AutocompleteLineField__(
// state: RichTextState,
value: TextFieldValue, onValueChange: (TextFieldValue) -> Unit, value: TextFieldValue, onValueChange: (TextFieldValue) -> Unit,
modifier: Modifier = Modifier, label: @Composable (() -> Unit)? = null, modifier: Modifier = Modifier, label: @Composable (() -> Unit)? = null,
onFilter: (String) -> List<String> onFilter: (String) -> List<String>

View File

@ -171,10 +171,14 @@ class EditionState(val edition: Edition) {
fun newSoloAssignment(name: String) { fun newSoloAssignment(name: String) {
transaction { transaction {
SoloAssignment.new { val assign = SoloAssignment.new {
this.name = name; this.edition = this@EditionState.edition; assignment = ""; deadline = now() this.name = name; this.edition = this@EditionState.edition; assignment = ""; deadline = now()
this.number = nextIdx() this.number = nextIdx()
} }
val global = SoloAssignmentCriterion.new {
this.name = "_global"; this.description = "[Global] Meta-criterion for $name"; this.assignment = assign
}
assign.globalCriterion = global
solo.refresh() solo.refresh()
} }
} }
@ -186,10 +190,14 @@ class EditionState(val edition: Edition) {
} }
fun newGroupAssignment(name: String) { fun newGroupAssignment(name: String) {
transaction { transaction {
GroupAssignment.new { val assign = GroupAssignment.new {
this.name = name; this.edition = this@EditionState.edition; assignment = ""; deadline = now() this.name = name; this.edition = this@EditionState.edition; assignment = ""; deadline = now()
this.number = nextIdx() this.number = nextIdx()
} }
val global = GroupAssignmentCriterion.new {
this.name = "_global"; this.description = "[Global] Meta-criterion for $name"; this.assignment = assign
}
assign.globalCriterion = global
groupAs.refresh() groupAs.refresh()
} }
} }
@ -373,12 +381,12 @@ class StudentState(val student: Student, edition: Edition) {
val asGroup = (GroupAssignments innerJoin GroupAssignmentCriteria innerJoin GroupFeedbacks innerJoin Groups).selectAll().where { val asGroup = (GroupAssignments innerJoin GroupAssignmentCriteria innerJoin GroupFeedbacks innerJoin Groups).selectAll().where {
(GroupFeedbacks.groupId inList groupsForEdition.keys.toList()) and (GroupFeedbacks.groupId inList groupsForEdition.keys.toList()) and
(GroupAssignmentCriteria.name eq "") (GroupAssignmentCriteria.id eq GroupAssignments.globalCriterion)
}.map { it[GroupAssignments.id] to it } }.map { it[GroupAssignments.id] to it }
val asIndividual = (GroupAssignments innerJoin GroupAssignmentCriteria innerJoin IndividualFeedbacks innerJoin Groups).selectAll().where { val asIndividual = (GroupAssignments innerJoin GroupAssignmentCriteria innerJoin IndividualFeedbacks innerJoin Groups).selectAll().where {
(IndividualFeedbacks.studentId eq student.id) and (IndividualFeedbacks.studentId eq student.id) and
(GroupAssignmentCriteria.name eq "") (GroupAssignmentCriteria.id eq GroupAssignments.globalCriterion)
}.map { it[GroupAssignments.id] to it } }.map { it[GroupAssignments.id] to it }
val res = mutableMapOf<EntityID<UUID>, LocalGroupGrade>() val res = mutableMapOf<EntityID<UUID>, LocalGroupGrade>()
@ -475,7 +483,7 @@ class GroupAssignmentState(val assignment: GroupAssignment) {
private val _task = mutableStateOf(assignment.assignment); val task = _task.immutable() private val _task = mutableStateOf(assignment.assignment); val task = _task.immutable()
private val _deadline = mutableStateOf(assignment.deadline); val deadline = _deadline.immutable() private val _deadline = mutableStateOf(assignment.deadline); val deadline = _deadline.immutable()
val criteria = RawDbState { val criteria = RawDbState {
assignment.criteria.orderBy(GroupAssignmentCriteria.name to SortOrder.ASC).toList() assignment.criteria.orderBy(GroupAssignmentCriteria.name to SortOrder.ASC).filter { it.id != assignment.globalCriterion.id }
} }
val feedback = RawDbState { loadFeedback() } val feedback = RawDbState { loadFeedback() }
@ -494,7 +502,7 @@ class GroupAssignmentState(val assignment: GroupAssignment) {
private fun Transaction.loadFeedback(): List<Pair<Group, LocalGFeedback>> { private fun Transaction.loadFeedback(): List<Pair<Group, LocalGFeedback>> {
val allCrit = GroupAssignmentCriterion.find { val allCrit = GroupAssignmentCriterion.find {
GroupAssignmentCriteria.assignmentId eq assignment.id GroupAssignmentCriteria.assignmentId eq assignment.id
} }//.filter { it.id != assignment.globalCriterion.id }
return Group.find { return Group.find {
(Groups.editionId eq assignment.edition.id) (Groups.editionId eq assignment.edition.id)
@ -502,16 +510,19 @@ class GroupAssignmentState(val assignment: GroupAssignment) {
val forGroup = (GroupFeedbacks innerJoin Groups).selectAll().where { val forGroup = (GroupFeedbacks innerJoin Groups).selectAll().where {
(GroupFeedbacks.assignmentId eq assignment.id) and (Groups.id eq group.id) (GroupFeedbacks.assignmentId eq assignment.id) and (Groups.id eq group.id)
}.map { row -> }.map { row ->
val crit = row[GroupFeedbacks.criterionId]?.let { GroupAssignmentCriterion[it] } val crit = GroupAssignmentCriterion[row[GroupFeedbacks.criterionId]]
val fdbk = row[GroupFeedbacks.feedback] val fdbk = row[GroupFeedbacks.feedback]
val grade = row[GroupFeedbacks.grade] val grade = row[GroupFeedbacks.grade]
crit to FeedbackEntry(fdbk, grade) crit to FeedbackEntry(fdbk, grade)
} }
val global = forGroup.firstOrNull { it.first == null }?.second val global = forGroup.firstOrNull { it.first.id == assignment.globalCriterion.id }?.second
val byCrit_ = forGroup.map { it.first?.let { k -> LocalCriterionFeedback(k, it.second) } } val byCrit_ = forGroup
.filterNotNull().associateBy { it.criterion.id } .filter{ it.first.id != assignment.globalCriterion.id }
.map { LocalCriterionFeedback(it.first, it.second) }
.associateBy { it.criterion.id }
val byCrit = allCrit.map { c -> val byCrit = allCrit.map { c ->
byCrit_[c.id] ?: LocalCriterionFeedback(c, null) byCrit_[c.id] ?: LocalCriterionFeedback(c, null)
} }
@ -522,21 +533,25 @@ class GroupAssignmentState(val assignment: GroupAssignment) {
val student = it.student val student = it.student
val role = it.role val role = it.role
val forSt = (IndividualFeedbacks innerJoin Groups innerJoin GroupStudents) val forSt = (IndividualFeedbacks innerJoin Groups)
.selectAll().where { .selectAll().where {
(IndividualFeedbacks.assignmentId eq assignment.id) and (IndividualFeedbacks.assignmentId eq assignment.id) and
(GroupStudents.studentId eq student.id) and (Groups.id eq group.id) (IndividualFeedbacks.studentId eq student.id) and (Groups.id eq group.id)
}.map { row -> }.map { row ->
val crit = row[IndividualFeedbacks.criterionId]?.let { id -> GroupAssignmentCriterion[id] } val stdId = row[IndividualFeedbacks.studentId]
val crit = GroupAssignmentCriterion[row[IndividualFeedbacks.criterionId]]
val fdbk = row[IndividualFeedbacks.feedback] val fdbk = row[IndividualFeedbacks.feedback]
val grade = row[IndividualFeedbacks.grade] val grade = row[IndividualFeedbacks.grade]
crit to FeedbackEntry(fdbk, grade) crit to FeedbackEntry(fdbk, grade)
} }
val global = forSt.firstOrNull { it.first == null }?.second val global = forSt.firstOrNull { it.first.id == assignment.globalCriterion.id }?.second
val byCrit_ = forSt.map { it.first?.let { k -> LocalCriterionFeedback(k, it.second) } } val byCrit_ = forSt
.filterNotNull().associateBy { it.criterion.id } .filter { it.first != assignment.globalCriterion.id }
.map { LocalCriterionFeedback(it.first, it.second) }
.associateBy { it.criterion.id }
val byCrit = allCrit.map { c -> val byCrit = allCrit.map { c ->
byCrit_[c.id] ?: LocalCriterionFeedback(c, null) byCrit_[c.id] ?: LocalCriterionFeedback(c, null)
} }
@ -556,7 +571,7 @@ class GroupAssignmentState(val assignment: GroupAssignment) {
it[groupId] = group.id it[groupId] = group.id
it[this.feedback] = msg it[this.feedback] = msg
it[this.grade] = grd it[this.grade] = grd
it[criterionId] = criterion?.id it[criterionId] = criterion?.id ?: assignment.globalCriterion.id
} }
} }
feedback.refresh(); autofill.refresh() feedback.refresh(); autofill.refresh()
@ -570,7 +585,7 @@ class GroupAssignmentState(val assignment: GroupAssignment) {
it[studentId] = student.id it[studentId] = student.id
it[this.feedback] = msg it[this.feedback] = msg
it[this.grade] = grd it[this.grade] = grd
it[criterionId] = criterion?.id it[criterionId] = criterion?.id ?: assignment.globalCriterion.id
} }
} }
feedback.refresh(); autofill.refresh() feedback.refresh(); autofill.refresh()
@ -628,7 +643,7 @@ class SoloAssignmentState(val assignment: SoloAssignment) {
private val _task = mutableStateOf(assignment.assignment); val task = _task.immutable() private val _task = mutableStateOf(assignment.assignment); val task = _task.immutable()
private val _deadline = mutableStateOf(assignment.deadline); val deadline = _deadline.immutable() private val _deadline = mutableStateOf(assignment.deadline); val deadline = _deadline.immutable()
val criteria = RawDbState { val criteria = RawDbState {
assignment.criteria.orderBy(SoloAssignmentCriteria.name to SortOrder.ASC).toList() assignment.criteria.orderBy(SoloAssignmentCriteria.name to SortOrder.ASC).filter { it.id != assignment.globalCriterion.id }
} }
val feedback = RawDbState { loadFeedback() } val feedback = RawDbState { loadFeedback() }
@ -638,25 +653,28 @@ class SoloAssignmentState(val assignment: SoloAssignment) {
}.flatten().distinct().sorted() }.flatten().distinct().sorted()
} }
private fun Transaction.loadFeedback(): List<Pair<Student, FullFeedback>> { private fun Transaction.loadFeedback(): List<Pair<Student, FullFeedback>> {3
val allCrit = SoloAssignmentCriterion.find { val allCrit = SoloAssignmentCriterion.find {
SoloAssignmentCriteria.assignmentId eq assignment.id SoloAssignmentCriteria.assignmentId eq assignment.id
} }.filter { it.id != assignment.globalCriterion.id }
return editionCourse.second.soloStudents.sortAsc(Students.name).map { student -> return editionCourse.second.soloStudents.sortAsc(Students.name).map { student ->
val forStudent = (IndividualFeedbacks innerJoin Students).selectAll().where { val forStudent = (IndividualFeedbacks innerJoin Students).selectAll().where {
(IndividualFeedbacks.assignmentId eq assignment.id) and (Students.id eq student.id) (IndividualFeedbacks.assignmentId eq assignment.id) and (Students.id eq student.id)
}.map { row -> }.map { row ->
val crit = row[IndividualFeedbacks.criterionId]?.let { SoloAssignmentCriterion[it] } val crit = SoloAssignmentCriterion[row[IndividualFeedbacks.criterionId]]
val fdbk = row[IndividualFeedbacks.feedback] val fdbk = row[IndividualFeedbacks.feedback]
val grade = row[IndividualFeedbacks.grade] val grade = row[IndividualFeedbacks.grade]
crit to LocalFeedback(fdbk, grade) crit to LocalFeedback(fdbk, grade)
} }
val global = forStudent.firstOrNull { it.first == null }?.second val global = forStudent.firstOrNull { it.first == assignment.globalCriterion.id }?.second
val byCrit_ = forStudent.map { it.first?.let { k -> Pair(k, it.second) } } val byCrit_ = forStudent
.filterNotNull().associateBy { it.first.id } .filter { it.first != assignment.globalCriterion.id }
.map { Pair(it.first, it.second) }
.associateBy { it.first.id }
val byCrit = allCrit.map { c -> val byCrit = allCrit.map { c ->
byCrit_[c.id] ?: Pair(c, null) byCrit_[c.id] ?: Pair(c, null)
} }
@ -672,7 +690,7 @@ class SoloAssignmentState(val assignment: SoloAssignment) {
it[studentId] = student.id it[studentId] = student.id
it[this.feedback] = msg ?: "" it[this.feedback] = msg ?: ""
it[this.grade] = grd ?: "" it[this.grade] = grd ?: ""
it[criterionId] = criterion?.id it[criterionId] = criterion?.id ?: assignment.globalCriterion.id
} }
} }
feedback.refresh(); autofill.refresh() feedback.refresh(); autofill.refresh()

View File

@ -20,6 +20,7 @@ kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-co
exposed-core = { group = "org.jetbrains.exposed", name = "exposed-core", version.ref = "exposed" } exposed-core = { group = "org.jetbrains.exposed", name = "exposed-core", version.ref = "exposed" }
exposed-dao = { group = "org.jetbrains.exposed", name = "exposed-dao", version.ref = "exposed" } exposed-dao = { group = "org.jetbrains.exposed", name = "exposed-dao", version.ref = "exposed" }
exposed-jdbc = { group = "org.jetbrains.exposed", name = "exposed-jdbc", version.ref = "exposed" } exposed-jdbc = { group = "org.jetbrains.exposed", name = "exposed-jdbc", version.ref = "exposed" }
exposed-migration = { group = "org.jetbrains.exposed", name = "exposed-migration", version.ref = "exposed" }
exposed-kotlin-datetime = { group = "org.jetbrains.exposed", name = "exposed-kotlin-datetime", version.ref = "exposed" } exposed-kotlin-datetime = { group = "org.jetbrains.exposed", name = "exposed-kotlin-datetime", version.ref = "exposed" }
sqlite = { group = "org.xerial", name = "sqlite-jdbc", version = "3.34.0" } sqlite = { group = "org.xerial", name = "sqlite-jdbc", version = "3.34.0" }
sl4j = { group = "org.slf4j", name = "slf4j-simple", version = "2.0.12" } sl4j = { group = "org.slf4j", name = "slf4j-simple", version = "2.0.12" }