diff --git a/build.gradle b/build.gradle
index ec0858c4..5f872723 100644
--- a/build.gradle
+++ b/build.gradle
@@ -40,14 +40,14 @@ dependencies {
testImplementation group: 'io.github.thepieterdc.random', name: 'random', version: '1.0.2'
}
-task calculateNextVersion {
+tasks.register('calculateNextVersion') {
doLast {
def version = (project.version as String).split('\\.').collect { Integer.parseInt(it) }
println(String.format("%s.%s.%s", version[0], version[1], version[2] + 1))
}
}
-task generateBuildConfig {
+tasks.register('generateBuildConfig') {
var outputDir = file("$projectDir/src/main/java/")
doFirst {
def srcFile = new File(outputDir, "io/github/thepieterdc/dodona/plugin/BuildConfig.java")
@@ -87,9 +87,9 @@ static def getVerifiableVersions() {
intellij {
downloadSources.set(false)
pluginName.set('dodona')
- plugins.set(['java', 'PythonCore:211.7628.24'])
+ plugins.set(['java', 'PythonCore:221.6008.17'])
updateSinceUntilBuild.set(false)
- version.set('211.7628.21')
+ version.set('221.6008.13')
}
jacocoTestReport {
@@ -107,7 +107,7 @@ patchPluginXml {
pluginDescription = 'Companion plugin for the Ghent University Dodona platform, which allows you to submit exercises right from your favourite JetBrains IDE. More information can be found at https://docs.dodona.be/en/guides/pycharm-plugin/'
- sinceBuild = '211.7628.21'
+ sinceBuild = '221.6008.13'
}
publishPlugin {
diff --git a/src/main/java/io/github/thepieterdc/dodona/plugin/authentication/ui/DodonaAccountPanel.java b/src/main/java/io/github/thepieterdc/dodona/plugin/authentication/ui/DodonaAccountPanel.java
index 2b48ace2..326ef532 100644
--- a/src/main/java/io/github/thepieterdc/dodona/plugin/authentication/ui/DodonaAccountPanel.java
+++ b/src/main/java/io/github/thepieterdc/dodona/plugin/authentication/ui/DodonaAccountPanel.java
@@ -39,12 +39,12 @@
public class DodonaAccountPanel extends BorderLayoutPanel implements Disposable {
private final CollectionListModel accountListModel;
private final JBList accountList;
-
+
private boolean modified;
-
+
@Nullable
private String newToken;
-
+
/**
* DodonaAccountPanel constructor.
*/
@@ -53,12 +53,12 @@ public DodonaAccountPanel() {
this.accountListModel = new CollectionListModel<>();
this.accountList = this.createAccountsList();
this.modified = false;
-
+
this.newToken = null;
-
+
this.addToCenter(this.createCenterComponent());
}
-
+
/**
* Shows an add account dialog.
*/
@@ -68,35 +68,35 @@ private void addAccount() {
// Create a new account.
final DodonaAccount account = DodonaAccountManager
.createAccount(dialog.getServer(), dialog.getUser());
-
+
// Remove the previous account.
this.removeAccount();
-
+
// Store the token.
this.newToken = dialog.getToken();
-
+
// Add the new account to the model.
this.accountListModel.add(account);
-
+
// Set the modification status.
this.modified = true;
}
}
-
+
/**
* Clears the modification status.
*/
public void clearModified() {
this.modified = false;
}
-
+
/**
* Clears the map of new authentication data.
*/
public void clearNewData() {
this.newToken = null;
}
-
+
/**
* Creates the list of Dodona accounts.
*
@@ -113,10 +113,10 @@ private JBList createAccountsList() {
.appendText(DodonaBundle.message("auth.accounts.empty"))
.appendSecondaryText(DodonaBundle.message("auth.accounts.add"), SimpleTextAttributes.LINK_ATTRIBUTES, e -> this.addAccount())
.appendSecondaryText(String.format(" (%s)", KeymapUtil.getFirstKeyboardShortcutText(CommonShortcuts.getNew())), StatusText.DEFAULT_ATTRIBUTES, null);
- UIUtil.putClientProperty(this, UIUtil.NOT_IN_HIERARCHY_COMPONENTS, Collections.singletonList(renderer));
+ this.putClientProperty(UIUtil.NOT_IN_HIERARCHY_COMPONENTS, Collections.singletonList(renderer));
return ret;
}
-
+
/**
* Creates the center component of the panel.
*
@@ -132,12 +132,12 @@ private Component createCenterComponent() {
.setPanelBorder(IdeBorderFactory.createBorder(SideBorder.TOP | SideBorder.BOTTOM))
.createPanel();
}
-
+
@Override
public void dispose() {
// Not implemented.
}
-
+
/**
* Gets the account and its access token, if the account was newly added.
*
@@ -150,14 +150,14 @@ public Pair getAccount() {
if (this.accountListModel.isEmpty()) {
return Pair.empty();
}
-
+
// Return the account and its token.
return Pair.create(
this.accountListModel.getElementAt(0),
this.newToken
);
}
-
+
/**
* Gets whether the accounts were modified.
*
@@ -166,7 +166,7 @@ public Pair getAccount() {
public boolean isModified() {
return this.modified;
}
-
+
/**
* Removes the selected account.
*/
@@ -176,7 +176,7 @@ private void removeAccount() {
this.modified = true;
}
}
-
+
/**
* Sets the account from the settings.
*/
diff --git a/src/main/java/io/github/thepieterdc/dodona/plugin/feedback/impl/FeedbackServiceImpl.java b/src/main/java/io/github/thepieterdc/dodona/plugin/feedback/impl/FeedbackServiceImpl.java
index 5a05bcc3..882d9db1 100644
--- a/src/main/java/io/github/thepieterdc/dodona/plugin/feedback/impl/FeedbackServiceImpl.java
+++ b/src/main/java/io/github/thepieterdc/dodona/plugin/feedback/impl/FeedbackServiceImpl.java
@@ -23,26 +23,34 @@
import java.util.Optional;
import java.util.function.BiConsumer;
+import static io.github.thepieterdc.dodona.plugin.notifications.SendableNotification.*;
+
public class FeedbackServiceImpl implements FeedbackService {
private final NotificationService notifications;
- private final Map> providers = new EnumMap<>(SubmissionStatus.class);
-
+ private final Map>
+ providers = new EnumMap<>(SubmissionStatus.class);
+
/**
* FeedbackService constructor.
*/
public FeedbackServiceImpl(final Project project) {
this.notifications = NotificationService.getInstance(project);
-
- this.providers.put(SubmissionStatus.COMPILATION_ERROR, this::compilationError);
+
+ this.providers.put(SubmissionStatus.COMPILATION_ERROR,
+ this::compilationError);
this.providers.put(SubmissionStatus.CORRECT, this::correct);
- this.providers.put(SubmissionStatus.INTERNAL_ERROR, (ex, sub) -> this.internalError(ex));
- this.providers.put(SubmissionStatus.MEMORY_LIMIT_EXCEEDED, this::memoryLimitExceeded);
- this.providers.put(SubmissionStatus.OUTPUT_LIMIT_EXCEEDED, this::outputLimitExceeded);
+ this.providers.put(SubmissionStatus.INTERNAL_ERROR,
+ (ex, sub) -> this.internalError(ex));
+ this.providers.put(SubmissionStatus.MEMORY_LIMIT_EXCEEDED,
+ this::memoryLimitExceeded);
+ this.providers.put(SubmissionStatus.OUTPUT_LIMIT_EXCEEDED,
+ this::outputLimitExceeded);
this.providers.put(SubmissionStatus.RUNTIME_ERROR, this::runtimeError);
- this.providers.put(SubmissionStatus.TIME_LIMIT_EXCEEDED, this::timeLimitExceeded);
+ this.providers.put(SubmissionStatus.TIME_LIMIT_EXCEEDED,
+ this::timeLimitExceeded);
this.providers.put(SubmissionStatus.WRONG, this::wrong);
}
-
+
/**
* Compilation error handler.
*
@@ -50,40 +58,44 @@ public FeedbackServiceImpl(final Project project) {
* @param submission the submission
*/
private void compilationError(final Exercise exercise,
- final SubmissionInfo submission) {
- this.notifications.error(
- DodonaBundle.message("feedback.compile_error.title"),
- SubmissionStatusIcon.COMPILATION_ERROR,
- DodonaBundle.message("feedback.compile_error.message", exercise.getName(), submission.getUrl())
- );
+ final SubmissionInfo submission) {
+ this.notifications.send(
+ error(DodonaBundle.message("feedback.compile_error.title"),
+ DodonaBundle.message("feedback.compile_error.message",
+ exercise.getName())).withIcon(
+ SubmissionStatusIcon.COMPILATION_ERROR)
+ .withLink(
+ DodonaBundle.message("feedback.compile_error.link_label"),
+ submission.getUrl()));
}
-
+
/**
* Correct solution handler.
*
* @param exercise the exercise
*/
private void correct(final Exercise exercise,
- final SubmissionInfo submission) {
- this.notifications.info(
- DodonaBundle.message("feedback.correct.title"),
- SubmissionStatusIcon.CORRECT,
- DodonaBundle.message("feedback.correct.message", exercise.getName(), submission.getUrl())
- );
+ final SubmissionInfo submission) {
+ this.notifications.send(
+ info(DodonaBundle.message("feedback.correct.title"),
+ DodonaBundle.message("feedback.correct.message",
+ exercise.getName())).withIcon(SubmissionStatusIcon.CORRECT)
+ .withLink(DodonaBundle.message("feedback.correct.link_label"),
+ submission.getUrl()));
}
-
+
/**
* Internal error handler.
*
* @param exercise the exercise
*/
private void internalError(final Exercise exercise) {
- this.notifications.warning(
- DodonaBundle.message("feedback.internal_error.title"),
- DodonaBundle.message("feedback.internal_error.message", exercise)
- );
+ this.notifications.send(
+ warning(DodonaBundle.message("feedback.internal_error.title"),
+ DodonaBundle.message("feedback.internal_error.message",
+ exercise)));
}
-
+
/**
* Memory limit handler.
*
@@ -91,14 +103,17 @@ private void internalError(final Exercise exercise) {
* @param submission the submission
*/
private void memoryLimitExceeded(final Exercise exercise,
- final SubmissionInfo submission) {
- this.notifications.error(
- DodonaBundle.message("feedback.memory_limit.title"),
- SubmissionStatusIcon.MEMORY_LIMIT_EXCEEDED,
- DodonaBundle.message("feedback.memory_limit.message", exercise.getName(), submission.getUrl())
- );
+ final SubmissionInfo submission) {
+ this.notifications.send(
+ error(DodonaBundle.message("feedback.memory_limit.title"),
+ DodonaBundle.message("feedback.memory_limit.message",
+ exercise.getName())).withIcon(
+ SubmissionStatusIcon.MEMORY_LIMIT_EXCEEDED)
+ .withLink(
+ DodonaBundle.message("feedback.memory_limit.link_label"),
+ submission.getUrl()));
}
-
+
/**
* Output limit handler.
*
@@ -106,20 +121,24 @@ private void memoryLimitExceeded(final Exercise exercise,
* @param submission the submission
*/
private void outputLimitExceeded(final Exercise exercise,
- final SubmissionInfo submission) {
- this.notifications.error(
- DodonaBundle.message("feedback.output_limit.title"),
- SubmissionStatusIcon.OUTPUT_LIMIT_EXCEEDED,
- DodonaBundle.message("feedback.output_limit.message", exercise.getName(), submission.getUrl())
- );
+ final SubmissionInfo submission) {
+ this.notifications.send(
+ error(DodonaBundle.message("feedback.output_limit.title"),
+ DodonaBundle.message("feedback.output_limit.message",
+ exercise.getName())).withIcon(
+ SubmissionStatusIcon.OUTPUT_LIMIT_EXCEEDED)
+ .withLink(
+ DodonaBundle.message("feedback.output_limit.link_label"),
+ submission.getUrl()));
}
-
+
@Override
- public void notify(final Exercise exercise, final SubmissionInfo submission) {
+ public void notify(final Exercise exercise,
+ final SubmissionInfo submission) {
Optional.ofNullable(this.providers.get(submission.getStatus()))
.ifPresent(p -> p.accept(exercise, submission));
}
-
+
/**
* Runtime error handler.
*
@@ -127,14 +146,17 @@ public void notify(final Exercise exercise, final SubmissionInfo submission) {
* @param submission the submission
*/
private void runtimeError(final Exercise exercise,
- final SubmissionInfo submission) {
- this.notifications.error(
- DodonaBundle.message("feedback.runtime_error.title"),
- SubmissionStatusIcon.RUNTIME_ERROR,
- DodonaBundle.message("feedback.runtime_error.message", exercise.getName(), submission.getUrl())
- );
+ final SubmissionInfo submission) {
+ this.notifications.send(
+ error(DodonaBundle.message("feedback.runtime_error.title"),
+ DodonaBundle.message("feedback.runtime_error.message",
+ exercise.getName())).withIcon(
+ SubmissionStatusIcon.RUNTIME_ERROR)
+ .withLink(
+ DodonaBundle.message("feedback.runtime_error.link_label"),
+ submission.getUrl()));
}
-
+
/**
* Time limit handler.
*
@@ -142,25 +164,31 @@ private void runtimeError(final Exercise exercise,
* @param submission the submission
*/
private void timeLimitExceeded(final Exercise exercise,
- final SubmissionInfo submission) {
- this.notifications.error(
- DodonaBundle.message("feedback.time_limit.title"),
- SubmissionStatusIcon.TIME_LIMIT_EXCEEDED,
- DodonaBundle.message("feedback.time_limit.message", exercise.getName(), submission.getUrl())
- );
+ final SubmissionInfo submission) {
+ this.notifications.send(
+ error(DodonaBundle.message("feedback.time_limit.title"),
+ DodonaBundle.message("feedback.time_limit.message",
+ exercise.getName())).withIcon(
+ SubmissionStatusIcon.TIME_LIMIT_EXCEEDED)
+ .withLink(
+ DodonaBundle.message("feedback.time_limit.link_label"),
+ submission.getUrl()));
}
-
+
/**
* Wrong solution handler.
*
* @param exercise the exercise
* @param submission the submission
*/
- private void wrong(final Exercise exercise, final SubmissionInfo submission) {
- this.notifications.warning(
- DodonaBundle.message("feedback.wrong.title"),
- SubmissionStatusIcon.WRONG,
- DodonaBundle.message("feedback.wrong.message", exercise.getName(), submission.getSummary(), submission.getUrl())
- );
+ private void wrong(final Exercise exercise,
+ final SubmissionInfo submission) {
+ this.notifications.send(
+ warning(DodonaBundle.message("feedback.wrong.title"),
+ DodonaBundle.message("feedback.wrong.message",
+ exercise.getName(), submission.getSummary())).withIcon(
+ SubmissionStatusIcon.COMPILATION_ERROR)
+ .withLink(DodonaBundle.message("feedback.wrong.link_label"),
+ submission.getUrl()));
}
}
diff --git a/src/main/java/io/github/thepieterdc/dodona/plugin/notifications/NotificationService.java b/src/main/java/io/github/thepieterdc/dodona/plugin/notifications/NotificationService.java
index 8ed6dfb6..bc14f6ba 100644
--- a/src/main/java/io/github/thepieterdc/dodona/plugin/notifications/NotificationService.java
+++ b/src/main/java/io/github/thepieterdc/dodona/plugin/notifications/NotificationService.java
@@ -11,29 +11,11 @@
import com.intellij.openapi.project.Project;
import javax.annotation.Nonnull;
-import javax.swing.*;
/**
* Shows notifications.
*/
public interface NotificationService {
- /**
- * Shows an error notification.
- *
- * @param title the title
- * @param message the message contents
- */
- void error(final String title, final String message);
-
- /**
- * Shows an error notification.
- *
- * @param title the title
- * @param icon custom icon
- * @param message the message contents
- */
- void error(final String title, final Icon icon, final String message);
-
/**
* Gets an instance of the NotificationService.
*
@@ -44,30 +26,11 @@ public interface NotificationService {
static NotificationService getInstance(final Project project) {
return project.getService(NotificationService.class);
}
-
- /**
- * Shows an informational notification.
- *
- * @param title the title
- * @param icon custom icon to display
- * @param message the message contents
- */
- void info(final String title, final Icon icon, final String message);
-
- /**
- * Shows a warning notification.
- *
- * @param title the title
- * @param message the message contents
- */
- void warning(final String title, final String message);
-
+
/**
- * Shows a warning notification.
+ * Sends the given notification.
*
- * @param title the title
- * @param icon custom icon to display
- * @param message the message contents
+ * @param notification the notification to send
*/
- void warning(final String title, final Icon icon, final String message);
+ void send(SendableNotification notification);
}
diff --git a/src/main/java/io/github/thepieterdc/dodona/plugin/notifications/SendableNotification.java b/src/main/java/io/github/thepieterdc/dodona/plugin/notifications/SendableNotification.java
new file mode 100644
index 00000000..976059aa
--- /dev/null
+++ b/src/main/java/io/github/thepieterdc/dodona/plugin/notifications/SendableNotification.java
@@ -0,0 +1,149 @@
+package io.github.thepieterdc.dodona.plugin.notifications;
+
+import com.intellij.notification.BrowseNotificationAction;
+import com.intellij.notification.NotificationType;
+import com.intellij.openapi.actionSystem.AnAction;
+
+import javax.annotation.Nullable;
+import javax.swing.*;
+import java.util.Optional;
+
+public class SendableNotification {
+ /**
+ * A notification that contains all of the information to be sent.
+ */
+
+ // The notification information.
+ @Nullable
+ private Icon icon;
+ private final String message;
+ private final String title;
+ private final NotificationType type;
+
+ // The URL information.
+ @Nullable
+ private String linkLabel;
+ @Nullable
+ private String linkUrl;
+
+ /**
+ * SendableNotification constructor.
+ *
+ * @param title title of the balloon
+ * @param message notification contents
+ * @param type the notification type
+ */
+ private SendableNotification(final String title, final String message, final NotificationType type) {
+ this.message = message;
+ this.title = title;
+ this.type = type;
+ }
+
+ /**
+ * Creates an error notification.
+ *
+ * @param title the title of the notification balloon
+ * @param message the notification contents
+ * @return the created notification
+ */
+ public static SendableNotification error(final String title, final String message) {
+ return new SendableNotification(title, message, NotificationType.ERROR);
+ }
+
+ /**
+ * Creates an informational notification.
+ *
+ * @param title the title of the notification balloon
+ * @param message the notification contents
+ * @return the created notification
+ */
+ public static SendableNotification info(final String title, final String message) {
+ return new SendableNotification(title, message, NotificationType.WARNING);
+ }
+
+ /**
+ * Gets the action of the notification.
+ *
+ * @return the action
+ */
+ public Optional getAction() {
+ // If nothing is configured, return an empty action.
+ if (this.linkLabel == null || this.linkUrl == null) {
+ return Optional.empty();
+ }
+
+ // Return the action.
+ return Optional.of(new BrowseNotificationAction(this.linkLabel, this.linkUrl));
+ }
+
+ /**
+ * Gets the icon of the notification.
+ *
+ * @return the icon
+ */
+ public Optional getIcon() {
+ return Optional.ofNullable(this.icon);
+ }
+
+ /**
+ * Gets the message of the notification.
+ *
+ * @return the message
+ */
+ public String getMessage() {
+ return this.message;
+ }
+
+ /**
+ * Gets the title of the notification.
+ *
+ * @return the title
+ */
+ public String getTitle() {
+ return this.title;
+ }
+
+ /**
+ * Gets the type of the notification.
+ *
+ * @return the type
+ */
+ public NotificationType getType() {
+ return this.type;
+ }
+
+ /**
+ * Creates a warning notification.
+ *
+ * @param title the title of the notification balloon
+ * @param message the notification contents
+ * @return the created notification
+ */
+ public static SendableNotification warning(final String title, final String message) {
+ return new SendableNotification(title, message, NotificationType.WARNING);
+ }
+
+ /**
+ * Attaches an icon to the notification.
+ *
+ * @param icon the icon to attach
+ * @return the instance
+ */
+ public SendableNotification withIcon(final Icon icon) {
+ this.icon = icon;
+ return this;
+ }
+
+ /**
+ * Attaches a link to the notification.
+ *
+ * @param label the label of the link
+ * @param url the url of the link
+ * @return the instance
+ */
+ public SendableNotification withLink(final String label, final String url) {
+ this.linkLabel = label;
+ this.linkUrl = url;
+ return this;
+ }
+}
diff --git a/src/main/java/io/github/thepieterdc/dodona/plugin/notifications/impl/NotificationServiceImpl.java b/src/main/java/io/github/thepieterdc/dodona/plugin/notifications/impl/NotificationServiceImpl.java
index d060a140..749e450c 100644
--- a/src/main/java/io/github/thepieterdc/dodona/plugin/notifications/impl/NotificationServiceImpl.java
+++ b/src/main/java/io/github/thepieterdc/dodona/plugin/notifications/impl/NotificationServiceImpl.java
@@ -8,26 +8,23 @@
*/
package io.github.thepieterdc.dodona.plugin.notifications.impl;
+import com.intellij.notification.Notification;
import com.intellij.notification.NotificationGroup;
import com.intellij.notification.NotificationGroupManager;
-import com.intellij.notification.NotificationListener;
-import com.intellij.notification.NotificationType;
import com.intellij.openapi.project.Project;
import io.github.thepieterdc.dodona.plugin.notifications.NotificationService;
+import io.github.thepieterdc.dodona.plugin.notifications.SendableNotification;
import org.jetbrains.annotations.NonNls;
-import javax.annotation.Nullable;
-import javax.swing.*;
-
/**
* Default implementation of a NotificationService.
*/
public class NotificationServiceImpl implements NotificationService {
@NonNls
private static final String GROUP_ID = "Dodona Notifications";
-
+
private final Project project;
-
+
/**
* NotificationServiceImpl constructor.
*
@@ -36,55 +33,25 @@ public class NotificationServiceImpl implements NotificationService {
public NotificationServiceImpl(final Project project) {
this.project = project;
}
-
- @Override
- public void error(final String title, final String message) {
- this.notify(NotificationType.ERROR, null, title, message);
- }
-
- @Override
- public void error(final String title, final Icon icon,
- final String message) {
- this.notify(NotificationType.ERROR, icon, title, message);
- }
-
+
@Override
- public void info(final String title, final Icon icon, final String message) {
- this.notify(NotificationType.INFORMATION, icon, title, message);
- }
-
- /**
- * Sends a notification.
- *
- * @param type the type of the notification
- * @param icon the icon
- * @param title the title
- * @param message the contents of the notification
- */
- private void notify(final NotificationType type,
- @Nullable final Icon icon,
- final String title,
- final String message) {
+ public void send(final SendableNotification notification) {
// Get the group.
final NotificationGroup group = NotificationGroupManager.getInstance()
.getNotificationGroup(GROUP_ID);
-
+
// Build the notification.
- group.createNotification(message, type)
- .setIcon(icon)
- .setListener(NotificationListener.URL_OPENING_LISTENER)
- .setTitle(title)
- .notify(this.project);
- }
-
- @Override
- public void warning(final String title, final String message) {
- this.notify(NotificationType.WARNING, null, title, message);
- }
-
- @Override
- public void warning(final String title, final Icon icon,
- final String message) {
- this.notify(NotificationType.WARNING, icon, title, message);
+ final Notification built = group
+ .createNotification(notification.getMessage(), notification.getType())
+ .setTitle(notification.getTitle());
+
+ // Add the action.
+ notification.getAction().ifPresent(built::addAction);
+
+ // Add the icon.
+ notification.getIcon().ifPresent(built::setIcon);
+
+ // Send the notification.
+ built.notify(this.project);
}
}
diff --git a/src/main/java/io/github/thepieterdc/dodona/plugin/tasks/SubmitSolutionTask.java b/src/main/java/io/github/thepieterdc/dodona/plugin/tasks/SubmitSolutionTask.java
index a1aa754d..2e727cb3 100644
--- a/src/main/java/io/github/thepieterdc/dodona/plugin/tasks/SubmitSolutionTask.java
+++ b/src/main/java/io/github/thepieterdc/dodona/plugin/tasks/SubmitSolutionTask.java
@@ -43,6 +43,9 @@
import java.util.function.Function;
import java.util.function.Supplier;
+import static io.github.thepieterdc.dodona.plugin.notifications.SendableNotification.error;
+import static io.github.thepieterdc.dodona.plugin.notifications.SendableNotification.info;
+
/**
* Submits code to Dodona.
*/
@@ -52,18 +55,18 @@ public final class SubmitSolutionTask extends AbstractDodonaBackgroundTask {
private static final long DELAY_TIMEOUT_MS = 120_000L;
private static final long DELAY_MAX_WAIT_MS = 20_000L;
private static final long DELAY_MIN_WAIT_MS = 2_000L;
-
+
private static final double PROGRESS_SUBMITTED = 0.5;
-
+
private final MessageBus bus;
-
+
private final String code;
private final Identification identification;
-
+
private final DodonaExecutorHolder executor;
private final FeedbackService feedback;
private final NotificationService notifications;
-
+
/**
* SubmitSolutionTask constructor.
*
@@ -72,8 +75,8 @@ public final class SubmitSolutionTask extends AbstractDodonaBackgroundTask {
* @param solution the solution to submit
*/
private SubmitSolutionTask(final Project project,
- final Identification exercise,
- final String solution) {
+ final Identification exercise,
+ final String solution) {
super(project, DodonaBundle.message("tasks.submit_solution.title"));
this.bus = project.getMessageBus();
this.code = solution;
@@ -82,7 +85,40 @@ private SubmitSolutionTask(final Project project,
this.identification = exercise;
this.notifications = NotificationService.getInstance(project);
}
-
+
+ /**
+ * Creates a code submission task from the given code.
+ *
+ * @param project the current project
+ * @param identification the exercise to submit the solution to
+ * @param code the code to submit
+ * @return the task
+ */
+ @Nonnull
+ public static DodonaBackgroundTask create(final Project project,
+ final Identification identification,
+ final String code) {
+ return new SubmitSolutionTask(project, identification, code);
+ }
+
+ /**
+ * Identifies the exercise and creates a code submission task from the
+ * given
+ * code.
+ *
+ * @param project the current project
+ * @param code the code to submit
+ * @return the task
+ */
+ @Nonnull
+ public static DodonaBackgroundTask create(final Project project,
+ final String code) {
+ return IdentificationService.getInstance()
+ .identify(code)
+ .map(result -> create(project, result, code))
+ .orElseThrow(UnidentifiedCodeException::new);
+ }
+
/**
* Awaits until the submission is evaluated.
*
@@ -93,103 +129,57 @@ private SubmitSolutionTask(final Project project,
*/
@Nonnull
private Submission awaitEvaluation(final ProgressIndicator progress,
- final Exercise exercise,
- final long submissionId) throws InterruptedException {
+ final Exercise exercise,
+ final long submissionId)
+ throws InterruptedException {
// Allocate the function here to prevent allocating this in the loop.
final Supplier refresh = () -> this.executor.getExecutor()
- .execute(dodona -> dodona.submissions().get(submissionId), progress);
-
+ .execute(dodona -> dodona.submissions().get(submissionId),
+ progress);
+
Submission submission = refresh.get();
-
+
boolean broadcasted = false;
-
+
// Perform exponential backoff until the solution is accepted or the
// timeout is reached.
long currentDelay = DELAY_INITIAL_MS;
long totalWaited = 0L;
- while (submission.getStatus() == SubmissionStatus.RUNNING
- || submission.getStatus() == SubmissionStatus.QUEUED) {
-
+ while (submission.getStatus() == SubmissionStatus.RUNNING ||
+ submission.getStatus() == SubmissionStatus.QUEUED) {
+
// Check for timeouts.
if (currentDelay < DELAY_MIN_WAIT_MS) {
throw new SubmissionTimeoutException(exercise, submission);
}
-
+
// Await the delay.
Thread.sleep(currentDelay);
-
+
totalWaited += currentDelay;
-
+
// Refresh the status.
submission = refresh.get();
-
+
// Broadcast the created submission.
if (!broadcasted) {
this.bus.syncPublisher(SubmissionCreatedListener.SUBMISSION_CREATED)
.onSubmissionCreated(submission);
broadcasted = true;
}
-
+
// Determine the next delay amount.
- currentDelay = Math.min(
- (long) (((double) currentDelay) * DELAY_BACKOFF_FACTOR),
- DELAY_MAX_WAIT_MS
- );
-
+ currentDelay = Math.min((long) (((double) currentDelay) *
+ DELAY_BACKOFF_FACTOR), DELAY_MAX_WAIT_MS);
+
// Ensure the delay is still within bounds.
- currentDelay = Math.min(
- DELAY_TIMEOUT_MS - totalWaited,
- currentDelay
- );
+ currentDelay =
+ Math.min(DELAY_TIMEOUT_MS - totalWaited, currentDelay);
}
-
+
return submission;
}
-
- /**
- * Creates a code submission task from the given code.
- *
- * @param project the current project
- * @param identification the exercise to submit the solution to
- * @param code the code to submit
- * @return the task
- */
- @Nonnull
- public static DodonaBackgroundTask create(final Project project,
- final Identification identification,
- final String code) {
- return new SubmitSolutionTask(project, identification, code);
- }
-
- /**
- * Identifies the exercise and creates a code submission task from the given
- * code.
- *
- * @param project the current project
- * @param code the code to submit
- * @return the task
- */
- @Nonnull
- public static DodonaBackgroundTask create(final Project project,
- final String code) {
- return IdentificationService.getInstance()
- .identify(code)
- .map(result -> create(project, result, code))
- .orElseThrow(UnidentifiedCodeException::new);
- }
-
- /**
- * Shows an error message.
- *
- * @param message the message contents
- */
- private void error(@Nls final String message) {
- this.notifications.error(
- DodonaBundle.message("tasks.submit_solution.failed"),
- message
- );
- }
-
+
/**
* Gets information about the identified exercise.
*
@@ -200,71 +190,74 @@ private void error(@Nls final String message) {
private Exercise getExercise(final ProgressIndicator progress) {
// Get the id of the exercise.
final long id = this.identification.getExerciseId();
-
+
// Get a resolver.
- final Function resolver = this.identification
- .getCourseId()
- .map(course ->
- (Function) mgr -> mgr.get(course, id)
- )
- .orElseGet(() -> mgr -> mgr.get(id));
-
+ final Function resolver =
+ this.identification.getCourseId()
+ .map(course -> (Function) mgr -> mgr.get(
+ course,
+ id))
+ .orElseGet(() -> mgr -> mgr.get(id));
+
// Execute the resolver.
- return this.executor.getExecutor().execute(
- dodona -> resolver.apply(dodona.exercises()),
- progress
- );
+ return this.executor.getExecutor()
+ .execute(dodona -> resolver.apply(dodona.exercises()), progress);
}
-
+
@Override
public void run(@NotNull final ProgressIndicator progress) {
try {
// Update the progress bar.
progress.setIndeterminate(true);
- progress.setText(
- DodonaBundle.message("tasks.submit_solution.submitting")
- );
-
+ progress.setText(DodonaBundle.message(
+ "tasks.submit_solution.submitting"));
+
// Submit the solution and get the id of the submission.
- final long id = this.executor.getExecutor().execute(this::submit, progress);
-
+ final long id =
+ this.executor.getExecutor().execute(this::submit, progress);
+
// Get information about the exercise.
final Exercise exercise = this.getExercise(progress);
-
+
// Update the progress bar.
progress.setIndeterminate(false);
progress.setFraction(PROGRESS_SUBMITTED);
- progress.setText(DodonaBundle.message("tasks.submit_solution.evaluating"));
-
- this.notifications.info(
- DodonaBundle.message("tasks.submit_solution.submitted.title"),
- SubmissionStatusIcon.QUEUED,
- DodonaBundle.message("tasks.submit_solution.submitted.message")
- );
-
+ progress.setText(DodonaBundle.message(
+ "tasks.submit_solution.evaluating"));
+
+ // Send the notification.
+ this.notifications.send(info(DodonaBundle.message(
+ "tasks.submit_solution.submitted.title"),
+ DodonaBundle.message("tasks.submit_solution.submitted.message")).withIcon(
+ SubmissionStatusIcon.QUEUED));
+
// Await the evaluation.
- final Submission evaluated = this.awaitEvaluation(progress, exercise, id);
-
+ final Submission evaluated =
+ this.awaitEvaluation(progress, exercise, id);
+
// Broadcast the evaluation result.
this.bus.syncPublisher(SubmissionEvaluatedListener.SUBMISSION_EVALUATED)
.onSubmissionEvaluated(evaluated);
-
+
// Update the progess bar.
progress.setFraction(1.0);
- progress.setText(DodonaBundle.message("tasks.submit_solution.evaluated"));
-
+ progress.setText(DodonaBundle.message(
+ "tasks.submit_solution.evaluated"));
+
// Provide feedback to the user.
this.feedback.notify(exercise, evaluated.getInfo());
} catch (final AuthenticationException ex) {
- this.error(DodonaBundle.message("tasks.submit_solution.error.auth"));
- ApplicationManager.getApplication().invokeLater(() ->
- DodonaAuthenticator.getInstance()
- .requestAuthentication(this.myProject, null)
- );
+ this.showError(DodonaBundle.message(
+ "tasks.submit_solution.error.auth"));
+ ApplicationManager.getApplication()
+ .invokeLater(() -> DodonaAuthenticator.getInstance()
+ .requestAuthentication(this.myProject, null));
} catch (final ActivityAccessDeniedException ex) {
- this.error(DodonaBundle.message("tasks.submit_solution.error.forbidden"));
+ this.showError(DodonaBundle.message(
+ "tasks.submit_solution.error.forbidden"));
} catch (final ActivityNotFoundException ex) {
- this.error(DodonaBundle.message("tasks.submit_solution.error.notfound"));
+ this.showError(DodonaBundle.message(
+ "tasks.submit_solution.error.notfound"));
} catch (final InterruptedException ex) {
throw new CancelledException();
} catch (final RuntimeException ex) {
@@ -273,13 +266,24 @@ public void run(@NotNull final ProgressIndicator progress) {
throw ex.getCause();
}
} catch (final IOException exx) {
- this.error(DodonaBundle.message("tasks.submit_solution.error.unreachable"));
+ this.showError(DodonaBundle.message(
+ "tasks.submit_solution.error.unreachable"));
} catch (final Throwable exx) {
ErrorReporter.report(ex);
}
}
}
-
+
+ /**
+ * Shows an error message.
+ *
+ * @param message the message contents
+ */
+ private void showError(@Nls final String message) {
+ this.notifications.send(error(DodonaBundle.message(
+ "tasks.submit_solution.failed"), message));
+ }
+
/**
* Submits the solution.
*
@@ -287,11 +291,10 @@ public void run(@NotNull final ProgressIndicator progress) {
* @return the submission id
*/
private long submit(final DodonaClient client) {
- return client.submissions().create(
- this.identification.getCourseId().orElse(null),
- this.identification.getSeriesId().orElse(null),
- this.identification.getExerciseId(),
- this.code
- );
+ return client.submissions()
+ .create(this.identification.getCourseId().orElse(null),
+ this.identification.getSeriesId().orElse(null),
+ this.identification.getExerciseId(),
+ this.code);
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/io/github/thepieterdc/dodona/plugin/toolwindow/ui/deadlines/DeadlinesPanel.java b/src/main/java/io/github/thepieterdc/dodona/plugin/toolwindow/ui/deadlines/DeadlinesPanel.java
index f0957ae3..cb2bd828 100644
--- a/src/main/java/io/github/thepieterdc/dodona/plugin/toolwindow/ui/deadlines/DeadlinesPanel.java
+++ b/src/main/java/io/github/thepieterdc/dodona/plugin/toolwindow/ui/deadlines/DeadlinesPanel.java
@@ -32,18 +32,18 @@
/**
* UI for the Deadlines tab.
*/
-public final class DeadlinesPanel extends StaticAsyncPanel, DeadlinesList> {
+public final class DeadlinesPanel
+ extends StaticAsyncPanel, DeadlinesList> {
@NonNls
private static final String CARD_NONE = "DEADLINES_NONE";
-
- private static final JComponent ICON_NONE = Icons.toComponent(
- Icons.DEADLINES_CHECK.color(TextColors.SECONDARY)
- );
-
+
+ private static final JComponent ICON_NONE =
+ Icons.toComponent(Icons.DEADLINES_CHECK.color(TextColors.SECONDARY));
+
private final DeadlinesList list;
-
+
private final DodonaExecutorHolder executor;
-
+
/**
* DeadlinesPanel constructor.
*
@@ -51,48 +51,50 @@ public final class DeadlinesPanel extends StaticAsyncPanel, Deadl
* @param executor request executor holder
*/
public DeadlinesPanel(final Project project,
- final DodonaExecutorHolder executor) {
+ final DodonaExecutorHolder executor) {
super(project, DodonaBundle.message("toolwindow.deadlines.loading"));
this.executor = executor;
this.list = new DeadlinesList();
this.setBorder(BorderFactory.createEmptyBorder());
}
-
+
@Nonnull
@Override
protected DeadlinesList createContentPane() {
return this.list;
}
-
+
@Nonnull
@Override
protected CompletableFuture> getData() {
- return this.executor.getExecutor().execute(DodonaClient::root).thenApply(root -> {
- // Create a map of the course ids to their names.
- final Map courseNames = root.getUser()
- .getSubscribedCourses()
- .stream()
- .collect(Collectors.toMap(Course::getId, Course::getName));
-
- // Create a deadline object for every deadline.
- return root.getDeadlineSeries().stream()
- .map(series -> Deadline.parse(courseNames, series))
- .sorted()
- .collect(Collectors.toList());
- });
+ return this.executor.getExecutor()
+ .execute(DodonaClient::root)
+ .thenApply(root -> {
+ // Create a map of the course ids to their names.
+ final Map courseNames = root.getUser()
+ .getSubscribedCourses()
+ .stream()
+ .collect(Collectors.toMap(Course::getId, Course::getName));
+
+ // Create a deadline object for every deadline.
+ return root.getDeadlineSeries()
+ .stream()
+ .map(series -> Deadline.parse(courseNames, series))
+ .sorted()
+ .collect(Collectors.toList());
+ });
}
-
+
@Override
protected void initialize(@Nls final String loadingText) {
super.initialize(loadingText);
-
+
// Create a card that is shown when no deadlines were found.
- this.add(
- CARD_NONE,
- new IconTextCard(ICON_NONE, DodonaBundle.message("toolwindow.deadlines.none")).wrap()
- );
+ this.add(CARD_NONE,
+ new IconTextCard(ICON_NONE,
+ DodonaBundle.message("toolwindow.deadlines.none")).wrap());
}
-
+
@Override
protected void showContentCard() {
if (this.list.listSize() > 0) {
diff --git a/src/main/resources/messages/Dodona.properties b/src/main/resources/messages/Dodona.properties
index d5c6d8cf..ff0f8810 100644
--- a/src/main/resources/messages/Dodona.properties
+++ b/src/main/resources/messages/Dodona.properties
@@ -45,21 +45,28 @@ exceptions.error.unidentified_code=The current exercise could not be identified.
exceptions.cancelled=Operation cancelled.
exceptions.error.current_file_read=The current file could not be accessed.
exceptions.warnings.submission_timeout=Your solution to "{0}" took longer than expected to evalute. View results
-feedback.compile_error.message=Your solution to "{0}" could not be compiled and contains syntax errors. View results
+feedback.compile_error.link_label=View results
+feedback.compile_error.message=Your solution to "{0}" could not be compiled and contains syntax errors.
feedback.compile_error.title=Compilation error
-feedback.correct.message=Your solution to "{0}" has been accepted! View results
+feedback.correct.link_label=View results
+feedback.correct.message=Your solution to "{0}" has been accepted!
feedback.correct.title=Correct solution
feedback.internal_error.message=Something went wrong while evaluating your solution to "{0}". The Dodona team has been notified.
feedback.internal_error.title=Internal error
-feedback.memory_limit.message=Your solution to "{0}" exceeded the allowed memory usage. Try to optimise your allocations and data structures. View results
+feedback.memory_limit.link_label=View results
+feedback.memory_limit.message=Your solution to "{0}" exceeded the allowed memory usage. Try to optimise your allocations and data structures.
feedback.memory_limit.title=Memory limit exceeded
-feedback.output_limit.message=Your solution to "{0}" generated too much output. View results
+feedback.output_limit.link_label=View results
+feedback.output_limit.message=Your solution to "{0}" generated too much output.
feedback.output_limit.title=Output limit exceeded
-feedback.runtime_error.message=A runtime error has occurred while evaluating your solution to "{0}". View results
+feedback.runtime_error.link_label=View results
+feedback.runtime_error.message=A runtime error has occurred while evaluating your solution to "{0}".
feedback.runtime_error.title=Runtime error
-feedback.time_limit.message=Your solution to "{0}" was too slow and has exceeded the allowed time limit. Try to optimise your code and reduce its computational complexity. View results
+feedback.time_limit.link_label=View results
+feedback.time_limit.message=Your solution to "{0}" was too slow and has exceeded the allowed time limit. Try to optimise your code and reduce its computational complexity.
feedback.time_limit.title=Time limit exceeded
-feedback.wrong.message=Your solution to "{0}" was not correct: {1}. View results
+feedback.wrong.link_label=View results
+feedback.wrong.message=Your solution to "{0}" was not correct: {1}.
feedback.wrong.title=Incorrect solution
module.course=Course
module.course.blank=Select a course from the list.