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.