From 4e9d785e20b6e65b32a25a46158400fdb39a6480 Mon Sep 17 00:00:00 2001 From: "Liana.Bakradze" Date: Mon, 10 Apr 2017 20:00:11 +0300 Subject: [PATCH] remote check (EDU-882) --- .../resources/META-INF/plugin.xml | 1 + .../learning/actions/StudyCheckAction.java | 76 +++++++++++++++++-- .../learning/checker/StudyCheckResult.java | 21 +++++ .../learning/checker/StudyTaskChecker.java | 14 ++++ .../courseFormat/tasks/ChoiceTask.java | 8 ++ .../learning/courseFormat/tasks/CodeTask.java | 8 ++ .../courseFormat/tasks/TheoryTask.java | 24 ++++++ .../stepic/EduAdaptiveStepicConnector.java | 31 ++++---- .../EduNextRecommendationCheckListener.java | 5 +- 9 files changed, 166 insertions(+), 22 deletions(-) create mode 100644 python/educational-core/src/com/jetbrains/edu/learning/checker/StudyCheckResult.java diff --git a/python/educational-core/resources/META-INF/plugin.xml b/python/educational-core/resources/META-INF/plugin.xml index 7745070d43b6..370643525c1d 100644 --- a/python/educational-core/resources/META-INF/plugin.xml +++ b/python/educational-core/resources/META-INF/plugin.xml @@ -55,6 +55,7 @@ + diff --git a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyCheckAction.java b/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyCheckAction.java index abf32c6c97e5..f8d160ee8891 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyCheckAction.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/actions/StudyCheckAction.java @@ -1,5 +1,6 @@ package com.jetbrains.edu.learning.actions; +import com.intellij.ide.projectView.ProjectView; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.KeyboardShortcut; import com.intellij.openapi.actionSystem.Presentation; @@ -8,25 +9,37 @@ import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.keymap.KeymapUtil; +import com.intellij.openapi.progress.PerformInBackgroundOption; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.util.Ref; import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.learning.checker.StudyCheckListener; +import com.jetbrains.edu.learning.StudySettings; import com.jetbrains.edu.learning.StudyUtils; +import com.jetbrains.edu.learning.checker.StudyCheckListener; +import com.jetbrains.edu.learning.checker.StudyCheckResult; import com.jetbrains.edu.learning.checker.StudyCheckUtils; +import com.jetbrains.edu.learning.checker.StudyTaskChecker; +import com.jetbrains.edu.learning.courseFormat.RemoteCourse; +import com.jetbrains.edu.learning.courseFormat.StudyStatus; import com.jetbrains.edu.learning.courseFormat.tasks.Task; import com.jetbrains.edu.learning.courseFormat.tasks.TheoryTask; import com.jetbrains.edu.learning.editor.StudyEditor; +import com.jetbrains.edu.learning.statistics.EduUsagesCollector; +import com.jetbrains.edu.learning.stepic.StepicUser; import icons.EducationalCoreIcons; import org.jetbrains.annotations.NotNull; import javax.swing.*; -public abstract class StudyCheckAction extends StudyActionWithShortcut { +public class StudyCheckAction extends StudyActionWithShortcut { public static final String SHORTCUT = "ctrl alt pressed ENTER"; + private static final String ACTION_ID = "Edu.Check"; private static final String TEXT = "Check Task"; + public static final String FAILED_CHECK_LAUNCH = "Failed to launch checking"; protected final Ref myCheckInProgress = new Ref<>(false); @@ -40,10 +53,9 @@ public abstract class StudyCheckAction extends StudyActionWithShortcut { return text + "(" + KeymapUtil.getShortcutText(new KeyboardShortcut(KeyStroke.getKeyStroke(SHORTCUT), null)) + ")"; } - public abstract void check(@NotNull final Project project); - @Override public void actionPerformed(@NotNull AnActionEvent e) { + EduUsagesCollector.taskChecked(); Project project = e.getProject(); if (project == null) { return; @@ -69,7 +81,55 @@ public abstract class StudyCheckAction extends StudyActionWithShortcut { for (StudyCheckListener listener : Extensions.getExtensions(StudyCheckListener.EP_NAME)) { listener.beforeCheck(project, task); } - check(project); + StudyTaskChecker checker = task.getChecker(project); + boolean isRemote = task.getLesson().getCourse() instanceof RemoteCourse; + ProgressManager.getInstance() + .run(new com.intellij.openapi.progress.Task.Backgroundable(project, "Checking Task", true, PerformInBackgroundOption.DEAF) { + private StudyCheckResult myResult; + + @Override + public void onSuccess() { + String message = myResult.getMessage(); + StudyStatus status = myResult.getStatus(); + task.setStatus(status); + switch (status) { + case Failed: + checker.onTaskFailed(message); + break; + case Solved: + checker.onTaskSolved(message); + break; + default: + StudyCheckUtils.showTestResultPopUp(message, MessageType.WARNING.getPopupBackground(), project); + } + StudyUtils.updateToolWindows(myProject); + ProjectView.getInstance(myProject).refresh(); + + for (StudyCheckListener listener : StudyCheckListener.EP_NAME.getExtensions()) { + listener.afterCheck(project, task); + } + checker.clearState(); + } + + @Override + public void onCancel() { + checker.clearState(); + } + + @Override + public void run(@NotNull ProgressIndicator indicator) { + indicator.setIndeterminate(true); + myResult = isRemote ? checkOnRemote() : checker.check(); + } + + private StudyCheckResult checkOnRemote() { + final StepicUser user = StudySettings.getInstance().getUser(); + if (user == null) { + return new StudyCheckResult(StudyStatus.Unchecked, "Failed to launch checking: you're not authorized"); + } + return checker.checkOnRemote(user); + } + }); } @Override @@ -99,6 +159,12 @@ public abstract class StudyCheckAction extends StudyActionWithShortcut { } } + @NotNull + @Override + public String getActionId() { + return ACTION_ID; + } + @Override public String[] getShortcuts() { return new String[]{SHORTCUT}; diff --git a/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyCheckResult.java b/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyCheckResult.java new file mode 100644 index 000000000000..3c1c5f62c942 --- /dev/null +++ b/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyCheckResult.java @@ -0,0 +1,21 @@ +package com.jetbrains.edu.learning.checker; + +import com.jetbrains.edu.learning.courseFormat.StudyStatus; + +public class StudyCheckResult { + private StudyStatus myStatus; + private String myMessage; + + public StudyCheckResult(StudyStatus status, String message) { + myStatus = status; + myMessage = message; + } + + public StudyStatus getStatus() { + return myStatus; + } + + public String getMessage() { + return myMessage; + } +} diff --git a/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyTaskChecker.java b/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyTaskChecker.java index 879621a1a725..7fcd1715b2d3 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyTaskChecker.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/checker/StudyTaskChecker.java @@ -3,7 +3,9 @@ package com.jetbrains.edu.learning.checker; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.MessageType; +import com.jetbrains.edu.learning.courseFormat.StudyStatus; import com.jetbrains.edu.learning.courseFormat.tasks.Task; +import com.jetbrains.edu.learning.stepic.StepicUser; import org.jetbrains.annotations.NotNull; public class StudyTaskChecker { @@ -24,4 +26,16 @@ public class StudyTaskChecker { ApplicationManager.getApplication() .invokeLater(() -> StudyCheckUtils.showTestResultPopUp(message, MessageType.ERROR.getPopupBackground(), myProject)); } + + public StudyCheckResult check() { + return new StudyCheckResult(StudyStatus.Unchecked, "Check for " + myTask.getTaskType() + " task isn't available"); + } + + public StudyCheckResult checkOnRemote(@NotNull StepicUser user) { + return new StudyCheckResult(StudyStatus.Unchecked, "Remote check for " + myTask.getTaskType() + " task isn't available"); + } + + public void clearState() { + + } } diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/ChoiceTask.java b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/ChoiceTask.java index 476af7826116..bc4804f56973 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/ChoiceTask.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/ChoiceTask.java @@ -4,8 +4,11 @@ import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; import com.intellij.openapi.project.Project; import com.jetbrains.edu.learning.StudyUtils; +import com.jetbrains.edu.learning.checker.StudyCheckResult; import com.jetbrains.edu.learning.checker.StudyTaskChecker; import com.jetbrains.edu.learning.editor.StudyChoiceVariantsPanel; +import com.jetbrains.edu.learning.stepic.EduAdaptiveStepicConnector; +import com.jetbrains.edu.learning.stepic.StepicUser; import com.jetbrains.edu.learning.ui.StudyToolWindow; import org.jetbrains.annotations.NotNull; @@ -57,6 +60,11 @@ public class ChoiceTask extends Task { @Override public StudyTaskChecker getChecker(@NotNull Project project) { return new StudyTaskChecker(this, project) { + @Override + public StudyCheckResult checkOnRemote(@NotNull StepicUser user) { + return EduAdaptiveStepicConnector.checkChoiceTask(myProject, myTask, user); + } + @Override public void onTaskFailed(@NotNull String message) { super.onTaskFailed(message); diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/CodeTask.java b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/CodeTask.java index b82c478c43d9..cf1595a561e7 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/CodeTask.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/CodeTask.java @@ -1,8 +1,11 @@ package com.jetbrains.edu.learning.courseFormat.tasks; import com.intellij.openapi.project.Project; +import com.jetbrains.edu.learning.checker.StudyCheckResult; import com.jetbrains.edu.learning.checker.StudyCheckUtils; import com.jetbrains.edu.learning.checker.StudyTaskChecker; +import com.jetbrains.edu.learning.stepic.EduAdaptiveStepicConnector; +import com.jetbrains.edu.learning.stepic.StepicUser; import org.jetbrains.annotations.NotNull; public class CodeTask extends Task { @@ -26,6 +29,11 @@ public class CodeTask extends Task { super.onTaskFailed("Wrong solution"); StudyCheckUtils.showTestResultsToolWindow(myProject, message); } + + @Override + public StudyCheckResult checkOnRemote(@NotNull StepicUser user) { + return EduAdaptiveStepicConnector.checkCodeTask(myProject, myTask, user); + } }; } } diff --git a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/TheoryTask.java b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/TheoryTask.java index 4d0d6b0071ab..db60c2c6b3a9 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/TheoryTask.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/courseFormat/tasks/TheoryTask.java @@ -1,5 +1,10 @@ package com.jetbrains.edu.learning.courseFormat.tasks; +import com.intellij.openapi.project.Project; +import com.jetbrains.edu.learning.checker.StudyCheckResult; +import com.jetbrains.edu.learning.checker.StudyTaskChecker; +import com.jetbrains.edu.learning.courseFormat.StudyStatus; +import com.jetbrains.edu.learning.stepic.StepicUser; import org.jetbrains.annotations.NotNull; public class TheoryTask extends Task { @@ -14,4 +19,23 @@ public class TheoryTask extends Task { public String getTaskType() { return "theory"; } + + @Override + public StudyTaskChecker getChecker(@NotNull Project project) { + return new StudyTaskChecker(this, project) { + @Override + public void onTaskSolved(@NotNull String message) { + } + + @Override + public StudyCheckResult check() { + return new StudyCheckResult(StudyStatus.Solved, ""); + } + + @Override + public StudyCheckResult checkOnRemote(@NotNull StepicUser user) { + return new StudyCheckResult(StudyStatus.Solved, ""); + } + }; + } } diff --git a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java b/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java index 28e68911c615..2a6c9ac41d70 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java @@ -13,13 +13,14 @@ import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.ModuleRootManager; -import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import com.jetbrains.edu.learning.StudySettings; import com.jetbrains.edu.learning.StudyTaskManager; import com.jetbrains.edu.learning.StudyUtils; +import com.jetbrains.edu.learning.actions.StudyCheckAction; +import com.jetbrains.edu.learning.checker.StudyCheckResult; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.courseFormat.*; import com.jetbrains.edu.learning.courseFormat.tasks.ChoiceTask; @@ -475,8 +476,8 @@ public class EduAdaptiveStepicConnector { return null; } - public static Pair checkChoiceTask(@NotNull Project project, @NotNull ChoiceTask task, @NotNull StepicUser user) { - if (task.getSelectedVariants().isEmpty()) return Pair.create(null, "No variants selected"); + public static StudyCheckResult checkChoiceTask(@NotNull Project project, @NotNull ChoiceTask task, @NotNull StepicUser user) { + if (task.getSelectedVariants().isEmpty()) return new StudyCheckResult(StudyStatus.Failed, "No variants selected"); final StepicWrappers.AdaptiveAttemptWrapper.Attempt attempt = getAttemptForStep(task.getStepId(), user.getId()); if (attempt != null) { @@ -484,12 +485,11 @@ public class EduAdaptiveStepicConnector { final boolean isActiveAttempt = task.getSelectedVariants().stream() .allMatch(index -> attempt.dataset.options.get(index).equals(task.getChoiceVariants().get(index))); - if (!isActiveAttempt) return Pair.create(false, "Your solution is out of date. Please try again"); + if (!isActiveAttempt) return new StudyCheckResult(StudyStatus.Failed, "Your solution is out of date. Please try again"); final StepicWrappers.SubmissionToPostWrapper wrapper = new StepicWrappers.SubmissionToPostWrapper(String.valueOf(attemptId), createChoiceTaskAnswerArray(task)); - final Pair pair = doAdaptiveCheck(wrapper, attemptId, user.getId()); - Boolean result = pair.getFirst(); - if (result != null && !result) { + final StudyCheckResult result = doAdaptiveCheck(wrapper, attemptId, user.getId()); + if (result.getStatus() == StudyStatus.Failed) { try { createNewAttempt(task.getStepId()); final Task updatedTask = getTask(project, task.getName(), task.getStepId(), user.getId()); @@ -503,10 +503,10 @@ public class EduAdaptiveStepicConnector { LOG.warn(e.getMessage()); } } - return pair; + return result; } - return new Pair<>(null, "Failed to launch checking"); + return new StudyCheckResult(StudyStatus.Unchecked, StudyCheckAction.FAILED_CHECK_LAUNCH); } private static boolean[] createChoiceTaskAnswerArray(@NotNull ChoiceTask task) { @@ -518,7 +518,7 @@ public class EduAdaptiveStepicConnector { return answer; } - public static Pair checkCodeTask(@NotNull Project project, @NotNull Task task, @NotNull StepicUser user) { + public static StudyCheckResult checkCodeTask(@NotNull Project project, @NotNull Task task, @NotNull StepicUser user) { int attemptId = -1; try { attemptId = getAttemptId(task); @@ -539,10 +539,10 @@ public class EduAdaptiveStepicConnector { else { LOG.warn("Got an incorrect attempt id: " + attemptId); } - return Pair.create(null, "Failed to launch checking"); + return new StudyCheckResult(StudyStatus.Unchecked, StudyCheckAction.FAILED_CHECK_LAUNCH); } - private static Pair doAdaptiveCheck(@NotNull StepicWrappers.SubmissionToPostWrapper submission, + private static StudyCheckResult doAdaptiveCheck(@NotNull StepicWrappers.SubmissionToPostWrapper submission, int attemptId, int userId) { final CloseableHttpClient client = EduStepicAuthorizedClient.getHttpClient(); if (client != null) { @@ -553,7 +553,7 @@ public class EduAdaptiveStepicConnector { final String status = wrapper.submissions[0].status; final String hint = wrapper.submissions[0].hint; final boolean isSolved = !status.equals("wrong"); - return Pair.create(isSolved, hint.isEmpty() ? StringUtil.capitalize(status) + " solution" : hint); + return new StudyCheckResult(isSolved ? StudyStatus.Solved : StudyStatus.Failed, hint.isEmpty() ? StringUtil.capitalize(status) + " solution" : hint); } else { LOG.warn("Got a submission wrapper with incorrect submissions number: " + wrapper.submissions.length); @@ -561,11 +561,10 @@ public class EduAdaptiveStepicConnector { } else { LOG.warn("Can't do adaptive check: " + "wrapper is null"); - return Pair.create(null, "Can't get check results for Stepik"); + return new StudyCheckResult(StudyStatus.Unchecked, "Can't get check results for Stepik"); } } - - return Pair.create(null, "Failed to launch checking"); + return new StudyCheckResult(StudyStatus.Unchecked, StudyCheckAction.FAILED_CHECK_LAUNCH); } @Nullable diff --git a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduNextRecommendationCheckListener.java b/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduNextRecommendationCheckListener.java index f1c7ef7677b5..0e0034e651df 100644 --- a/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduNextRecommendationCheckListener.java +++ b/python/educational-core/src/com/jetbrains/edu/learning/stepic/EduNextRecommendationCheckListener.java @@ -1,5 +1,6 @@ package com.jetbrains.edu.learning.stepic; +import com.intellij.openapi.progress.PerformInBackgroundOption; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; @@ -32,9 +33,11 @@ public class EduNextRecommendationCheckListener implements StudyCheckListener { if (statusAfterCheck != StudyStatus.Solved) { return; } - ProgressManager.getInstance().run(new com.intellij.openapi.progress.Task.Backgroundable(project, EduAdaptiveStepicConnector.LOADING_NEXT_RECOMMENDATION) { + ProgressManager.getInstance().run(new com.intellij.openapi.progress.Task.Backgroundable(project, EduAdaptiveStepicConnector.LOADING_NEXT_RECOMMENDATION, false, + PerformInBackgroundOption.DEAF) { @Override public void run(@NotNull ProgressIndicator indicator) { + indicator.setIndeterminate(true); EduAdaptiveStepicConnector.addNextRecommendedTask(project, task.getLesson(), indicator, EduAdaptiveStepicConnector.NEXT_RECOMMENDATION_REACTION); } });