Skip to content

Commit

Permalink
Studentreview (#94)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kroko-fant authored Oct 3, 2023
1 parent df684f4 commit 3348d3b
Show file tree
Hide file tree
Showing 23 changed files with 592 additions and 140 deletions.
6 changes: 3 additions & 3 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ tasks {
version.set(properties("pluginVersion").get())
changeNotes.set(
"""<p>
<h1>Removed Deprecation</h1>
<h1>Added Review Mode</h1>
<h2>Improvements</h2>
<ul>
<li>Removed all DSL1 UI-Elements</li>
<li>Upgraded to DSL2</li>
<li>Added Review Mode for Students</li>
<li>Refactored Assessment Editor a bit</li>
</ul>
</p>"""
)
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pluginGroup=de.tum.www1.artemis.plugin.intellij
pluginName=orion
pluginRepositoryUrl=https://github.com/ls1intum/Orion
# SemVer format -> https://semver.org
pluginVersion=1.2.6
pluginVersion=1.2.7
# Last 2 digits of the year and the major version digit, 211-211.* equals (20)21.1.*
# See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
pluginSinceBuild=232
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class OrionStartupProjectRefreshActivity : ProjectActivity, DumbAware {
return
}
when (exerciseInfo.currentView) {
ExerciseView.STUDENT -> Unit
ExerciseView.TUTOR -> OrionAssessmentUtils.configureEditorsForAssessment(project)
else -> Unit
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ interface IOrionExerciseConnector {
*/
fun initializeAssessment(submissionId: Long, feedback: String)

/**
* Initializes the [OrionFeedbackService]
*
* @param feedback a serialized [Programming]
*/
fun initializeFeedback(feedback: String)

/**
* Clones the exercise participation repository and saves it under the artemis home directory
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import de.tum.www1.orion.dto.Feedback
import de.tum.www1.orion.dto.ProgrammingExercise
import de.tum.www1.orion.exercise.OrionAssessmentService
import de.tum.www1.orion.exercise.OrionExerciseService
import de.tum.www1.orion.exercise.OrionFeedbackService
import de.tum.www1.orion.messaging.OrionIntellijStateNotifier
import de.tum.www1.orion.ui.browser.IBrowser
import de.tum.www1.orion.ui.util.notify
Expand All @@ -20,7 +21,7 @@ import java.util.*
/**
* Java handler for when an exercise is first opened
*/
@Service
@Service(Service.Level.PROJECT)
class OrionExerciseConnector(val project: Project) : OrionConnector(), IOrionExerciseConnector {
override fun editExercise(exerciseJson: String) {
val exercise = gson().fromJson(exerciseJson, ProgrammingExercise::class.java)
Expand Down Expand Up @@ -50,6 +51,19 @@ class OrionExerciseConnector(val project: Project) : OrionConnector(), IOrionExe
project.service<OrionAssessmentService>().initializeFeedback(submissionId, feedbackArray)
}

override fun initializeFeedback(feedback: String) {
val feedbackArray = gson().fromJson(feedback, Array<Feedback>::class.java)
initializeFeedbackForParticipations(feedbackArray)
}

/**
* initializes feedback object for a student. it takes the first rated participation
* @param feedback an array of [Feedback] provided by the artemis client.
*/
private fun initializeFeedbackForParticipations(feedback: Array<Feedback>) {
project.service<OrionFeedbackService>().initializeFeedback(0, feedback)
}

override fun initializeHandlers(browser: IBrowser, queryInjector: JBCefJSQuery) {
val reactions = mapOf("editExercise" to { scanner: Scanner -> editExercise(scanner.nextAll()) },
"importParticipation" to { scanner: Scanner -> importParticipation(scanner.nextLine(), scanner.nextAll()) },
Expand All @@ -71,7 +85,8 @@ class OrionExerciseConnector(val project: Project) : OrionConnector(), IOrionExe
},
"initializeAssessment" to { scanner ->
initializeAssessment(scanner.nextLine().toLong(), scanner.nextAll())
})
},
"initializeFeedback" to { scanner -> initializeFeedback(scanner.nextAll()) })
addJavaHandler(browser, reactions)

val parameterNames = mapOf(
Expand All @@ -82,7 +97,8 @@ class OrionExerciseConnector(val project: Project) : OrionConnector(), IOrionExe
"submissionId", "correctionRound", //"testRun",
"downloadURL"
),
"initializeAssessment" to listOf("submissionId", "feedback")
"initializeAssessment" to listOf("submissionId", "feedback"),
"initializeFeedback" to listOf("feedback")
)
addLoadHandler(browser, queryInjector, parameterNames)
}
Expand Down
56 changes: 52 additions & 4 deletions src/main/java/de/tum/www1/orion/dto/Dto.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package de.tum.www1.orion.dto
import de.tum.www1.orion.enumeration.ProgrammingLanguage
import java.net.URL

// All entities are defined like the entities in Artemis

/**
* Course with properties as defined in Artemis at entities/course.model.ts
*/
Expand All @@ -21,6 +23,7 @@ data class ProgrammingExercise(
val programmingLanguage: ProgrammingLanguage,
val auxiliaryRepositories: List<AuxiliaryRepository>?,
val exerciseGroup: ExerciseGroup?,
val studentParticipations: Array<ProgrammingExerciseStudentParticipation>
) {
/**
* Returns the course of the exercise, either directly or, if it is not set, from the associated exam
Expand All @@ -34,14 +37,59 @@ data class ProgrammingExercise(
}
}

data class ExerciseGroup(val id: Long, val exam: Exam)
/**
* A group of Exercises
* @param id the unique id
* @param exam the [Exam] the exercise group belongs to
*/
data class ExerciseGroup(
val id: Long,
val exam: Exam
)

data class Exam(val id: Long, val title: String, val course: Course)
/**
* Exam class providing some values of the exam
*/
data class Exam(
val id: Long,
val title: String,
val course: Course
)

/**
* Programming exercise participation as defined in Artemis at entities/participation/programming-exercise-student-participation.model.ts
* @param id the unique id of a programming exercise
* @param repositoryUrl the URL of the repository
* @param buildPlanId the id of the buildplan
*/
data class ProgrammingExerciseParticipation(val id: Long, val repositoryUrl: URL, val buildPlanId: String)
data class ProgrammingExerciseParticipation(
val id: Long,
val repositoryUrl: URL,
val buildPlanId: String,
var locked: Boolean
)

/**
* The Result of s student submission
* @param id the unique id of the result
* @param rated boolean value indicating if the result has a rating
* @param feedbacks an array containing tutor-feedback of the type [Feedback]
*/
data class Result(
val id: Long,
val rated: Boolean,
val feedbacks: Array<Feedback>?
)

/**
* A Programming excercise participation
* @param id the unique id
* @param results an Array containing [Result]s
*/
data class ProgrammingExerciseStudentParticipation(
val id: Long,
val results: Array<Result>?
)

/**
* Auxiliary repository as defined in Artemis at entities/programming-exercise-auxiliary-repository-model.ts
Expand All @@ -66,7 +114,7 @@ data class AuxiliaryRepository(
data class Feedback(
var credits: Double,
var detailText: String,
val reference: String,
val reference: String?,
val text: String,
val type: String,
var gradingInstruction: GradingInstruction?,
Expand Down
124 changes: 40 additions & 84 deletions src/main/java/de/tum/www1/orion/exercise/OrionAssessmentService.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package de.tum.www1.orion.exercise

import com.intellij.collaboration.ui.codereview.diff.EditorComponentInlaysManager
import com.intellij.openapi.application.WriteAction
import com.intellij.openapi.application.invokeAndWaitIfNeeded
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.components.Service
Expand All @@ -26,21 +25,8 @@ import de.tum.www1.orion.util.translate
* @property project the service belongs to
*/
@Service(Service.Level.PROJECT)
class OrionAssessmentService(private val project: Project) {
private var feedbackPerFile: MutableMap<String, MutableList<Feedback>> = mutableMapOf()
// set storing a pair of path and line for all new, unsaved feedback comments
// required to ensure only one feedback can be created per line
private val pendingFeedback: MutableSet<Pair<String, Int>> = mutableSetOf()
private var isInitialized: Boolean = false

/**
* Initializes the map with feedback sent from Artemis and notifies all [OrionAssessmentEditor]s
*
* @param submissionId to check validity against
* @param feedback to load
*/
fun initializeFeedback(submissionId: Long, feedback: Array<Feedback>) {
// validate submissionId, reject feedback for a different submission
class OrionAssessmentService(private val project: Project) : OrionInlineCommentService(project = project) {
override fun initializeFeedback(submissionId: Long, feedback: Array<Feedback>) {
if (project.service<OrionTutorExerciseRegistry>().submissionId != submissionId) {
project.notify(translate("orion.warning.assessment.submissionId"))
return
Expand All @@ -57,11 +43,14 @@ class OrionAssessmentService(private val project: Project) {

// reference has the format "file:FILE_line:LINE"
feedback.forEach {
val textParts = it.reference.split("_")
if (textParts.size == 2 && textParts[0].startsWith("file:") && textParts[1].startsWith("line:")) {
it.path = textParts[0].substring(5)
it.line = textParts[1].substring(5).toInt()
if (it.reference != null) {
val textParts = it.reference.split("_")
if (textParts.size == 2 && textParts[0].startsWith("file:") && textParts[1].startsWith("line:")) {
it.path = textParts[0].substring(5)
it.line = textParts[1].substring(5).toInt()
}
}

}

// filter invalid entries, group by file
Expand All @@ -81,21 +70,33 @@ class OrionAssessmentService(private val project: Project) {
}
} else {
// if not first load, close and reopen all files opened by assessment editors to reload the feedback
closeAssessmentEditors(true)
closeEditors(true)
}
}
}


/**
* Retrieves the feedback list for the given file or null if no feedback has been loaded yet
* Adds a new feedback comment to the given file and line, if no feedback comment is present at that position yet
*
* @param relativePath of the file to get the feedback for
* @return feedback of the file
* @param path of the file to add a feedback to
* @param line line in that file to add feedback to
* @param inlaysManager passed through to the new comment
*/
fun getFeedbackFor(relativePath: String): List<Feedback>? {
if (!isInitialized) return null
fun addFeedbackCommentIfPossible(path: String, line: Int, inlaysManager: EditorComponentInlaysManager) {

val pair = Pair(path, line)
// if there is already a feedback comment in that file and line, abort
if (pendingFeedback.contains(pair) || getFeedbackFor(path)
?.any { it.line == line } == true
) {
return
}

// add feedback
InlineAssessmentComment(path, line, inlaysManager)
pendingFeedback.add(pair)

return feedbackPerFile[relativePath] ?: emptyList()
}

/**
Expand All @@ -117,6 +118,16 @@ class OrionAssessmentService(private val project: Project) {
synchronizeWithArtemis()
}

/**
* Removes a pending feedback. This allows a different feedback to be added to the given file and line
*
* @param path
* @param line
*/
fun deletePendingFeedback(path: String, line: Int) {
pendingFeedback.remove(Pair(path, line))
}

/**
* Adds the given feedback to the map and informs Artemis
*
Expand All @@ -130,68 +141,13 @@ class OrionAssessmentService(private val project: Project) {
}

/**
* Close all open [OrionAssessmentEditor]s and reinitialize all variables.
* Should be called upon downloading a new submission
*/
fun reset() {
closeAssessmentEditors(false)
feedbackPerFile = mutableMapOf()
isInitialized = false
}

/**
* Adds a new feedback comment to the given file and line, if no feedback comment is present at that position yet
*
* @param path of the file to add a feedback to
* @param line line in that file to add feedback to
* @param inlaysManager passed through to the new comment
*/
fun addFeedbackCommentIfPossible(path: String, line: Int, inlaysManager: EditorComponentInlaysManager) {
val pair = Pair(path, line)
// if there is already a feedback comment in that file and line, abort
if (pendingFeedback.contains(pair) || getFeedbackFor(path)
?.any { it.line == line } == true) {
return
}

// add feedback
InlineAssessmentComment(path, line, inlaysManager)
pendingFeedback.add(pair)
}

/**
* Removes a pending feedback. This allows a different feedback to be added to the given file and line
*
* @param path
* @param line
* Synchronizes tutorfeedback with Artemis
*/
fun deletePendingFeedback(path: String, line: Int) {
pendingFeedback.remove(Pair(path, line))
}
fun synchronizeWithArtemis() {

private fun synchronizeWithArtemis() {
val submissionId = project.service<OrionTutorExerciseRegistry>().submissionId ?: return
val feedbackAsJson = gson().toJson(feedbackPerFile.values.flatten())
project.messageBus.syncPublisher(OrionIntellijStateNotifier.INTELLIJ_STATE_TOPIC)
.updateAssessment(submissionId, feedbackAsJson)
}

private fun closeAssessmentEditors(reopen: Boolean) {
WriteAction.runAndWait<Throwable> {
FileEditorManager.getInstance(project).let { manager ->
val selectedFile = manager.selectedEditor?.file
manager.allEditors.filterIsInstance<OrionAssessmentEditor>().map { it.file }
.forEach {
manager.closeFile(it)
if (reopen && it != selectedFile) {
manager.openFile(it, false)
}
}
// open selected file last to ensure focus; the focusEditor parameter does not seem to work
if (reopen && selectedFile != null) {
manager.openFile(selectedFile, true)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import de.tum.www1.orion.util.runWithIndeterminateProgressModal
import de.tum.www1.orion.util.translate
import de.tum.www1.orion.vcs.OrionGitAdapter
import de.tum.www1.orion.vcs.OrionGitAdapter.clone
import org.slf4j.LoggerFactory
import com.intellij.openapi.diagnostic.Logger
import java.io.File
import java.io.IOException
import java.nio.file.Files
Expand Down Expand Up @@ -107,7 +107,7 @@ class OrionExerciseService(private val project: Project) {
ProjectUtil.openOrImport(projectPath, project, false)
}
} catch (e: IOException) {
LoggerFactory.getLogger(OrionExerciseConnector::class.java).error(e.message, e)
Logger.getInstance(OrionExerciseConnector::class.java).error(e.message, e)
project.notify(e.toString())
}
}
Expand Down
Loading

0 comments on commit 3348d3b

Please sign in to comment.