Skip to content

Commit

Permalink
Backlog search & filter (#71)
Browse files Browse the repository at this point in the history
* backlog search & filter

* Cleanup & Simplify

* Cleanup

---------

Co-authored-by: Dominik Fuchß <[email protected]>
  • Loading branch information
Feuermagier and dfuchss authored Oct 24, 2024
1 parent 610e674 commit 8613af1
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 74 deletions.
2 changes: 1 addition & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -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<ProgrammingSubmission> lastFetchedSubmissions = new ArrayList<>();
private final List<Runnable> 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<ProgrammingSubmission> 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");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<ProgrammingSubmission> submissions) {
backlogList.removeAll();

List<ProgrammingSubmission> 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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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")
Expand Down

0 comments on commit 8613af1

Please sign in to comment.