diff --git a/.idea/misc.xml b/.idea/misc.xml index ba0541a..447794c 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -7,7 +7,7 @@ - + \ No newline at end of file diff --git a/src/main/java/edu/kit/kastel/sdq/intelligrade/extensions/guis/BacklogPanel.java b/src/main/java/edu/kit/kastel/sdq/intelligrade/extensions/guis/BacklogPanel.java new file mode 100644 index 0000000..d20e2a1 --- /dev/null +++ b/src/main/java/edu/kit/kastel/sdq/intelligrade/extensions/guis/BacklogPanel.java @@ -0,0 +1,167 @@ +/* Licensed under EPL-2.0 2024. */ +package edu.kit.kastel.sdq.intelligrade.extensions.guis; + +import java.awt.event.ActionEvent; +import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import javax.swing.JButton; +import javax.swing.JPanel; +import javax.swing.event.DocumentEvent; + +import com.intellij.icons.AllIcons; +import com.intellij.ui.DocumentAdapter; +import com.intellij.ui.JBColor; +import com.intellij.ui.ScrollPaneFactory; +import com.intellij.ui.SearchTextField; +import com.intellij.ui.components.JBCheckBox; +import com.intellij.ui.components.JBLabel; +import com.intellij.ui.components.JBPanel; +import edu.kit.kastel.sdq.artemis4j.grading.ProgrammingSubmission; +import edu.kit.kastel.sdq.intelligrade.state.PluginState; +import edu.kit.kastel.sdq.intelligrade.utils.ArtemisUtils; +import net.miginfocom.swing.MigLayout; +import org.jetbrains.annotations.NotNull; + +public class BacklogPanel extends JPanel { + private final SearchTextField searchField; + private final JBCheckBox showFirstRound; + private final JBCheckBox showSecondRound; + private final JBLabel shownSubmissionsLabel; + private final JPanel backlogList; + + private List lastFetchedSubmissions = new ArrayList<>(); + private final List onBacklogUpdate = new ArrayList<>(); + + public BacklogPanel() { + super(new MigLayout("wrap 2", "[grow] []")); + + var filterPanel = new JBPanel<>(new MigLayout("wrap 4", "[][grow][][]")); + this.add(filterPanel, "spanx 2, growx"); + + shownSubmissionsLabel = new JBLabel(); + filterPanel.add(shownSubmissionsLabel); + + // Disabling history here so that in the exam review the next student can't see the previous student's id + searchField = new SearchTextField(false); + filterPanel.add(searchField, "growx"); + searchField.addDocumentListener(new DocumentAdapter() { + @Override + protected void textChanged(@NotNull DocumentEvent documentEvent) { + updateBacklog(); + } + }); + + showFirstRound = new JBCheckBox("Round 1"); + showFirstRound.setSelected(true); + showFirstRound.addActionListener(a -> updateBacklog()); + filterPanel.add(showFirstRound); + + showSecondRound = new JBCheckBox("Round 2"); + showSecondRound.setSelected(true); + showSecondRound.addActionListener(a -> updateBacklog()); + filterPanel.add(showSecondRound); + + backlogList = new JBPanel<>(new MigLayout("wrap 5, gapx 10", "[][][][][grow]")); + this.add(ScrollPaneFactory.createScrollPane(backlogList, true), "spanx 2, growx"); + + var refreshButton = new JButton(AllIcons.Actions.Refresh); + refreshButton.addActionListener(this::refreshButtonClicked); + this.add(refreshButton, "skip 1, alignx right"); + } + + private void refreshButtonClicked(ActionEvent actionEvent) { + for (Runnable runnable : onBacklogUpdate) { + runnable.run(); + } + } + + public void addBacklogUpdateListener(Runnable listener) { + onBacklogUpdate.add(listener); + } + + public void setSubmissions(List submissions) { + this.lastFetchedSubmissions = new ArrayList<>(submissions); + this.lastFetchedSubmissions.sort(Comparator.comparing(ProgrammingSubmission::getSubmissionDate)); + this.updateBacklog(); + } + + public void clear() { + this.backlogList.removeAll(); + this.updateUI(); + } + + private void updateBacklog() { + backlogList.removeAll(); + + String searchText = searchField.getText(); + boolean firstRound = showFirstRound.isSelected(); + boolean secondRound = showSecondRound.isSelected(); + + int shown = 0; + for (ProgrammingSubmission submission : lastFetchedSubmissions) { + if (searchText != null && !submission.getParticipantIdentifier().contains(searchText)) { + continue; + } + + if (!firstRound && submission.getCorrectionRound() == 0) { + continue; + } + + if (!secondRound && submission.getCorrectionRound() == 1) { + continue; + } + + shown++; + + // Participant + backlogList.add(new JBLabel(submission.getParticipantIdentifier())); + addSubmissionDateLabel(submission); + // Correction Round + backlogList.add(new JBLabel("Round " + (submission.getCorrectionRound() + 1))); + addScoreItem(submission); + addActionButton(submission); + } + + shownSubmissionsLabel.setText("Showing %d/%d".formatted(shown, lastFetchedSubmissions.size())); + + this.updateUI(); + } + + private void addSubmissionDateLabel(ProgrammingSubmission submission) { + String dateText = submission + .getSubmissionDate() + .format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT, FormatStyle.SHORT)); + backlogList.add(new JBLabel(dateText), "alignx right"); + } + + private void addScoreItem(ProgrammingSubmission submission) { + // Score in percent + var latestResult = submission.getLatestResult(); + String resultText = ""; + if (submission.isSubmitted()) { + resultText = latestResult + .map(resultDTO -> "%.0f%%".formatted(resultDTO.score())) + .orElse("???"); + } + backlogList.add(new JBLabel(resultText), "alignx right"); + } + + private void addActionButton(ProgrammingSubmission submission) { + // Action Button + JButton reopenButton; + if (submission.isSubmitted()) { + reopenButton = new JButton("Reopen"); + } else if (ArtemisUtils.isSubmissionStarted(submission)) { + reopenButton = new JButton("Continue"); + reopenButton.setForeground(JBColor.ORANGE); + } else { + reopenButton = new JButton("Start"); + } + reopenButton.addActionListener(a -> PluginState.getInstance().reopenAssessment(submission)); + backlogList.add(reopenButton, "growx"); + } +} diff --git a/src/main/java/edu/kit/kastel/sdq/intelligrade/extensions/guis/ExercisePanel.java b/src/main/java/edu/kit/kastel/sdq/intelligrade/extensions/guis/ExercisePanel.java index 5d6fd47..60db200 100644 --- a/src/main/java/edu/kit/kastel/sdq/intelligrade/extensions/guis/ExercisePanel.java +++ b/src/main/java/edu/kit/kastel/sdq/intelligrade/extensions/guis/ExercisePanel.java @@ -2,10 +2,6 @@ package edu.kit.kastel.sdq.intelligrade.extensions.guis; import java.awt.event.ItemEvent; -import java.time.format.DateTimeFormatter; -import java.time.format.FormatStyle; -import java.util.ArrayList; -import java.util.Comparator; import java.util.List; import java.util.Optional; import java.util.function.Predicate; @@ -14,7 +10,6 @@ import javax.swing.JPanel; import javax.swing.event.DocumentEvent; -import com.intellij.icons.AllIcons; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; @@ -35,7 +30,6 @@ import com.intellij.ui.components.TextComponentEmptyText; import edu.kit.kastel.sdq.artemis4j.ArtemisNetworkException; import edu.kit.kastel.sdq.artemis4j.client.AssessmentStatsDTO; -import edu.kit.kastel.sdq.artemis4j.client.AssessmentType; import edu.kit.kastel.sdq.artemis4j.grading.ArtemisConnection; import edu.kit.kastel.sdq.artemis4j.grading.Course; import edu.kit.kastel.sdq.artemis4j.grading.Exam; @@ -74,8 +68,7 @@ public class ExercisePanel extends SimpleToolWindowPanel { private JButton closeAssessmentButton; private JButton reRunAutograder; - private JPanel backlogPanel; - private JPanel backlogList; + private BacklogPanel backlogPanel; public ExercisePanel() { super(true, true); @@ -108,7 +101,8 @@ public ExercisePanel() { content.add(assessmentPanel, "span 2, growx"); content.add(new TitledSeparator("Backlog"), "spanx 2, growx"); - createBacklogPanel(); + backlogPanel = new BacklogPanel(); + backlogPanel.addBacklogUpdateListener(this::updateBacklogAndStats); content.add(backlogPanel, "span 2, growx"); setContent(ScrollPaneFactory.createScrollPane(content)); @@ -227,17 +221,6 @@ private void createAssessmentPanel() { assessmentPanel.add(reRunAutograder, "spanx 2, growx"); } - private void createBacklogPanel() { - backlogPanel = new JBPanel<>(new MigLayout("wrap 2", "[grow] []")); - - backlogList = new JBPanel<>(new MigLayout("wrap 5, gapx 10", "[][][][][grow]")); - backlogPanel.add(ScrollPaneFactory.createScrollPane(backlogList, true), "spanx 2, growx"); - - var refreshButton = new JButton(AllIcons.Actions.Refresh); - refreshButton.addActionListener(a -> updateBacklogAndStats()); - backlogPanel.add(refreshButton, "skip 1, alignx right"); - } - private void handleExerciseSelected(ItemEvent e) { // Exercise selected: Update plugin state, enable/disable grading buttons, update backlog if (e.getStateChange() != ItemEvent.DESELECTED) { @@ -368,15 +351,14 @@ private void updateBacklogAndStats() { LOG.warn(ex); ArtemisUtils.displayNetworkErrorBalloon("Failed to fetch backlog or statistics", ex); ApplicationManager.getApplication().invokeLater(() -> { - backlogList.removeAll(); - this.updateUI(); + backlogPanel.clear(); }); return; } ApplicationManager.getApplication().invokeLater(() -> { updateStatistics(exercise, stats, submissions); - updateBacklog(submissions); + backlogPanel.setSubmissions(submissions); updateUI(); // Tell the user that we've done something @@ -412,60 +394,11 @@ private void updateStatistics( int submittedSubmissions = (int) submissions.stream().filter(ProgrammingSubmission::isSubmitted).count(); int lockedSubmissions = (int) - submissions.stream().filter(ExercisePanel::isSubmissionStarted).count(); + submissions.stream().filter(ArtemisUtils::isSubmissionStarted).count(); String userText = "%d (%d locked)".formatted(submittedSubmissions, lockedSubmissions); userStatisticsLabel.setText(userText); } - private void updateBacklog(List submissions) { - backlogList.removeAll(); - - List sortedSubmissions = new ArrayList<>(submissions); - sortedSubmissions.sort(Comparator.comparing(ProgrammingSubmission::getSubmissionDate)); - for (ProgrammingSubmission submission : sortedSubmissions) { - // Participant - backlogList.add(new JBLabel(submission.getParticipantIdentifier())); - - // Submission date - String dateText = submission - .getSubmissionDate() - .format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT, FormatStyle.SHORT)); - backlogList.add(new JBLabel(dateText), "alignx right"); - - // Correction Round - backlogList.add(new JBLabel("Round " + (submission.getCorrectionRound() + 1))); - - // Score in percent - var latestResult = submission.getLatestResult(); - String resultText = ""; - if (submission.isSubmitted()) { - resultText = latestResult - .map(resultDTO -> "%.0f%%".formatted(resultDTO.score())) - .orElse("???"); - } - backlogList.add(new JBLabel(resultText), "alignx right"); - - // Action Button - JButton reopenButton; - if (submission.isSubmitted()) { - reopenButton = new JButton("Reopen"); - } else if (isSubmissionStarted(submission)) { - reopenButton = new JButton("Continue"); - reopenButton.setForeground(JBColor.ORANGE); - } else { - reopenButton = new JButton("Start"); - } - reopenButton.addActionListener(a -> PluginState.getInstance().reopenAssessment(submission)); - backlogList.add(reopenButton, "growx"); - } - } - - private static boolean isSubmissionStarted(ProgrammingSubmission submission) { - return !submission.isSubmitted() - && submission.getLatestResult().isPresent() - && submission.getLatestResult().get().assessmentType() != AssessmentType.AUTOMATIC; - } - private record OptionalExam(Exam exam) { @Override public String toString() { diff --git a/src/main/java/edu/kit/kastel/sdq/intelligrade/utils/ArtemisUtils.java b/src/main/java/edu/kit/kastel/sdq/intelligrade/utils/ArtemisUtils.java index 7c20e20..1307652 100644 --- a/src/main/java/edu/kit/kastel/sdq/intelligrade/utils/ArtemisUtils.java +++ b/src/main/java/edu/kit/kastel/sdq/intelligrade/utils/ArtemisUtils.java @@ -7,6 +7,8 @@ import com.intellij.notification.NotificationGroupManager; import com.intellij.notification.NotificationType; import edu.kit.kastel.sdq.artemis4j.ArtemisNetworkException; +import edu.kit.kastel.sdq.artemis4j.client.AssessmentType; +import edu.kit.kastel.sdq.artemis4j.grading.ProgrammingSubmission; /** * Utility Class to handle Artemis related common tasks such as @@ -24,6 +26,12 @@ public static boolean doesUrlExist(String url) { } } + public static boolean isSubmissionStarted(ProgrammingSubmission submission) { + return !submission.isSubmitted() + && submission.getLatestResult().isPresent() + && submission.getLatestResult().get().assessmentType() != AssessmentType.AUTOMATIC; + } + public static void displayGenericErrorBalloon(String title, String content) { NotificationGroupManager.getInstance() .getNotificationGroup("IntelliGrade Notifications")