From 91b1837fb76d846b819af3112adfe077c8864421 Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Thu, 7 Apr 2016 14:17:09 +0300 Subject: [PATCH 01/44] Add initial support for stepic adaptive courses --- .../edu/learning/checker/StudyCheckTask.java | 58 ++- .../edu/learning/courseFormat/Course.java | 39 +- .../StudyProjectGenerator.java | 14 +- .../edu/learning/stepic/CourseInfo.java | 30 +- .../stepic/EduAdaptiveStepicConnector.java | 291 +++++++++++++ .../learning/stepic/EduStepicConnector.java | 404 ++++-------------- .../edu/learning/stepic/StepicUser.java | 71 +++ .../edu/learning/stepic/StepicWrappers.java | 375 ++++++++++++++++ .../edu/learning/stepic/StudySettings.java | 11 + .../edu/learning/ui/StudyToolWindow.java | 79 +++- .../edu/learning/PyStudyCheckAction.java | 5 +- 11 files changed, 1021 insertions(+), 356 deletions(-) create mode 100644 python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java create mode 100644 python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicUser.java create mode 100644 python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java index f8302304b9b0..4db6ae231823 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java @@ -6,8 +6,10 @@ import com.intellij.ide.projectView.ProjectView; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.MessageType; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; @@ -17,8 +19,10 @@ import com.jetbrains.edu.learning.StudyTaskManager; import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.actions.StudyAfterCheckAction; import com.jetbrains.edu.learning.core.EduUtils; +import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseFormat.StudyStatus; import com.jetbrains.edu.learning.courseFormat.Task; +import com.jetbrains.edu.learning.stepic.EduAdaptiveStepicConnector; import com.jetbrains.edu.learning.stepic.EduStepicConnector; import com.jetbrains.edu.learning.stepic.StudySettings; import org.jetbrains.annotations.NotNull; @@ -70,12 +74,23 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun @Override public void run(@NotNull ProgressIndicator indicator) { + final Course course = StudyTaskManager.getInstance(myProject).getCourse(); + if (course != null && course.isAdaptive()) { + checkAdaptiveCourse(indicator); + } + else { + if (checkCourse(indicator)) return; + } + runAfterTaskSolvedActions(); + } + + private boolean checkCourse(@NotNull ProgressIndicator indicator) { final CapturingProcessHandler handler = new CapturingProcessHandler(myTestProcess, null, myCommandLine); final ProcessOutput output = handler.runProcessWithProgressIndicator(indicator); if (indicator.isCanceled()) { ApplicationManager.getApplication().invokeLater( () -> StudyCheckUtils.showTestResultPopUp("Check cancelled", MessageType.WARNING.getPopupBackground(), myProject)); - return; + return true; } @@ -88,31 +103,56 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun myProject)); //log error output of tests LOG.info("#educational " + stderr); - return; + return true; } postAttemptToStepic(testsOutput); if (testsOutput.isSuccess()) { - onTaskSolved(testsOutput); + onTaskSolved(testsOutput.getMessage()); } else { - onTaskFailed(testsOutput); + onTaskFailed(testsOutput.getMessage()); } - runAfterTaskSolvedActions(); + return false; } - protected void onTaskFailed(StudyTestsOutputParser.TestsOutput testsOutput) { + private void checkAdaptiveCourse(ProgressIndicator indicator) { + ProgressManager.getInstance().runProcessWithProgressAsynchronously(new Backgroundable(myProject, "Checking Task") { + @Override + public void run(@NotNull ProgressIndicator indicator) { + final Pair pair = EduAdaptiveStepicConnector.checkTask(myTask, myProject); + if (pair != null) { + final String checkMessage = pair.getSecond(); + if (pair.getFirst()) { + onTaskSolved(checkMessage); + } + else { + onTaskFailed(checkMessage); + } + } + else { + ApplicationManager.getApplication().invokeLater(() -> + StudyCheckUtils.showTestResultPopUp("Failed to launch checking", + MessageType.WARNING.getPopupBackground(), + myProject)); + } + } + }, indicator); + + } + + protected void onTaskFailed( String message) { myTaskManger.setStatus(myTask, StudyStatus.Failed); ApplicationManager.getApplication().invokeLater( - () -> StudyCheckUtils.showTestResultPopUp(testsOutput.getMessage(), MessageType.ERROR.getPopupBackground(), myProject)); + () -> StudyCheckUtils.showTestResultPopUp(message, MessageType.ERROR.getPopupBackground(), myProject)); } - protected void onTaskSolved(StudyTestsOutputParser.TestsOutput testsOutput) { + protected void onTaskSolved(String message) { myTaskManger.setStatus(myTask, StudyStatus.Solved); ApplicationManager.getApplication().invokeLater( - () -> StudyCheckUtils.showTestResultPopUp(testsOutput.getMessage(), MessageType.INFO.getPopupBackground(), myProject)); + () -> StudyCheckUtils.showTestResultPopUp(message, MessageType.INFO.getPopupBackground(), myProject)); } private void runAfterTaskSolvedActions() { diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Course.java b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Course.java index 5dfbebf0f609..93a14fd9c045 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Course.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Course.java @@ -7,7 +7,7 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.Function; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.core.EduUtils; -import com.jetbrains.edu.learning.stepic.CourseInfo; +import com.jetbrains.edu.learning.stepic.StepicUser; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; @@ -20,13 +20,18 @@ public class Course { @Expose private String description; @Expose private String name; private String myCourseDirectory = ""; - @Expose private List authors = new ArrayList(); + @Expose private List authors = new ArrayList(); private boolean myUpToDate; @Expose @SerializedName("language") private String myLanguage="Python"; private String courseType="PyCharm"; + + @Expose private boolean isAdaptive; + + @Expose + private int id; /** * Initializes state of course @@ -67,25 +72,25 @@ public class Course { } @NotNull - public List getAuthors() { + public List getAuthors() { return authors; } - public static String getAuthorsString(@NotNull List authors) { - return StringUtil.join(authors, new Function() { + public static String getAuthorsString(@NotNull List authors) { + return StringUtil.join(authors, new Function() { @Override - public String fun(CourseInfo.Author author) { + public String fun(StepicUser author) { return author.getName(); } }, ", "); } public void setAuthors(String[] authors) { - this.authors = new ArrayList(); + this.authors = new ArrayList(); for (String name : authors) { final List pair = StringUtil.split(name, " "); if (!pair.isEmpty()) - this.authors.add(new CourseInfo.Author(pair.get(0), pair.size() > 1 ? pair.get(1) : "")); + this.authors.add(new StepicUser(pair.get(0), pair.size() > 1 ? pair.get(1) : "")); } } @@ -133,7 +138,7 @@ public class Course { myLanguage = language; } - public void setAuthors(List authors) { + public void setAuthors(List authors) { this.authors = authors; } @@ -145,4 +150,20 @@ public class Course { public void setCourseType(String courseType) { this.courseType = courseType; } + + public boolean isAdaptive() { + return isAdaptive; + } + + public void setAdaptive(boolean adaptive) { + isAdaptive = adaptive; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java b/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java index 908a0e215948..157c6a6859c7 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java @@ -22,17 +22,18 @@ import com.intellij.platform.templates.github.ZipUtil; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.util.containers.ContainerUtil; +import com.jetbrains.edu.learning.StudyProjectComponent; +import com.jetbrains.edu.learning.StudyTaskManager; +import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.core.EduUtils; import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseFormat.Lesson; import com.jetbrains.edu.learning.courseFormat.Task; import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.StudyProjectComponent; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.stepic.CourseInfo; import com.jetbrains.edu.learning.stepic.EduStepicConnector; +import com.jetbrains.edu.learning.stepic.StepicUser; import org.apache.commons.codec.binary.Base64; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -466,10 +467,13 @@ public class StudyProjectGenerator { courseInfo.setName(courseName); courseInfo.setDescription(courseDescription); courseInfo.setType("pycharm " + language); - final ArrayList authors = new ArrayList<>(); + final ArrayList authors = new ArrayList<>(); for (JsonElement author : courseAuthors) { final JsonObject authorAsJsonObject = author.getAsJsonObject(); - authors.add(new CourseInfo.Author(authorAsJsonObject.get("first_name").getAsString(), authorAsJsonObject.get("last_name").getAsString())); + final StepicUser stepicUser = new StepicUser(); + stepicUser.setFirst_name(authorAsJsonObject.get("first_name").getAsString()); + stepicUser.setLast_name(authorAsJsonObject.get("last_name").getAsString()); + authors.add(stepicUser); } courseInfo.setAuthors(authors); } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/CourseInfo.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/CourseInfo.java index 2bc2099924d9..a0ab35497ed8 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/CourseInfo.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/CourseInfo.java @@ -12,20 +12,24 @@ import java.util.List; * and when project is being created */ public class CourseInfo { - boolean is_public; - public List sections; @SerializedName("title") private String myName; + @SerializedName("summary") private String myDescription; + @SerializedName("course_format") //course type in format "pycharm " private String myType = "pycharm Python"; - - List instructors = new ArrayList(); - - List myAuthors = new ArrayList(); + + @SerializedName("is_adaptive") + private boolean isAdaptive; + int id; + boolean is_public; + public List sections; + List instructors = new ArrayList(); + List myAuthors = new ArrayList<>(); public static CourseInfo INVALID_COURSE = new CourseInfo(); @@ -34,7 +38,7 @@ public class CourseInfo { } @NotNull - public List getAuthors() { + public List getAuthors() { return myAuthors; } @@ -93,18 +97,18 @@ public class CourseInfo { myName = name; } - public void setAuthors(List authors) { + public void setAuthors(List authors) { myAuthors = authors; - for (Author author : authors) { + for (StepicUser author : authors) { if (author.id > 0) { instructors.add(author.id); } } } - public void addAuthor(Author author) { + public void addAuthor(StepicUser author) { if (myAuthors == null) { - myAuthors = new ArrayList(); + myAuthors = new ArrayList<>(); } myAuthors.add(author); } @@ -116,4 +120,8 @@ public class CourseInfo { public void setType(String type) { myType = type; } + + public boolean isAdaptive() { + return isAdaptive; + } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java new file mode 100644 index 000000000000..43eb2008aef5 --- /dev/null +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java @@ -0,0 +1,291 @@ +package com.jetbrains.edu.learning.stepic; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileManager; +import com.jetbrains.edu.learning.StudyTaskManager; +import com.jetbrains.edu.learning.StudyUtils; +import com.jetbrains.edu.learning.core.EduNames; +import com.jetbrains.edu.learning.courseFormat.Course; +import com.jetbrains.edu.learning.courseFormat.Lesson; +import com.jetbrains.edu.learning.courseFormat.Task; +import com.jetbrains.edu.learning.courseFormat.TaskFile; +import com.jetbrains.edu.learning.courseGeneration.StudyGenerator; +import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator; +import com.jetbrains.edu.learning.editor.StudyEditor; +import com.jetbrains.edu.learning.ui.StudyToolWindow; +import org.apache.http.HttpEntity; +import org.apache.http.HttpStatus; +import org.apache.http.StatusLine; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.util.EntityUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.concurrent.TimeUnit; + +import static com.jetbrains.edu.learning.stepic.EduStepicConnector.getHttpClient; +import static com.jetbrains.edu.learning.stepic.EduStepicConnector.setHeaders; + +public class EduAdaptiveStepicConnector { + private static final Logger LOG = Logger.getInstance(EduAdaptiveStepicConnector.class); + private static final String STEPIC_URL = "https://stepic.org/"; + private static final String STEPIC_API_URL = STEPIC_URL + "api/"; + private static final String RECOMMENDATIONS_URL = "recommendations"; + private static final String CONTENT_TYPE_APPL_JSON = "application/json"; + private static final String LESSON_URL = "lessons/"; + private static final String RECOMMENDATION_REACTIONS_URL = "recommendation-reactions"; + private static final String LESSON_DEFAULT_NAME = "Adaptive"; + private static final String ATTEMPTS_URL = "attempts"; + private static final String SUBMISSION_URL = "submissions"; + + @Nullable + public static Task getNextRecommendation(@NotNull Course course) { + + try { + final CloseableHttpClient client = getHttpClient(); + final URI uri = new URIBuilder(STEPIC_API_URL + RECOMMENDATIONS_URL).addParameter("course", String.valueOf(course.getId())).build(); + final HttpGet request = new HttpGet(uri); + setHeaders(request, CONTENT_TYPE_APPL_JSON); + + final CloseableHttpResponse response = client.execute(request); + final StatusLine statusLine = response.getStatusLine(); + final HttpEntity responseEntity = response.getEntity(); + final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : ""; + + if (statusLine.getStatusCode() == HttpStatus.SC_OK) { + Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); + final StepicWrappers.RecommendationWrapper recomWrapper = gson.fromJson(responseString, StepicWrappers.RecommendationWrapper.class); + + if (recomWrapper.recommendations.length != 0) { + final StepicWrappers.Recommendation recommendation = recomWrapper.recommendations[0]; + final String lessonId = recommendation.lesson; + final StepicWrappers.LessonContainer + lessonContainer = EduStepicConnector.getFromStepic(LESSON_URL + lessonId, StepicWrappers.LessonContainer.class); + final Lesson realLesson = lessonContainer.lessons.get(0); + final Integer stepId = realLesson.steps.get(0); + course.getLessons().get(0).id = Integer.parseInt(lessonId); + final StepicWrappers.Step step = EduStepicConnector.getStep(stepId); + return getTaskFromStep(LESSON_DEFAULT_NAME + lessonId, stepId, step); + } + } + else { + throw new IOException("Stepic returned non 200 status code " + responseString); + } + } + catch (IOException e) { + LOG.warn(e.getMessage()); + } + catch (URISyntaxException e) { + LOG.warn(e.getMessage()); + } + return null; + } + + public static boolean postRecommendationReaction(int reaction, @NotNull final String user, @NotNull final String lessonId) { + + final HttpPost post = new HttpPost(STEPIC_API_URL + RECOMMENDATION_REACTIONS_URL); + final String json = new Gson().toJson(new StepicWrappers.RecommendationReactionWrapper(new StepicWrappers.RecommendationReaction(reaction, user, lessonId))); + post.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON)); + final CloseableHttpClient client = getHttpClient(); + setHeaders(post, CONTENT_TYPE_APPL_JSON); + try { + final CloseableHttpResponse execute = client.execute(post); + return execute.getStatusLine().getStatusCode() == HttpStatus.SC_CREATED; + } + catch (IOException e) { + LOG.warn(e.getMessage()); + } + return false; + } + + public static void addNextRecommendedTask(@NotNull Project project, int reaction) { + final StudyEditor editor = StudyUtils.getSelectedStudyEditor(project); + final Course course = StudyTaskManager.getInstance(project).getCourse(); + if (course != null && editor != null && editor.getTaskFile() != null) { + // TODO: get user from settings + final StepicUser user = StudySettings.getInstance().getUser(); + if (user != null && postRecommendationReaction(reaction, String.valueOf(user.id), String.valueOf(editor.getTaskFile().getTask().getLesson().id))) { + final Task task = getNextRecommendation(course); + + if (task != null) { + final Lesson adaptive = course.getLessons().get(0); + if (reaction == 0 || reaction == -1) { + final Task unsolvedTask = adaptive.getTaskList().get(0); + unsolvedTask.setName(task.getName()); + unsolvedTask.setStepicId(task.getStepicId()); + unsolvedTask.setText(task.getText()); + unsolvedTask.getTaskFiles().clear(); + unsolvedTask.getTaskFiles().putAll(task.getTaskFiles()); + + final File lessonDirectory = new File(course.getCourseDirectory(), EduNames.LESSON + String.valueOf(adaptive.getIndex())); + final File taskDirectory = new File(lessonDirectory, EduNames.TASK + String.valueOf(adaptive.getTaskList().size())); + StudyProjectGenerator.flushTask(task, taskDirectory); + final StudyToolWindow window = StudyUtils.getStudyToolWindow(project); + if (window != null) { + window.setTaskText(unsolvedTask.getText()); + } + + adaptive.initLesson(course, false); + VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); + } + else { + adaptive.addTask(task); + task.setIndex(adaptive.getTaskList().size()); + final VirtualFile lessonDir = project.getBaseDir().findChild(EduNames.LESSON + String.valueOf(adaptive.getIndex())); + + if (lessonDir != null) { + ApplicationManager.getApplication().runWriteAction(() -> { + try { + StudyGenerator.createTask(task, lessonDir, new File(course.getCourseDirectory(), lessonDir.getName()), project); + } + catch (IOException e) { + LOG.warn(e.getMessage()); + } + }); + } + + final File lessonDirectory = new File(course.getCourseDirectory(), EduNames.LESSON + String.valueOf(adaptive.getIndex())); + final File taskDirectory = new File(lessonDirectory, EduNames.TASK + String.valueOf(adaptive.getTaskList().size())); + StudyProjectGenerator.flushTask(task, taskDirectory); + adaptive.initLesson(course, false); + VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); + } + } + } + else { + LOG.warn("Recommendation reactions weren't posted"); + } + } + } + + @NotNull + private static Task getTaskFromStep(@NotNull String name, int lessonID, StepicWrappers.Step step) { + final Task task = new Task(); + task.setName(name); + task.setStepicId(lessonID); + task.setText(step.text); + if (step.options.test != null) { + for (StepicWrappers.TestFileWrapper wrapper : step.options.test) { + task.addTestsTexts(wrapper.name, wrapper.text); + } + } + + task.taskFiles = new HashMap(); // TODO: it looks like we don't need taskFiles as map anymore + if (step.options.files != null) { + for (TaskFile taskFile : step.options.files) { + task.taskFiles.put(taskFile.name, taskFile); + } + } + else { + final TaskFile taskFile = new TaskFile(); + taskFile.name = "code"; + taskFile.text = "# write your answer here \n"; + task.taskFiles.put("code.py", taskFile); + } + return task; + } + + @Nullable + public static Pair checkTask(@NotNull final Task task, @NotNull final Project project) { + try { + final int attemptId = getAttemptId(task, ATTEMPTS_URL); + final Editor editor = StudyUtils.getSelectedEditor(project); + String language = getLanguageString(task, project); + if (editor != null && language != null) { + final StepicWrappers.SubmissionToPostWrapper submissionToPostWrapper = + new StepicWrappers.SubmissionToPostWrapper(String.valueOf(attemptId), "python3", editor.getDocument().getText()); + final HttpPost httpPost = new HttpPost(STEPIC_API_URL + SUBMISSION_URL); + httpPost.setEntity(new StringEntity(new Gson().toJson(submissionToPostWrapper))); + final CloseableHttpClient client = getHttpClient(); + setHeaders(httpPost, CONTENT_TYPE_APPL_JSON); + final CloseableHttpResponse execute = client.execute(httpPost); + StepicWrappers.ResultSubmissionWrapper wrapper = + new Gson().fromJson(EntityUtils.toString(execute.getEntity()), StepicWrappers.ResultSubmissionWrapper.class); + + final StepicUser user = StudySettings.getInstance().getUser(); + if (user != null) { + final int id = user.getId(); + while (wrapper.submissions.length == 1 && wrapper.submissions[0].status.equals("evaluation")) { + TimeUnit.MILLISECONDS.sleep(500); + final URI submissionURI = new URIBuilder(STEPIC_API_URL + SUBMISSION_URL) + .addParameter("attempt", String.valueOf(attemptId)) + .addParameter("order", "desc") + .addParameter("user", String.valueOf(id)) + .build(); + final HttpGet httpGet = new HttpGet(submissionURI); + setHeaders(httpGet, CONTENT_TYPE_APPL_JSON); + final CloseableHttpResponse httpResponse = client.execute(httpGet); + wrapper = new Gson().fromJson(EntityUtils.toString(httpResponse.getEntity()), StepicWrappers.ResultSubmissionWrapper.class); + } + final boolean isSolved = wrapper.submissions.length == 1 && !wrapper.submissions[0].status.equals("wrong"); + return Pair.create(isSolved, wrapper.submissions[0].hint); + } + } + } + catch (ClientProtocolException e) { + LOG.warn(e.getMessage()); + } + catch (IOException e) { + LOG.warn(e.getMessage()); + } + catch (URISyntaxException e) { + LOG.warn(e.getMessage()); + } + catch (InterruptedException e) { + LOG.warn(e.getMessage()); + } + return Pair.create(false, ""); + } + + @Nullable + private static String getLanguageString(@NotNull Task task, @NotNull Project project) { + final Sdk sdk = StudyUtils.findSdk(task, project); + if (sdk != null) { + if (sdk.getVersionString() != null && sdk.getVersionString().startsWith("3")) { + return "python3"; + } + else { + return "python2"; + } + } + else { + StudyUtils.showNoSdkNotification(task, project); + } + return null; + } + + private static int getAttemptId(@NotNull Task task, String attempts) throws IOException { + final StepicWrappers.AttemptToPostWrapper attemptWrapper = new StepicWrappers.AttemptToPostWrapper(task.getStepicId()); + + final HttpPost post = new HttpPost(STEPIC_API_URL + attempts); + post.setEntity(new StringEntity(new Gson().toJson(attemptWrapper))); + + final CloseableHttpClient client = getHttpClient(); + setHeaders(post, CONTENT_TYPE_APPL_JSON); + final CloseableHttpResponse httpResponse = client.execute(post); + final StepicWrappers.AttemptContainer container = + new Gson().fromJson(EntityUtils.toString(httpResponse.getEntity()), StepicWrappers.AttemptContainer.class); + return container.attempts.get(0).id; + } +} diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java index 8d871be6a2e2..5af813d0a241 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java @@ -3,7 +3,6 @@ package com.jetbrains.edu.learning.stepic; import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import com.google.gson.annotations.Expose; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.diagnostic.Logger; @@ -70,15 +69,23 @@ public class EduStepicConnector { private EduStepicConnector() { } - public static boolean login(@NotNull final String user, @NotNull final String password) { + public static boolean login(@NotNull final String username, @NotNull final String password) { initializeClient(); - return postCredentials(user, password); + if (postCredentials(username, password)) { + final StepicWrappers.AuthorWrapper stepicUserWrapper = getCurrentUser(); + if (stepicUserWrapper != null && stepicUserWrapper.users.size() == 1) { + final StepicUser user = stepicUserWrapper.users.get(0); + StudySettings.getInstance().setUser(user); + } + return true; + } + return false; } @Nullable - public static AuthorWrapper getCurrentUser() { + public static StepicWrappers.AuthorWrapper getCurrentUser() { try { - return getFromStepic("stepics/1", AuthorWrapper.class); + return getFromStepic("stepics/1", StepicWrappers.AuthorWrapper.class); } catch (IOException e) { LOG.warn("Couldn't get author info"); @@ -90,7 +97,7 @@ public class EduStepicConnector { final HttpPost userRequest = new HttpPost(stepicApiUrl + "users"); initializeClient(); setHeaders(userRequest, "application/json"); - String requestBody = new Gson().toJson(new UserWrapper(user, password)); + String requestBody = new Gson().toJson(new StepicWrappers.UserWrapper(user, password)); userRequest.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON)); try { @@ -109,7 +116,7 @@ public class EduStepicConnector { return true; } - private static void initializeClient() { + public static void initializeClient() { final HttpGet request = new HttpGet(stepicUrl); request.addHeader(new BasicHeader("referer", "https://stepic.org")); request.addHeader(new BasicHeader("content-type", "application/json")); @@ -195,7 +202,7 @@ public class EduStepicConnector { return true; } - private static T getFromStepic(String link, final Class container) throws IOException { + static T getFromStepic(String link, final Class container) throws IOException { final HttpGet request = new HttpGet(stepicApiUrl + link); if (ourClient == null) { initializeClient(); @@ -212,6 +219,15 @@ public class EduStepicConnector { Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); return gson.fromJson(responseString, container); } + + @NotNull + public static CloseableHttpClient getHttpClient() { + if (ourClient == null) { + login(); + initializeClient(); + } + return ourClient; + } @NotNull public static List getCourses() { @@ -231,16 +247,17 @@ public class EduStepicConnector { private static boolean addCoursesFromStepic(List result, int pageNumber) throws IOException { final String url = pageNumber == 0 ? "courses" : "courses?page=" + String.valueOf(pageNumber); - final CoursesContainer coursesContainer = getFromStepic(url, CoursesContainer.class); + final StepicWrappers.CoursesContainer coursesContainer = getFromStepic(url, StepicWrappers.CoursesContainer.class); final List courseInfos = coursesContainer.courses; for (CourseInfo info : courseInfos) { final String courseType = info.getType(); - if (StringUtil.isEmptyOrSpaces(courseType)) continue; + if (!info.isAdaptive() && StringUtil.isEmptyOrSpaces(courseType)) continue; final List typeLanguage = StringUtil.split(courseType, " "); - if (typeLanguage.size() == 2 && PYCHARM_PREFIX.equals(typeLanguage.get(0))) { + // TODO: should adaptive course be of PyCharmType + if (info.isAdaptive() || (typeLanguage.size() == 2 && PYCHARM_PREFIX.equals(typeLanguage.get(0)))) { for (Integer instructor : info.instructors) { - final CourseInfo.Author author = getFromStepic("users/" + String.valueOf(instructor), AuthorWrapper.class).users.get(0); + final StepicUser author = getFromStepic("users/" + String.valueOf(instructor), StepicWrappers.AuthorWrapper.class).users.get(0); info.addAuthor(author); } @@ -255,29 +272,50 @@ public class EduStepicConnector { course.setAuthors(info.getAuthors()); course.setDescription(info.getDescription()); course.setName(info.getName()); - String courseType = info.getType(); - course.setLanguage(courseType.substring(PYCHARM_PREFIX.length() + 1)); + course.setAdaptive(info.isAdaptive()); + course.setId(info.id); course.setUpToDate(true); // TODO: get from stepic - try { - for (Integer section : info.sections) { - course.addLessons(getLessons(section)); + + if (!course.isAdaptive()) { + String courseType = info.getType(); + course.setLanguage(courseType.substring(PYCHARM_PREFIX.length() + 1)); + try { + for (Integer section : info.sections) { + course.addLessons(getLessons(section)); + } + return course; + } + catch (IOException e) { + LOG.error("IOException " + e.getMessage()); } - return course; } - catch (IOException e) { - LOG.error("IOException " + e.getMessage()); + else { + final Lesson lesson = new Lesson(); + //TODO: more specific name? + lesson.setName("Adaptive"); + course.addLesson(lesson); + final Task recommendation = EduAdaptiveStepicConnector.getNextRecommendation(course); + if (recommendation != null) { + lesson.addTask(recommendation); + } + + return course; } return null; } + public static List getLessons(int sectionId) throws IOException { - final SectionContainer sectionContainer = getFromStepic("sections/" + String.valueOf(sectionId), SectionContainer.class); + final StepicWrappers.SectionContainer + sectionContainer = getFromStepic("sections/" + String.valueOf(sectionId), StepicWrappers.SectionContainer.class); List unitIds = sectionContainer.sections.get(0).units; final List lessons = new ArrayList(); for (Integer unitId : unitIds) { - UnitContainer unit = getFromStepic("units/" + String.valueOf(unitId), UnitContainer.class); + StepicWrappers.UnitContainer + unit = getFromStepic("units/" + String.valueOf(unitId), StepicWrappers.UnitContainer.class); int lessonID = unit.units.get(0).lesson; - LessonContainer lesson = getFromStepic("lessons/" + String.valueOf(lessonID), LessonContainer.class); + StepicWrappers.LessonContainer + lesson = getFromStepic("lessons/" + String.valueOf(lessonID), StepicWrappers.LessonContainer.class); Lesson realLesson = lesson.lessons.get(0); realLesson.taskList = new ArrayList(); for (Integer s : realLesson.steps) { @@ -291,13 +329,13 @@ public class EduStepicConnector { } private static void createTask(Lesson lesson, Integer stepicId) throws IOException { - final Step step = getStep(stepicId); + final StepicWrappers.Step step = getStep(stepicId); if (!step.name.equals(PYCHARM_PREFIX)) return; final Task task = new Task(); task.setStepicId(stepicId); task.setName(step.options != null ? step.options.title : PYCHARM_PREFIX); task.setText(step.text); - for (TestFileWrapper wrapper : step.options.test) { + for (StepicWrappers.TestFileWrapper wrapper : step.options.test) { task.addTestsTexts(wrapper.name, wrapper.text); } @@ -310,8 +348,8 @@ public class EduStepicConnector { lesson.taskList.add(task); } - public static Step getStep(Integer step) throws IOException { - return getFromStepic("steps/" + String.valueOf(step), StepContainer.class).steps.get(0).block; + public static StepicWrappers.Step getStep(Integer step) throws IOException { + return getFromStepic("steps/" + String.valueOf(step), StepicWrappers.StepContainer.class).steps.get(0).block; } @@ -342,9 +380,9 @@ public class EduStepicConnector { } } - final HttpPost attemptRequest = new HttpPost(stepicApiUrl + "attempts"); + final HttpPost attemptRequest = new HttpPost(stepicApiUrl + "ATTEMPTS_URL"); setHeaders(attemptRequest, "application/json"); - String attemptRequestBody = new Gson().toJson(new AttemptWrapper(task.getStepicId())); + String attemptRequestBody = new Gson().toJson(new StepicWrappers.AttemptWrapper(task.getStepicId())); attemptRequest.setEntity(new StringEntity(attemptRequestBody, ContentType.APPLICATION_JSON)); try { @@ -355,12 +393,12 @@ public class EduStepicConnector { if (statusLine.getStatusCode() != HttpStatus.SC_CREATED) { LOG.error("Failed to make attempt " + attemptResponseString); } - final AttemptWrapper.Attempt attempt = new Gson().fromJson(attemptResponseString, AttemptContainer.class).attempts.get(0); + final StepicWrappers.AttemptWrapper.Attempt attempt = new Gson().fromJson(attemptResponseString, StepicWrappers.AttemptContainer.class).attempts.get(0); final Map taskFiles = task.getTaskFiles(); - final ArrayList files = new ArrayList(); + final ArrayList files = new ArrayList(); for (TaskFile fileEntry : taskFiles.values()) { - files.add(new SolutionFile(fileEntry.name, fileEntry.text)); + files.add(new StepicWrappers.SolutionFile(fileEntry.name, fileEntry.text)); } postSubmission(passed, attempt, files); } @@ -369,11 +407,11 @@ public class EduStepicConnector { } } - private static void postSubmission(boolean passed, AttemptWrapper.Attempt attempt, ArrayList files) throws IOException { - final HttpPost request = new HttpPost(stepicApiUrl + "submissions"); + private static void postSubmission(boolean passed, StepicWrappers.AttemptWrapper.Attempt attempt, ArrayList files) throws IOException { + final HttpPost request = new HttpPost(stepicApiUrl + "SUBMISSION_URL"); setHeaders(request, "application/json"); - String requestBody = new Gson().toJson(new SubmissionWrapper(attempt.id, passed ? "1" : "0", files)); + String requestBody = new Gson().toJson(new StepicWrappers.SubmissionWrapper(attempt.id, passed ? "1" : "0", files)); request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON)); final CloseableHttpResponse response = ourClient.execute(request); final HttpEntity responseEntity = response.getEntity(); @@ -403,13 +441,13 @@ public class EduStepicConnector { if (ourClient == null || !relogin) { if (!login()) return; } - final AuthorWrapper user = getCurrentUser(); + final StepicWrappers.AuthorWrapper user = getCurrentUser(); if (user != null) { course.setAuthors(user.users); } setHeaders(request, "application/json"); - String requestBody = new Gson().toJson(new CourseWrapper(course)); + String requestBody = new Gson().toJson(new StepicWrappers.CourseWrapper(course)); request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON)); try { @@ -425,7 +463,7 @@ public class EduStepicConnector { LOG.error("Failed to push " + responseString); return; } - final CourseInfo postedCourse = new Gson().fromJson(responseString, CoursesContainer.class).courses.get(0); + final CourseInfo postedCourse = new Gson().fromJson(responseString, StepicWrappers.CoursesContainer.class).courses.get(0); final int sectionId = postModule(postedCourse.id, 1, String.valueOf(postedCourse.getName())); int position = 1; @@ -506,8 +544,8 @@ public class EduStepicConnector { private static void postUnit(int lessonId, int position, int sectionId) { final HttpPost request = new HttpPost(stepicApiUrl + "units"); setHeaders(request, "application/json"); - final UnitWrapper unitWrapper = new UnitWrapper(); - unitWrapper.unit = new Unit(); + final StepicWrappers.UnitWrapper unitWrapper = new StepicWrappers.UnitWrapper(); + unitWrapper.unit = new StepicWrappers.Unit(); unitWrapper.unit.lesson = lessonId; unitWrapper.unit.position = position; unitWrapper.unit.section = sectionId; @@ -532,11 +570,11 @@ public class EduStepicConnector { private static int postModule(int courseId, int position, @NotNull final String title) { final HttpPost request = new HttpPost(stepicApiUrl + "sections"); setHeaders(request, "application/json"); - final Section section = new Section(); + final StepicWrappers.Section section = new StepicWrappers.Section(); section.course = courseId; section.title = title; section.position = position; - final SectionWrapper sectionContainer = new SectionWrapper(); + final StepicWrappers.SectionWrapper sectionContainer = new StepicWrappers.SectionWrapper(); sectionContainer.section = section; String requestBody = new Gson().toJson(sectionContainer); request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON)); @@ -549,7 +587,8 @@ public class EduStepicConnector { if (line.getStatusCode() != HttpStatus.SC_CREATED) { LOG.error("Failed to push " + responseString); } - final Section postedSection = new Gson().fromJson(responseString, SectionContainer.class).sections.get(0); + final StepicWrappers.Section + postedSection = new Gson().fromJson(responseString, StepicWrappers.SectionContainer.class).sections.get(0); return postedSection.id; } catch (IOException e) { @@ -568,7 +607,7 @@ public class EduStepicConnector { } setHeaders(request, "application/json"); - String requestBody = new Gson().toJson(new LessonWrapper(lesson)); + String requestBody = new Gson().toJson(new StepicWrappers.LessonWrapper(lesson)); request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON)); try { @@ -604,7 +643,7 @@ public class EduStepicConnector { } setHeaders(request, "application/json"); - String requestBody = new Gson().toJson(new LessonWrapper(lesson)); + String requestBody = new Gson().toJson(new StepicWrappers.LessonWrapper(lesson)); request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON)); try { @@ -659,7 +698,7 @@ public class EduStepicConnector { ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { - final String requestBody = gson.toJson(new StepSourceWrapper(project, task, lessonId)); + final String requestBody = gson.toJson(new StepicWrappers.StepSourceWrapper(project, task, lessonId)); request.setEntity(new StringEntity(requestBody, ContentType.APPLICATION_JSON)); try { @@ -678,280 +717,9 @@ public class EduStepicConnector { }); } - private static void setHeaders(@NotNull final HttpRequestBase request, String contentType) { + static void setHeaders(@NotNull final HttpRequestBase request, String contentType) { request.addHeader(new BasicHeader("referer", stepicUrl)); request.addHeader(new BasicHeader("X-CSRFToken", ourCSRFToken)); request.addHeader(new BasicHeader("content-type", contentType)); } - - private static class StepContainer { - List steps; - } - - private static class Step { - @Expose StepOptions options; - @Expose String text; - @Expose String name = "pycharm"; - @Expose StepOptions source; - - public static Step fromTask(Project project, @NotNull final Task task) { - final Step step = new Step(); - step.text = task.getTaskText(project); - step.source = StepOptions.fromTask(project, task); - return step; - } - } - - private static class StepOptions { - @Expose List test; - @Expose String title; - @Expose List files; - @Expose String text; - - public static StepOptions fromTask(final Project project, @NotNull final Task task) { - final StepOptions source = new StepOptions(); - setTests(task, source, project); - source.files = new ArrayList(); - source.title = task.getName(); - for (final Map.Entry entry : task.getTaskFiles().entrySet()) { - final TaskFile taskFile = new TaskFile(); - TaskFile.copy(entry.getValue(), taskFile); - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - final VirtualFile taskDir = task.getTaskDir(project); - assert taskDir != null; - VirtualFile ideaDir = project.getBaseDir().findChild(".idea"); - assert ideaDir != null; - EduUtils.createStudentFileFromAnswer(project, ideaDir, taskDir, entry.getKey(), taskFile); - } - }); - taskFile.name = entry.getKey(); - - VirtualFile ideaDir = project.getBaseDir().findChild(".idea"); - if (ideaDir == null) return null; - final VirtualFile file = ideaDir.findChild(taskFile.name); - try { - if (file != null) { - if (EduUtils.isImage(taskFile.name)) { - taskFile.text = Base64.encodeBase64URLSafeString(FileUtil.loadBytes(file.getInputStream())); - } - else { - taskFile.text = FileUtil.loadTextAndClose(file.getInputStream()); - } - } - } - catch (IOException e) { - LOG.error("Can't find file " + file.getPath()); - } - - source.files.add(taskFile); - } - return source; - } - - private static void setTests(@NotNull final Task task, @NotNull final StepOptions source, @NotNull final Project project) { - final Map testsText = task.getTestsText(); - if (testsText.isEmpty()) { - ApplicationManager.getApplication().runReadAction(new Runnable() { - @Override - public void run() { - source.test = Collections.singletonList(new TestFileWrapper(EduNames.TESTS_FILE, task.getTestsText(project))); - } - }); - } - else { - source.test = new ArrayList(); - for (Map.Entry entry : testsText.entrySet()) { - source.test.add(new TestFileWrapper(entry.getKey(), entry.getValue())); - } - } - } - } - - private static class CoursesContainer { - public List courses; - public Map meta; - } - - static class StepSourceWrapper { - @Expose - StepSource stepSource; - - public StepSourceWrapper(Project project, Task task, int lessonId) { - stepSource = new StepSource(project, task, lessonId); - } - } - - static class CourseWrapper { - CourseInfo course; - - public CourseWrapper(Course course) { - this.course = new CourseInfo(); - this.course.setName(course.getName()); - this.course.setDescription(course.getDescription()); - this.course.setAuthors(course.getAuthors()); - } - } - - static class LessonWrapper { - Lesson lesson; - - public LessonWrapper(Lesson lesson) { - this.lesson = new Lesson(); - this.lesson.setName(lesson.getName()); - this.lesson.id = lesson.id; - this.lesson.steps = new ArrayList(); - } - } - - static class LessonContainer { - List lessons; - } - - static class StepSource { - @Expose Step block; - @Expose int position = 0; - @Expose int lesson = 0; - - public StepSource(Project project, Task task, int lesson) { - this.lesson = lesson; - position = task.getIndex(); - block = Step.fromTask(project, task); - } - } - - static class TestFileWrapper { - @Expose private final String name; - @Expose private final String text; - - public TestFileWrapper(String name, String text) { - this.name = name; - this.text = text; - } - } - - static class Section { - List units; - int course; - String title; - int position; - int id; - } - - static class SectionWrapper { - Section section; - } - - static class SectionContainer { - List
sections; - List lessons; - - List units; - } - - static class Unit { - int id; - int section; - int lesson; - int position; - } - - static class UnitContainer { - - List units; - } - - static class UnitWrapper{ - Unit unit; - } - - - static class AttemptWrapper { - static class Attempt { - public Attempt(int step) { - this.step = step; - } - - int step; - int id; - } - public AttemptWrapper(int step) { - attempt = new Attempt(step); - } - - Attempt attempt; - } - - static class AttemptContainer { - List attempts; - } - - static class SolutionFile { - String name; - String text; - - public SolutionFile(String name, String text) { - this.name = name; - this.text = text; - } - } - - static class AuthorWrapper { - List users; - } - - static class SubmissionWrapper { - Submission submission; - - - public SubmissionWrapper(int attempt, String score, ArrayList files) { - submission = new Submission(score, attempt, files); - } - - static class Submission { - int attempt; - - private final Reply reply; - - public Submission(String score, int attempt, ArrayList files) { - reply = new Reply(files, score); - this.attempt = attempt; - } - - static class Reply { - String score; - List solution; - - public Reply(ArrayList files, String score) { - this.score = score; - solution = files; - } - } - } - - } - - static class User { - String first_name; - String last_name; - String email; - String password; - - public User(String user, String password) { - email = user; - this.password = password; - this.first_name = user; - this.last_name = user; - } - } - - static class UserWrapper { - User user; - - public UserWrapper(String user, String password) { - this.user = new User(user, password); - } - } - } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicUser.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicUser.java new file mode 100644 index 000000000000..05365f40ae8e --- /dev/null +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicUser.java @@ -0,0 +1,71 @@ +package com.jetbrains.edu.learning.stepic; + +import com.intellij.openapi.util.text.StringUtil; + +public class StepicUser { + int id; + String first_name; + String last_name; + String email; + String password; + + public StepicUser(int id, String first_name, String last_name, String email, String password) { + this.id = id; + this.first_name = first_name; + this.last_name = last_name; + this.email = email; + this.password = password; + } + + public StepicUser(String email, String password) { + this.email = email; + this.password = password; + } + + public StepicUser() { + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getFirst_name() { + return first_name; + } + + public void setFirst_name(String first_name) { + this.first_name = first_name; + } + + public String getLast_name() { + return last_name; + } + + public void setLast_name(String last_name) { + this.last_name = last_name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getName() { + return StringUtil.join(new String[]{first_name, last_name}, " "); + } +} diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java new file mode 100644 index 000000000000..8a02678c182c --- /dev/null +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java @@ -0,0 +1,375 @@ +package com.jetbrains.edu.learning.stepic; + +import com.google.gson.annotations.Expose; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.jetbrains.edu.learning.core.EduNames; +import com.jetbrains.edu.learning.core.EduUtils; +import com.jetbrains.edu.learning.courseFormat.Course; +import com.jetbrains.edu.learning.courseFormat.Lesson; +import com.jetbrains.edu.learning.courseFormat.Task; +import com.jetbrains.edu.learning.courseFormat.TaskFile; +import org.apache.commons.codec.binary.Base64; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class StepicWrappers { + private static final Logger LOG = Logger.getInstance(StepOptions.class); + + static class StepContainer { + List steps; + } + + public static class Step { + @Expose StepOptions options; + @Expose String text; + @Expose String name = "pycharm"; + @Expose StepOptions source; + + public static Step fromTask(Project project, @NotNull final Task task) { + final Step step = new Step(); + step.text = task.getTaskText(project); + step.source = StepOptions.fromTask(project, task); + return step; + } + } + + public static class StepOptions { + @Expose List test; + @Expose String title; + @Expose List files; + @Expose String text; + + public static StepOptions fromTask(final Project project, @NotNull final Task task) { + final StepOptions source = new StepOptions(); + setTests(task, source, project); + source.files = new ArrayList(); + source.title = task.getName(); + for (final Map.Entry entry : task.getTaskFiles().entrySet()) { + final TaskFile taskFile = new TaskFile(); + TaskFile.copy(entry.getValue(), taskFile); + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + final VirtualFile taskDir = task.getTaskDir(project); + assert taskDir != null; + VirtualFile ideaDir = project.getBaseDir().findChild(".idea"); + assert ideaDir != null; + EduUtils.createStudentFileFromAnswer(project, ideaDir, taskDir, entry.getKey(), taskFile); + } + }); + taskFile.name = entry.getKey(); + + VirtualFile ideaDir = project.getBaseDir().findChild(".idea"); + if (ideaDir == null) return null; + final VirtualFile file = ideaDir.findChild(taskFile.name); + try { + if (file != null) { + if (EduUtils.isImage(taskFile.name)) { + taskFile.text = Base64.encodeBase64URLSafeString(FileUtil.loadBytes(file.getInputStream())); + } + else { + taskFile.text = FileUtil.loadTextAndClose(file.getInputStream()); + } + } + } + catch (IOException e) { + LOG.error("Can't find file " + file.getPath()); + } + + source.files.add(taskFile); + } + return source; + } + + private static void setTests(@NotNull final Task task, @NotNull final StepOptions source, @NotNull final Project project) { + final Map testsText = task.getTestsText(); + if (testsText.isEmpty()) { + ApplicationManager.getApplication().runReadAction(new Runnable() { + @Override + public void run() { + source.test = Collections.singletonList(new TestFileWrapper(EduNames.TESTS_FILE, task.getTestsText(project))); + } + }); + } + else { + source.test = new ArrayList(); + for (Map.Entry entry : testsText.entrySet()) { + source.test.add(new TestFileWrapper(entry.getKey(), entry.getValue())); + } + } + } + } + + static class CoursesContainer { + public List courses; + public Map meta; + } + + static class StepSourceWrapper { + @Expose + StepSource stepSource; + + public StepSourceWrapper(Project project, Task task, int lessonId) { + stepSource = new StepSource(project, task, lessonId); + } + } + + static class CourseWrapper { + CourseInfo course; + + public CourseWrapper(Course course) { + this.course = new CourseInfo(); + this.course.setName(course.getName()); + this.course.setDescription(course.getDescription()); + this.course.setAuthors(course.getAuthors()); + } + } + + static class LessonWrapper { + Lesson lesson; + + public LessonWrapper(Lesson lesson) { + this.lesson = new Lesson(); + this.lesson.setName(lesson.getName()); + this.lesson.id = lesson.id; + this.lesson.steps = new ArrayList(); + } + } + + static class LessonContainer { + List lessons; + } + + static class StepSource { + @Expose Step block; + @Expose int position = 0; + @Expose int lesson = 0; + + public StepSource(Project project, Task task, int lesson) { + this.lesson = lesson; + position = task.getIndex(); + block = Step.fromTask(project, task); + } + } + + static class TestFileWrapper { + @Expose public final String name; + @Expose public final String text; + + public TestFileWrapper(String name, String text) { + this.name = name; + this.text = text; + } + } + + static class Section { + List units; + int course; + String title; + int position; + int id; + } + + static class SectionWrapper { + Section section; + } + + static class SectionContainer { + List
sections; + List lessons; + + List units; + } + + static class Unit { + int id; + int section; + int lesson; + int position; + } + + static class UnitContainer { + + List units; + } + + static class UnitWrapper{ + Unit unit; + } + + static class AttemptWrapper { + static class Attempt { + public Attempt(int step) { + this.step = step; + } + + int step; + int id; + } + public AttemptWrapper(int step) { + attempt = new Attempt(step); + } + + Attempt attempt; + } + + static class AttemptToPostWrapper { + static class Attempt { + int step; + String dataset_url; + String status; + String time; + String time_left; + String user; + String user_id; + + public Attempt(int step) { + this.step = step; + } + } + public AttemptToPostWrapper(int step) { + attempt = new Attempt(step); + } + + Attempt attempt; + } + + static class AttemptContainer { + List attempts; + } + + static class SolutionFile { + String name; + String text; + + public SolutionFile(String name, String text) { + this.name = name; + this.text = text; + } + } + + static class AuthorWrapper { + List users; + } + + static class SubmissionWrapper { + Submission submission; + + + public SubmissionWrapper(int attempt, String score, ArrayList files) { + submission = new Submission(score, attempt, files); + } + + static class Submission { + int attempt; + + private final Reply reply; + + public Submission(String score, int attempt, ArrayList files) { + reply = new Reply(files, score); + this.attempt = attempt; + } + + static class Reply { + String score; + List solution; + + public Reply(ArrayList files, String score) { + this.score = score; + solution = files; + } + } + } + + } + + static class UserWrapper { + StepicUser user; + + public UserWrapper(String user, String password) { + this.user = new StepicUser(user, password); + } + } + + static class RecommendationReaction { + int reaction; + String user; + String lesson; + + public RecommendationReaction(int reaction, String user, String lesson) { + this.reaction = reaction; + this.user = user; + this.lesson = lesson; + } + } + + static class RecommendationReactionWrapper { + RecommendationReaction recommendationReaction; + + public RecommendationReactionWrapper(RecommendationReaction recommendationReaction) { + this.recommendationReaction = recommendationReaction; + } + } + + static class RecommendationWrapper { + Recommendation[] recommendations; + } + + static class Recommendation { + String id; + String lesson; + } + + + + static class SubmissionToPostWrapper { + Submission submission; + + + + public SubmissionToPostWrapper(@NotNull String attemptId, @NotNull String language, @NotNull String code) { + submission = new Submission(attemptId, new Submission.Reply(language, code)); + } + + static class Submission { + String attempt; + Reply reply; + + public Submission(String attempt, Reply reply) { + this.attempt = attempt; + this.reply = reply; + } + + static class Reply { + String language; + String code; + + public Reply(String language, String code) { + this.language = language; + this.code = code; + } + } + } + + } + + static class ResultSubmissionWrapper { + ResultSubmission[] submissions; + + static class ResultSubmission { + int id; + String status; + String hint; + } + } + +} diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StudySettings.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StudySettings.java index 8941d4343064..c07279896732 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StudySettings.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StudySettings.java @@ -34,6 +34,7 @@ public class StudySettings implements PersistentStateComponent EduAdaptiveStepicConnector.addNextRecommendedTask(project, 0)); + addMouseListener(boringPanel, () -> EduAdaptiveStepicConnector.addNextRecommendedTask(project, -1)); + + return panel; + } + + private static void addMouseListener(@NotNull final JPanel panel, @NotNull Runnable onClickAction ) { + panel.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 1) { + final ProgressIndicatorBase progress = new ProgressIndicatorBase(); + progress.setText("Loading Next Recommendation"); + ProgressManager.getInstance().run(new Task.Backgroundable(ProjectUtil.guessCurrentProject(panel), + "Loading Next Recommendation") { + + @Override + public void run(@NotNull ProgressIndicator indicator) { + onClickAction.run(); + } + }); + } + } + + @Override + public void mouseEntered(MouseEvent e) { + panel.setBackground(UIUtil.getTreeSelectionBackground()); + } + + @Override + public void mouseExited(MouseEvent e) { + panel.setBackground(UIUtil.getLabelBackground()); + } + }); + } + public void dispose() { } diff --git a/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java b/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java index 43f5fc5645f4..1b238004fd57 100644 --- a/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java +++ b/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java @@ -15,7 +15,6 @@ import com.jetbrains.edu.learning.actions.StudyRunAction; import com.jetbrains.edu.learning.checker.StudyCheckTask; import com.jetbrains.edu.learning.checker.StudyCheckUtils; import com.jetbrains.edu.learning.checker.StudyTestRunner; -import com.jetbrains.edu.learning.checker.StudyTestsOutputParser; import com.jetbrains.edu.learning.courseFormat.StudyStatus; import com.jetbrains.edu.learning.courseFormat.Task; import com.jetbrains.edu.learning.courseFormat.TaskFile; @@ -92,7 +91,7 @@ public class PyStudyCheckAction extends StudyCheckAction { final String commandLine) { return new StudyCheckTask(project, studyState, myCheckInProgress, testProcess, commandLine) { @Override - protected void onTaskFailed(StudyTestsOutputParser.TestsOutput testsOutput) { + protected void onTaskFailed(String message) { ApplicationManager.getApplication().invokeLater(() -> { if (myTaskDir == null) return; myTaskManger.setStatus(myTask, StudyStatus.Failed); @@ -107,7 +106,7 @@ public class PyStudyCheckAction extends StudyCheckAction { StudyCheckUtils.runSmartTestProcess(myTaskDir, testRunner, name, taskFile, project); })); } - StudyCheckUtils.showTestResultPopUp(testsOutput.getMessage(), MessageType.ERROR.getPopupBackground(), project); + StudyCheckUtils.showTestResultPopUp(message, MessageType.ERROR.getPopupBackground(), project); StudyCheckUtils.navigateToFailedPlaceholder(myStudyState, myTask, myTaskDir, project); }); } From 3c2d5b5e5441adf874707ebf806bf02b589902b4 Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Thu, 7 Apr 2016 15:00:58 +0300 Subject: [PATCH 02/44] Fix problem with deserializing isAdaptive field of Course class --- .../learning/courseGeneration/StudyProjectGenerator.java | 2 +- .../com/jetbrains/edu/learning/stepic/CourseInfo.java | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java b/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java index 157c6a6859c7..a1afc0290bc5 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java @@ -90,7 +90,7 @@ public class StudyProjectGenerator { final File courseFile = new File(new File(ourCoursesDir, mySelectedCourseInfo.getName()), EduNames.COURSE_META_FILE); if (courseFile.exists()) { reader = new InputStreamReader(new FileInputStream(courseFile), "UTF-8"); - Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); + Gson gson = new GsonBuilder().create(); final Course course = gson.fromJson(reader, Course.class); course.initCourse(false); return course; diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/CourseInfo.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/CourseInfo.java index a0ab35497ed8..eff3c6f3684e 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/CourseInfo.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/CourseInfo.java @@ -21,8 +21,7 @@ public class CourseInfo { @SerializedName("course_format") //course type in format "pycharm " private String myType = "pycharm Python"; - - @SerializedName("is_adaptive") + private boolean isAdaptive; int id; @@ -120,8 +119,12 @@ public class CourseInfo { public void setType(String type) { myType = type; } - + public boolean isAdaptive() { return isAdaptive; } + + public void setAdaptive(boolean adaptive) { + isAdaptive = adaptive; + } } From eb79c302839ff1c16aafd99ae7256732b6537c1e Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Thu, 7 Apr 2016 15:57:21 +0300 Subject: [PATCH 03/44] Add progress window on course getting in new project panel --- .../edu/learning/ui/StudyNewProjectPanel.java | 44 +++++++++++++++---- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java index b37224e6a80f..e61617cb4897 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java @@ -3,8 +3,12 @@ package com.jetbrains.edu.learning.ui; import com.intellij.facet.ui.FacetValidatorsManager; import com.intellij.facet.ui.ValidationResult; import com.intellij.icons.AllIcons; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileChooser.FileChooser; import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.DefaultProjectFactoryImpl; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.ui.popup.ListPopup; @@ -13,8 +17,8 @@ import com.intellij.openapi.ui.popup.util.BaseListPopupStep; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Consumer; -import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.StudyUtils; +import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator; import com.jetbrains.edu.learning.stepic.CourseInfo; import com.jetbrains.edu.learning.stepic.EduStepicConnector; @@ -22,6 +26,7 @@ import com.jetbrains.edu.learning.stepic.StudySettings; import icons.InteractiveLearningIcons; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.service.SharedThreadPool; import javax.swing.*; import java.awt.*; @@ -29,15 +34,18 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; /** * author: liana * data: 7/31/14. */ public class StudyNewProjectPanel{ + private static final Logger LOG = Logger.getInstance(StudyNewProjectPanel.class); private List myAvailableCourses = new ArrayList(); private JButton myBrowseButton; - private JComboBox myCoursesComboBox; + private JComboBox myCoursesComboBox; private JButton myRefreshButton; private JPanel myContentPanel; private JLabel myAuthorLabel; @@ -261,13 +269,31 @@ public class StudyNewProjectPanel{ } super.doOKAction(); - final boolean isSuccess = EduStepicConnector.login(myRemoteCourse.getLogin(), myRemoteCourse.getPassword()); - if (!isSuccess) { - setError("Failed to log in"); - } - StudySettings.getInstance().setLogin(myRemoteCourse.getLogin()); - StudySettings.getInstance().setPassword(myRemoteCourse.getPassword()); - refreshCoursesList(); + final ProgressManager progressManager = ProgressManager.getInstance(); + progressManager.runProcessWithProgressSynchronously(() -> { + + final Future future = SharedThreadPool.getInstance().executeOnPooledThread(() -> { + final boolean isSuccess = EduStepicConnector.login(myRemoteCourse.getLogin(), myRemoteCourse.getPassword()); + ApplicationManager.getApplication().invokeLater(() -> { + if (!isSuccess) { + setError("Failed to log in"); + } + StudySettings.getInstance().setLogin(myRemoteCourse.getLogin()); + StudySettings.getInstance().setPassword(myRemoteCourse.getPassword()); + refreshCoursesList(); + }); + }); + + while (!future.isDone()) { + progressManager.getProgressIndicator().checkCanceled(); + try { + TimeUnit.MILLISECONDS.sleep(500); + } + catch (final InterruptedException e) { + LOG.warn(e.getMessage()); + } + } + }, "Getting Stepic Course List", true, new DefaultProjectFactoryImpl().getDefaultProject()); } } From 05ed872b41a8532ff8ce954d6d5f2dc6a4ccc97f Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Wed, 20 Apr 2016 14:12:21 +0300 Subject: [PATCH 04/44] Minor fixes in ui on login failed --- .../learning/stepic/EduStepicConnector.java | 18 +++------------ .../edu/learning/ui/StudyNewProjectPanel.java | 23 ++++++++----------- .../PyStudyDirectoryProjectGenerator.java | 5 ++-- 3 files changed, 16 insertions(+), 30 deletions(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java index 5af813d0a241..03f0345eb03d 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java @@ -23,10 +23,7 @@ import com.jetbrains.edu.learning.courseFormat.Lesson; import com.jetbrains.edu.learning.courseFormat.Task; import com.jetbrains.edu.learning.courseFormat.TaskFile; import org.apache.commons.codec.binary.Base64; -import org.apache.http.HttpEntity; -import org.apache.http.HttpStatus; -import org.apache.http.NameValuePair; -import org.apache.http.StatusLine; +import org.apache.http.*; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.*; import org.apache.http.cookie.Cookie; @@ -39,7 +36,6 @@ import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicNameValuePair; -import org.apache.http.protocol.HTTP; import org.apache.http.util.EntityUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -48,7 +44,6 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; @@ -171,14 +166,7 @@ public class EduStepicConnector { nvps.add(new BasicNameValuePair("password", password)); nvps.add(new BasicNameValuePair("remember", "on")); - try { - request.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8)); - } - catch (UnsupportedEncodingException e) { - LOG.error(e.getMessage()); - ourClient = null; - return false; - } + request.setEntity(new UrlEncodedFormEntity(nvps, Consts.UTF_8)); setHeaders(request, "application/x-www-form-urlencoded"); @@ -189,7 +177,7 @@ public class EduStepicConnector { if (line.getStatusCode() != HttpStatus.SC_MOVED_TEMPORARILY) { final HttpEntity responseEntity = response.getEntity(); final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : ""; - LOG.error("Failed to login " + responseString); + LOG.warn("Failed to login " + responseString); ourClient = null; return false; } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java index e61617cb4897..de940bd0194e 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java @@ -41,8 +41,8 @@ import java.util.concurrent.TimeUnit; * author: liana * data: 7/31/14. */ -public class StudyNewProjectPanel{ - private static final Logger LOG = Logger.getInstance(StudyNewProjectPanel.class); +public class StudyNewProjectPanel { + private static final Logger LOG = Logger.getInstance(StudyNewProjectPanel.class); private List myAvailableCourses = new ArrayList(); private JButton myBrowseButton; private JComboBox myCoursesComboBox; @@ -84,7 +84,6 @@ public class StudyNewProjectPanel{ myRefreshButton.setIcon(AllIcons.Actions.Refresh); myLabel.setPreferredSize(new JLabel("Project name").getPreferredSize()); - } private void setupBrowseButton() { @@ -225,7 +224,7 @@ public class StudyNewProjectPanel{ return; } final String authorsString = Course.getAuthorsString(selectedCourse.getAuthors()); - myAuthorLabel.setText(!StringUtil.isEmptyOrSpaces(authorsString) ?"Author: " + authorsString : ""); + myAuthorLabel.setText(!StringUtil.isEmptyOrSpaces(authorsString) ? "Author: " + authorsString : ""); myCoursesComboBox.removeItem(CourseInfo.INVALID_COURSE); myDescriptionLabel.setText(selectedCourse.getDescription()); myGenerator.setSelectedCourse(selectedCourse); @@ -271,17 +270,16 @@ public class StudyNewProjectPanel{ super.doOKAction(); final ProgressManager progressManager = ProgressManager.getInstance(); progressManager.runProcessWithProgressSynchronously(() -> { - final Future future = SharedThreadPool.getInstance().executeOnPooledThread(() -> { final boolean isSuccess = EduStepicConnector.login(myRemoteCourse.getLogin(), myRemoteCourse.getPassword()); - ApplicationManager.getApplication().invokeLater(() -> { - if (!isSuccess) { - setError("Failed to log in"); - } + if (!isSuccess) { + setError("Failed to log in"); + } + else { StudySettings.getInstance().setLogin(myRemoteCourse.getLogin()); StudySettings.getInstance().setPassword(myRemoteCourse.getPassword()); - refreshCoursesList(); - }); + ApplicationManager.getApplication().invokeLater(StudyNewProjectPanel.this::refreshCoursesList); + } }); while (!future.isDone()) { @@ -293,8 +291,7 @@ public class StudyNewProjectPanel{ LOG.warn(e.getMessage()); } } - }, "Getting Stepic Course List", true, new DefaultProjectFactoryImpl().getDefaultProject()); + }, "Login And Getting Stepic Course List", true, new DefaultProjectFactoryImpl().getDefaultProject()); } } - } diff --git a/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java b/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java index df9db88d18ae..551b831f1d5b 100644 --- a/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java +++ b/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java @@ -6,6 +6,7 @@ import com.intellij.facet.ui.ValidationResult; import com.intellij.ide.fileTemplates.FileTemplate; import com.intellij.ide.fileTemplates.FileTemplateManager; import com.intellij.ide.fileTemplates.FileTemplateUtil; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; import com.intellij.openapi.progress.ProcessCanceledException; @@ -15,8 +16,8 @@ import com.intellij.platform.DirectoryProjectGenerator; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiManager; import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator; -import com.jetbrains.edu.learning.ui.StudyNewProjectPanel; import com.jetbrains.edu.learning.stepic.CourseInfo; +import com.jetbrains.edu.learning.ui.StudyNewProjectPanel; import com.jetbrains.python.newProject.PythonProjectGenerator; import icons.InteractiveLearningPythonIcons; import org.jetbrains.annotations.Nls; @@ -89,7 +90,7 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator imp throw new UnsupportedOperationException(); } public void validate() { - fireStateChanged(); + ApplicationManager.getApplication().invokeLater(()->fireStateChanged()); } }); return settingsPanel.getContentPanel(); From 93f0fae3a931984646b037d7b908e5a5eeab27ca Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Wed, 20 Apr 2016 17:44:00 +0300 Subject: [PATCH 05/44] Set email field as default focused element --- .../edu/learning/ui/StudyAddRemoteCourse.java | 4 ++++ .../edu/learning/ui/StudyNewProjectPanel.java | 13 +++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyAddRemoteCourse.java b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyAddRemoteCourse.java index adce70291557..f61e12f93599 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyAddRemoteCourse.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyAddRemoteCourse.java @@ -40,6 +40,10 @@ public class StudyAddRemoteCourse { return myContentPanel; } + public JTextField getLoginField() { + return myLoginField; + } + public String getPassword() { return String.valueOf(myPasswordField.getPassword()); } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java index de940bd0194e..505580f413e5 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java @@ -26,7 +26,6 @@ import com.jetbrains.edu.learning.stepic.StudySettings; import icons.InteractiveLearningIcons; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.jps.service.SharedThreadPool; import javax.swing.*; import java.awt.*; @@ -256,6 +255,12 @@ public class StudyNewProjectPanel { return myRemoteCourse.getContentPanel(); } + @Nullable + @Override + public JComponent getPreferredFocusedComponent() { + return myRemoteCourse.getLoginField(); + } + @Override protected void doOKAction() { if (StringUtil.isEmptyOrSpaces(myRemoteCourse.getLogin())) { @@ -270,7 +275,7 @@ public class StudyNewProjectPanel { super.doOKAction(); final ProgressManager progressManager = ProgressManager.getInstance(); progressManager.runProcessWithProgressSynchronously(() -> { - final Future future = SharedThreadPool.getInstance().executeOnPooledThread(() -> { + final Future future = ApplicationManager.getApplication().executeOnPooledThread(() -> { final boolean isSuccess = EduStepicConnector.login(myRemoteCourse.getLogin(), myRemoteCourse.getPassword()); if (!isSuccess) { setError("Failed to log in"); @@ -282,7 +287,7 @@ public class StudyNewProjectPanel { } }); - while (!future.isDone()) { + while (!future.isCancelled() && !future.isDone()) { progressManager.getProgressIndicator().checkCanceled(); try { TimeUnit.MILLISECONDS.sleep(500); @@ -291,7 +296,7 @@ public class StudyNewProjectPanel { LOG.warn(e.getMessage()); } } - }, "Login And Getting Stepic Course List", true, new DefaultProjectFactoryImpl().getDefaultProject()); + }, "Signing In And Getting Stepic Course List", true, new DefaultProjectFactoryImpl().getDefaultProject()); } } } From c01ae357bb35748e8b5d1ee8de92a92186f0f671 Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Wed, 20 Apr 2016 17:46:05 +0300 Subject: [PATCH 06/44] Minor Change login fail logging --- .../com/jetbrains/edu/learning/stepic/EduStepicConnector.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java index 03f0345eb03d..5e36808a8c0d 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java @@ -177,7 +177,8 @@ public class EduStepicConnector { if (line.getStatusCode() != HttpStatus.SC_MOVED_TEMPORARILY) { final HttpEntity responseEntity = response.getEntity(); final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : ""; - LOG.warn("Failed to login " + responseString); + LOG.warn("Failed to login: " + line.getStatusCode() + line.getReasonPhrase()); + LOG.debug("Failed to login " + responseString); ourClient = null; return false; } From a72aa78a62ba9be8dbc34f4acfcd8c7198034462 Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Wed, 20 Apr 2016 18:37:14 +0300 Subject: [PATCH 07/44] Fix problem with displaying login error --- .../edu/learning/ui/StudyNewProjectPanel.java | 39 +++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java index 505580f413e5..c9bc67faaae6 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java @@ -33,6 +33,8 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @@ -275,17 +277,13 @@ public class StudyNewProjectPanel { super.doOKAction(); final ProgressManager progressManager = ProgressManager.getInstance(); progressManager.runProcessWithProgressSynchronously(() -> { - final Future future = ApplicationManager.getApplication().executeOnPooledThread(() -> { - final boolean isSuccess = EduStepicConnector.login(myRemoteCourse.getLogin(), myRemoteCourse.getPassword()); - if (!isSuccess) { - setError("Failed to log in"); - } - else { - StudySettings.getInstance().setLogin(myRemoteCourse.getLogin()); - StudySettings.getInstance().setPassword(myRemoteCourse.getPassword()); - ApplicationManager.getApplication().invokeLater(StudyNewProjectPanel.this::refreshCoursesList); - } - }); + final Future future = ApplicationManager.getApplication().executeOnPooledThread( + new Callable() { + @Override + public Boolean call() throws Exception { + return EduStepicConnector.login(myRemoteCourse.getLogin(), myRemoteCourse.getPassword()); + } + }); while (!future.isCancelled() && !future.isDone()) { progressManager.getProgressIndicator().checkCanceled(); @@ -296,6 +294,25 @@ public class StudyNewProjectPanel { LOG.warn(e.getMessage()); } } + + try { + final boolean isSuccess = future.get(); + + if (!isSuccess) { + setError("Failed to log in"); + } + else { + StudySettings.getInstance().setLogin(myRemoteCourse.getLogin()); + StudySettings.getInstance().setPassword(myRemoteCourse.getPassword()); + ApplicationManager.getApplication().invokeLater(StudyNewProjectPanel.this::refreshCoursesList); + } + } + catch (InterruptedException e) { + LOG.warn(e.getMessage()); + } + catch (ExecutionException e) { + LOG.warn(e.getMessage()); + } }, "Signing In And Getting Stepic Course List", true, new DefaultProjectFactoryImpl().getDefaultProject()); } } From 5a0a6e33603c69b41bc1d3d8b66d56855df03434 Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Thu, 21 Apr 2016 14:43:27 +0300 Subject: [PATCH 08/44] Minor From lesson choose only code steps --- .../stepic/EduAdaptiveStepicConnector.java | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java index 43eb2008aef5..0a6d05f16c0e 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java @@ -61,9 +61,9 @@ public class EduAdaptiveStepicConnector { @Nullable public static Task getNextRecommendation(@NotNull Course course) { - + try { - final CloseableHttpClient client = getHttpClient(); + final CloseableHttpClient client = getHttpClient(); final URI uri = new URIBuilder(STEPIC_API_URL + RECOMMENDATIONS_URL).addParameter("course", String.valueOf(course.getId())).build(); final HttpGet request = new HttpGet(uri); setHeaders(request, CONTENT_TYPE_APPL_JSON); @@ -72,7 +72,7 @@ public class EduAdaptiveStepicConnector { final StatusLine statusLine = response.getStatusLine(); final HttpEntity responseEntity = response.getEntity(); final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : ""; - + if (statusLine.getStatusCode() == HttpStatus.SC_OK) { Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); final StepicWrappers.RecommendationWrapper recomWrapper = gson.fromJson(responseString, StepicWrappers.RecommendationWrapper.class); @@ -83,14 +83,18 @@ public class EduAdaptiveStepicConnector { final StepicWrappers.LessonContainer lessonContainer = EduStepicConnector.getFromStepic(LESSON_URL + lessonId, StepicWrappers.LessonContainer.class); final Lesson realLesson = lessonContainer.lessons.get(0); - final Integer stepId = realLesson.steps.get(0); course.getLessons().get(0).id = Integer.parseInt(lessonId); - final StepicWrappers.Step step = EduStepicConnector.getStep(stepId); - return getTaskFromStep(LESSON_DEFAULT_NAME + lessonId, stepId, step); + + for (int stepId : realLesson.steps) { + final StepicWrappers.Step step = EduStepicConnector.getStep(stepId); + if (step.name.equals("code")) { + return getTaskFromStep(LESSON_DEFAULT_NAME + lessonId, stepId, step); + } + } } } else { - throw new IOException("Stepic returned non 200 status code " + responseString); + throw new IOException("Stepic returned non 200 status code: " + responseString); } } catch (IOException e) { @@ -105,7 +109,8 @@ public class EduAdaptiveStepicConnector { public static boolean postRecommendationReaction(int reaction, @NotNull final String user, @NotNull final String lessonId) { final HttpPost post = new HttpPost(STEPIC_API_URL + RECOMMENDATION_REACTIONS_URL); - final String json = new Gson().toJson(new StepicWrappers.RecommendationReactionWrapper(new StepicWrappers.RecommendationReaction(reaction, user, lessonId))); + final String json = new Gson() + .toJson(new StepicWrappers.RecommendationReactionWrapper(new StepicWrappers.RecommendationReaction(reaction, user, lessonId))); post.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON)); final CloseableHttpClient client = getHttpClient(); setHeaders(post, CONTENT_TYPE_APPL_JSON); @@ -125,7 +130,8 @@ public class EduAdaptiveStepicConnector { if (course != null && editor != null && editor.getTaskFile() != null) { // TODO: get user from settings final StepicUser user = StudySettings.getInstance().getUser(); - if (user != null && postRecommendationReaction(reaction, String.valueOf(user.id), String.valueOf(editor.getTaskFile().getTask().getLesson().id))) { + if (user != null && + postRecommendationReaction(reaction, String.valueOf(user.id), String.valueOf(editor.getTaskFile().getTask().getLesson().id))) { final Task task = getNextRecommendation(course); if (task != null) { @@ -137,7 +143,7 @@ public class EduAdaptiveStepicConnector { unsolvedTask.setText(task.getText()); unsolvedTask.getTaskFiles().clear(); unsolvedTask.getTaskFiles().putAll(task.getTaskFiles()); - + final File lessonDirectory = new File(course.getCourseDirectory(), EduNames.LESSON + String.valueOf(adaptive.getIndex())); final File taskDirectory = new File(lessonDirectory, EduNames.TASK + String.valueOf(adaptive.getTaskList().size())); StudyProjectGenerator.flushTask(task, taskDirectory); @@ -147,7 +153,7 @@ public class EduAdaptiveStepicConnector { } adaptive.initLesson(course, false); - VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); + VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); } else { adaptive.addTask(task); @@ -205,7 +211,7 @@ public class EduAdaptiveStepicConnector { } return task; } - + @Nullable public static Pair checkTask(@NotNull final Task task, @NotNull final Project project) { try { @@ -263,7 +269,7 @@ public class EduAdaptiveStepicConnector { final Sdk sdk = StudyUtils.findSdk(task, project); if (sdk != null) { if (sdk.getVersionString() != null && sdk.getVersionString().startsWith("3")) { - return "python3"; + return "python3"; } else { return "python2"; @@ -280,7 +286,7 @@ public class EduAdaptiveStepicConnector { final HttpPost post = new HttpPost(STEPIC_API_URL + attempts); post.setEntity(new StringEntity(new Gson().toJson(attemptWrapper))); - + final CloseableHttpClient client = getHttpClient(); setHeaders(post, CONTENT_TYPE_APPL_JSON); final CloseableHttpResponse httpResponse = client.execute(post); From d2d85eac9492740f33c47a35444f1f34f8061151 Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Thu, 21 Apr 2016 14:51:29 +0300 Subject: [PATCH 09/44] Get name for task from containing lesson --- .../edu/learning/stepic/EduAdaptiveStepicConnector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java index 0a6d05f16c0e..2b87cfc5b479 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java @@ -88,7 +88,7 @@ public class EduAdaptiveStepicConnector { for (int stepId : realLesson.steps) { final StepicWrappers.Step step = EduStepicConnector.getStep(stepId); if (step.name.equals("code")) { - return getTaskFromStep(LESSON_DEFAULT_NAME + lessonId, stepId, step); + return getTaskFromStep(realLesson.getName(), stepId, step); } } } From 6704740e1198be20cd440b49bce917d911495363 Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Fri, 22 Apr 2016 16:12:27 +0300 Subject: [PATCH 10/44] Store stepic settings in project settings Cache adaptive course for every user --- .../student/resources/META-INF/plugin.xml | 2 - .../edu/learning/StudyTaskManager.java | 39 ++++++- .../jetbrains/edu/learning/StudyUtils.java | 32 +++++- .../StudyRefreshAnswerPlaceholder.java | 6 +- .../actions/StudyRefreshTaskFileAction.java | 12 +-- .../edu/learning/checker/StudyCheckTask.java | 5 +- .../StudyProjectGenerator.java | 45 ++++---- .../stepic/EduAdaptiveStepicConnector.java | 38 +++---- .../learning/stepic/EduStepicConnector.java | 48 ++++----- .../edu/learning/stepic/LoginDialog.java | 10 +- .../learning/stepic/StepicStudyOptions.java | 38 +++++-- .../edu/learning/stepic/StepicUser.java | 62 ++++++----- .../edu/learning/stepic/StudySettings.java | 101 ------------------ .../edu/learning/ui/StudyNewProjectPanel.java | 22 ++-- 14 files changed, 229 insertions(+), 231 deletions(-) delete mode 100644 python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StudySettings.java diff --git a/python/educational-core/student/resources/META-INF/plugin.xml b/python/educational-core/student/resources/META-INF/plugin.xml index 9cd1dc9e0b1b..2d5b272bd2bb 100644 --- a/python/educational-core/student/resources/META-INF/plugin.xml +++ b/python/educational-core/student/resources/META-INF/plugin.xml @@ -82,8 +82,6 @@ - diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/StudyTaskManager.java b/python/educational-core/student/src/com/jetbrains/edu/learning/StudyTaskManager.java index 6d682f4af732..40da8f72175c 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/StudyTaskManager.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/StudyTaskManager.java @@ -14,6 +14,7 @@ import com.intellij.util.xmlb.XmlSerializer; import com.jetbrains.edu.learning.core.EduUtils; import com.jetbrains.edu.learning.courseFormat.*; import com.jetbrains.edu.learning.oldCourseFormat.OldCourse; +import com.jetbrains.edu.learning.stepic.StepicUser; import org.jdom.Element; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -33,6 +34,7 @@ public class StudyTaskManager implements PersistentStateComponent, Dumb private Course myCourse; private OldCourse myOldCourse; public int VERSION = 2; + public StepicUser myUser; public Map myStudyStatusMap = new HashMap<>(); public Map myTaskStatusMap = new HashMap<>(); public Map> myUserTests = new HashMap<>(); @@ -70,7 +72,7 @@ public class StudyTaskManager implements PersistentStateComponent, Dumb @NotNull public List getUserTests(@NotNull final Task task) { final List userTests = myUserTests.get(task); - return userTests != null ? userTests : Collections.emptyList(); + return userTests != null ? userTests : Collections.emptyList(); } public void removeUserTest(@NotNull final Task task, @NotNull final UserTest userTest) { @@ -207,6 +209,7 @@ public class StudyTaskManager implements PersistentStateComponent, Dumb myInvisibleFiles = taskManager.myInvisibleFiles; myTaskStatusMap = taskManager.myTaskStatusMap; myStudyStatusMap = taskManager.myStudyStatusMap; + myUser = taskManager.getUser(); } } final Element oldCourseElement = state.getChild(COURSE_ELEMENT); @@ -250,4 +253,38 @@ public class StudyTaskManager implements PersistentStateComponent, Dumb public boolean isInvisibleFile(String path) { return myInvisibleFiles.contains(path); } + + public String getLogin() { + if (myUser != null) { + return myUser.getEmail(); + } + return ""; + } + + public String getPassword() { + if (myUser != null) { + return myUser.getPassword(); + } + return ""; + } + + public void setLogin(String login) { + if (myUser != null) { + myUser.setEmail(login); + } + } + + public void setPassword(String password) { + if (myUser != null) { + myUser.setPassword(password); + } + } + + public StepicUser getUser() { + return myUser; + } + + public void setUser(StepicUser user) { + myUser = user; + } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java b/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java index 8ed23fb768d3..6a233d40602c 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java @@ -21,6 +21,7 @@ import com.intellij.openapi.fileEditor.FileEditor; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx; import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.ui.popup.Balloon; import com.intellij.openapi.util.Disposer; @@ -43,6 +44,7 @@ import com.jetbrains.edu.learning.core.EduAnswerPlaceholderPainter; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.core.EduUtils; import com.jetbrains.edu.learning.courseFormat.*; +import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator; import com.jetbrains.edu.learning.editor.StudyEditor; import com.jetbrains.edu.learning.ui.StudyProgressToolWindowFactory; import com.jetbrains.edu.learning.ui.StudyToolWindow; @@ -354,12 +356,12 @@ public class StudyUtils { } @Nullable - public static Document getPatternDocument(@NotNull final TaskFile taskFile, String name) { + public static Document getPatternDocument(@NotNull final Project project, @NotNull final TaskFile taskFile, String name) { Task task = taskFile.getTask(); String lessonDir = EduNames.LESSON + String.valueOf(task.getLesson().getIndex()); String taskDir = EduNames.TASK + String.valueOf(task.getIndex()); Course course = task.getLesson().getCourse(); - File resourceFile = new File(course.getCourseDirectory()); + File resourceFile = getCourseDirectory(project, course); if (!resourceFile.exists()) { return null; } @@ -502,4 +504,30 @@ public class StudyUtils { public static boolean isStudyProject(@NotNull Project project) { return StudyTaskManager.getInstance(project).getCourse() != null; } + + @Nullable + public static Project getStudyProject() { + Project studyProject = null; + Project[] openProjects = ProjectManager.getInstance().getOpenProjects(); + for (Project project : openProjects) { + if (StudyTaskManager.getInstance(project).getCourse() != null) { + studyProject = project; + } + } + return studyProject; + } + + @NotNull + public static File getCourseDirectory(@NotNull Project project, Course course) { + final File courseDirectory; + if (course.isAdaptive()) { + courseDirectory = new File(StudyProjectGenerator.ourCoursesDir, + StudyProjectGenerator.ADAPTIVE_COURSE_PREFIX + course.getName() + + "_" + StudyTaskManager.getInstance(project).getUser().getEmail()); + } + else { + courseDirectory = new File(StudyProjectGenerator.ourCoursesDir, course.getName()); + } + return courseDirectory; + } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshAnswerPlaceholder.java b/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshAnswerPlaceholder.java index 42a7674c384b..163610e56079 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshAnswerPlaceholder.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshAnswerPlaceholder.java @@ -9,10 +9,10 @@ import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; -import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; -import com.jetbrains.edu.learning.courseFormat.TaskFile; import com.jetbrains.edu.learning.StudyState; import com.jetbrains.edu.learning.StudyUtils; +import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; +import com.jetbrains.edu.learning.courseFormat.TaskFile; import com.jetbrains.edu.learning.editor.StudyEditor; import org.jetbrains.annotations.Nullable; @@ -36,7 +36,7 @@ public class StudyRefreshAnswerPlaceholder extends DumbAwareAction { } StudyEditor studyEditor = StudyUtils.getSelectedStudyEditor(project); final StudyState studyState = new StudyState(studyEditor); - Document patternDocument = StudyUtils.getPatternDocument(answerPlaceholder.getTaskFile(), studyState.getVirtualFile().getName()); + Document patternDocument = StudyUtils.getPatternDocument(project, answerPlaceholder.getTaskFile(), studyState.getVirtualFile().getName()); if (patternDocument == null) { return; } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshTaskFileAction.java b/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshTaskFileAction.java index 110feeee370f..e68daec27996 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshTaskFileAction.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshTaskFileAction.java @@ -19,13 +19,13 @@ import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.problems.WolfTheProblemSolver; +import com.jetbrains.edu.learning.StudyState; +import com.jetbrains.edu.learning.StudyTaskManager; +import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.core.EduAnswerPlaceholderPainter; import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; import com.jetbrains.edu.learning.courseFormat.StudyStatus; import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.StudyState; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.editor.StudyEditor; import com.jetbrains.edu.learning.navigation.StudyNavigator; import org.jetbrains.annotations.NotNull; @@ -89,7 +89,7 @@ public class StudyRefreshTaskFileAction extends StudyToolbarAction { @NotNull final Project project, TaskFile taskFile, String name) { - if (!resetDocument(document, taskFile, name)) { + if (!resetDocument(project, document, taskFile, name)) { return false; } resetAnswerPlaceholders(taskFile, project); @@ -117,10 +117,10 @@ public class StudyRefreshTaskFileAction extends StudyToolbarAction { } - private static boolean resetDocument(@NotNull final Document document, + private static boolean resetDocument(@NotNull final Project project, @NotNull final Document document, @NotNull final TaskFile taskFile, String fileName) { - final Document patternDocument = StudyUtils.getPatternDocument(taskFile, fileName); + final Document patternDocument = StudyUtils.getPatternDocument(project, taskFile, fileName); if (patternDocument == null) { return false; } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java index 4db6ae231823..29a4011c9306 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java @@ -24,7 +24,6 @@ import com.jetbrains.edu.learning.courseFormat.StudyStatus; import com.jetbrains.edu.learning.courseFormat.Task; import com.jetbrains.edu.learning.stepic.EduAdaptiveStepicConnector; import com.jetbrains.edu.learning.stepic.EduStepicConnector; -import com.jetbrains.edu.learning.stepic.StudySettings; import org.jetbrains.annotations.NotNull; public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroundable { @@ -122,7 +121,7 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun ProgressManager.getInstance().runProcessWithProgressAsynchronously(new Backgroundable(myProject, "Checking Task") { @Override public void run(@NotNull ProgressIndicator indicator) { - final Pair pair = EduAdaptiveStepicConnector.checkTask(myTask, myProject); + final Pair pair = EduAdaptiveStepicConnector.checkTask(myProject, myTask); if (pair != null) { final String checkMessage = pair.getSecond(); if (pair.getFirst()) { @@ -171,7 +170,7 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun } protected void postAttemptToStepic(StudyTestsOutputParser.TestsOutput testsOutput) { - final StudySettings studySettings = StudySettings.getInstance(); + final StudyTaskManager studySettings = StudyTaskManager.getInstance(myProject); final String login = studySettings.getLogin(); final String password = StringUtil.isEmptyOrSpaces(login) ? "" : studySettings.getPassword(); EduStepicConnector.postAttempt(myTask, testsOutput.isSuccess(), login, password); diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java b/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java index a1afc0290bc5..66c9c0152e3c 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java @@ -45,16 +45,18 @@ import java.util.List; import java.util.Map; public class StudyProjectGenerator { - private static final Logger LOG = Logger.getInstance(StudyProjectGenerator.class.getName()); - private final List myListeners = ContainerUtil.newArrayList(); - protected static final File ourCoursesDir = new File(PathManager.getConfigPath(), "courses"); - private static final String CACHE_NAME = "courseNames.txt"; - private List myCourses = new ArrayList<>(); - protected CourseInfo mySelectedCourseInfo; - private static final String COURSE_NAME_ATTRIBUTE = "name"; - private static final String COURSE_DESCRIPTION = "description"; public static final String AUTHOR_ATTRIBUTE = "authors"; public static final String LANGUAGE_ATTRIBUTE = "language"; + public static final String ADAPTIVE_COURSE_PREFIX = "__AdaptivePyCharmPython__"; + public static final File ourCoursesDir = new File(PathManager.getConfigPath(), "courses"); + private static final Logger LOG = Logger.getInstance(StudyProjectGenerator.class.getName()); + private static final String COURSE_NAME_ATTRIBUTE = "name"; + private static final String COURSE_DESCRIPTION = "description"; + private static final String CACHE_NAME = "courseNames.txt"; + private final List myListeners = ContainerUtil.newArrayList(); + public StepicUser myUser; + private List myCourses = new ArrayList<>(); + protected CourseInfo mySelectedCourseInfo; public void setCourses(List courses) { myCourses = courses; @@ -65,7 +67,8 @@ public class StudyProjectGenerator { } public void generateProject(@NotNull final Project project, @NotNull final VirtualFile baseDir) { - final Course course = getCourse(); + StudyTaskManager.getInstance(project).setUser(myUser); + final Course course = getCourse(project); if (course == null) { LOG.warn("Course is null"); return; @@ -75,8 +78,8 @@ public class StudyProjectGenerator { () -> DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, () -> ApplicationManager.getApplication().runWriteAction(() -> { course.initCourse(false); - final File courseDirectory = new File(ourCoursesDir, course.getName()); - StudyGenerator.createCourse(course, baseDir, courseDirectory, project); + StudyGenerator.createCourse(course, baseDir, StudyUtils + .getCourseDirectory(project, course), project); course.setCourseDirectory(new File(ourCoursesDir, mySelectedCourseInfo.getName()).getAbsolutePath()); VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); StudyProjectComponent.getInstance(project).registerStudyToolWindow(course); @@ -84,7 +87,7 @@ public class StudyProjectGenerator { }))); } - protected Course getCourse() { + protected Course getCourse(@NotNull final Project project) { Reader reader = null; try { final File courseFile = new File(new File(ourCoursesDir, mySelectedCourseInfo.getName()), EduNames.COURSE_META_FILE); @@ -102,9 +105,9 @@ public class StudyProjectGenerator { finally { StudyUtils.closeSilently(reader); } - final Course course = EduStepicConnector.getCourse(mySelectedCourseInfo); + final Course course = EduStepicConnector.getCourse(project, mySelectedCourseInfo, myUser.getEmail()); if (course != null) { - flushCourse(course); + flushCourse(project, course); } return course; } @@ -143,8 +146,8 @@ public class StudyProjectGenerator { } } - public void flushCourse(@NotNull final Course course) { - final File courseDirectory = new File(ourCoursesDir, course.getName()); + public void flushCourse(@NotNull final Project project, @NotNull final Course course) { + final File courseDirectory = StudyUtils.getCourseDirectory(project, course); FileUtil.createDirectory(courseDirectory); flushCourseJson(course, courseDirectory); @@ -361,7 +364,9 @@ public class StudyProjectGenerator { while ((line = reader.readLine()) != null) { Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); final CourseInfo courseInfo = gson.fromJson(line, CourseInfo.class); - courses.add(courseInfo); + if (!courseInfo.isAdaptive()) { + courses.add(courseInfo); + } } } catch (IOException | JsonSyntaxException e) { @@ -395,7 +400,9 @@ public class StudyProjectGenerator { CourseInfo courseName = addCourse(myCourses, courseDir); flushCache(myCourses); if (courseName != null && !courseName.getName().equals(unzippedName)) { + //noinspection ResultOfMethodCallIgnored courseDir.renameTo(new File(ourCoursesDir, courseName.getName())); + //noinspection ResultOfMethodCallIgnored courseDir.delete(); } return courseName; @@ -471,8 +478,8 @@ public class StudyProjectGenerator { for (JsonElement author : courseAuthors) { final JsonObject authorAsJsonObject = author.getAsJsonObject(); final StepicUser stepicUser = new StepicUser(); - stepicUser.setFirst_name(authorAsJsonObject.get("first_name").getAsString()); - stepicUser.setLast_name(authorAsJsonObject.get("last_name").getAsString()); + stepicUser.setFirstName(authorAsJsonObject.get("first_name").getAsString()); + stepicUser.setLastName(authorAsJsonObject.get("last_name").getAsString()); authors.add(stepicUser); } courseInfo.setAuthors(authors); diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java index 2b87cfc5b479..01e3a63290e2 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java @@ -55,15 +55,13 @@ public class EduAdaptiveStepicConnector { private static final String CONTENT_TYPE_APPL_JSON = "application/json"; private static final String LESSON_URL = "lessons/"; private static final String RECOMMENDATION_REACTIONS_URL = "recommendation-reactions"; - private static final String LESSON_DEFAULT_NAME = "Adaptive"; private static final String ATTEMPTS_URL = "attempts"; private static final String SUBMISSION_URL = "submissions"; @Nullable - public static Task getNextRecommendation(@NotNull Course course) { - + public static Task getNextRecommendation(@NotNull final Project project, @NotNull Course course) { try { - final CloseableHttpClient client = getHttpClient(); + final CloseableHttpClient client = getHttpClient(project); final URI uri = new URIBuilder(STEPIC_API_URL + RECOMMENDATIONS_URL).addParameter("course", String.valueOf(course.getId())).build(); final HttpGet request = new HttpGet(uri); setHeaders(request, CONTENT_TYPE_APPL_JSON); @@ -106,13 +104,14 @@ public class EduAdaptiveStepicConnector { return null; } - public static boolean postRecommendationReaction(int reaction, @NotNull final String user, @NotNull final String lessonId) { + public static boolean postRecommendationReaction(@NotNull final Project project, @NotNull final String lessonId, + @NotNull final String user,int reaction) { final HttpPost post = new HttpPost(STEPIC_API_URL + RECOMMENDATION_REACTIONS_URL); final String json = new Gson() .toJson(new StepicWrappers.RecommendationReactionWrapper(new StepicWrappers.RecommendationReaction(reaction, user, lessonId))); post.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON)); - final CloseableHttpClient client = getHttpClient(); + final CloseableHttpClient client = getHttpClient(project); setHeaders(post, CONTENT_TYPE_APPL_JSON); try { final CloseableHttpResponse execute = client.execute(post); @@ -124,15 +123,16 @@ public class EduAdaptiveStepicConnector { return false; } - public static void addNextRecommendedTask(@NotNull Project project, int reaction) { + public static void addNextRecommendedTask(@NotNull final Project project, int reaction) { final StudyEditor editor = StudyUtils.getSelectedStudyEditor(project); final Course course = StudyTaskManager.getInstance(project).getCourse(); if (course != null && editor != null && editor.getTaskFile() != null) { // TODO: get user from settings - final StepicUser user = StudySettings.getInstance().getUser(); + final StepicUser user = StudyTaskManager.getInstance(project).getUser(); if (user != null && - postRecommendationReaction(reaction, String.valueOf(user.id), String.valueOf(editor.getTaskFile().getTask().getLesson().id))) { - final Task task = getNextRecommendation(course); + postRecommendationReaction(project, String.valueOf(editor.getTaskFile().getTask().getLesson().id), + String.valueOf(user.id), reaction)) { + final Task task = getNextRecommendation(project, course); if (task != null) { final Lesson adaptive = course.getLessons().get(0); @@ -213,9 +213,9 @@ public class EduAdaptiveStepicConnector { } @Nullable - public static Pair checkTask(@NotNull final Task task, @NotNull final Project project) { + public static Pair checkTask(@NotNull final Project project, @NotNull final Task task) { try { - final int attemptId = getAttemptId(task, ATTEMPTS_URL); + final int attemptId = getAttemptId(project, task, ATTEMPTS_URL); final Editor editor = StudyUtils.getSelectedEditor(project); String language = getLanguageString(task, project); if (editor != null && language != null) { @@ -223,13 +223,13 @@ public class EduAdaptiveStepicConnector { new StepicWrappers.SubmissionToPostWrapper(String.valueOf(attemptId), "python3", editor.getDocument().getText()); final HttpPost httpPost = new HttpPost(STEPIC_API_URL + SUBMISSION_URL); httpPost.setEntity(new StringEntity(new Gson().toJson(submissionToPostWrapper))); - final CloseableHttpClient client = getHttpClient(); + final CloseableHttpClient client = getHttpClient(project); setHeaders(httpPost, CONTENT_TYPE_APPL_JSON); final CloseableHttpResponse execute = client.execute(httpPost); StepicWrappers.ResultSubmissionWrapper wrapper = new Gson().fromJson(EntityUtils.toString(execute.getEntity()), StepicWrappers.ResultSubmissionWrapper.class); - final StepicUser user = StudySettings.getInstance().getUser(); + final StepicUser user = StudyTaskManager.getInstance(project).getUser(); if (user != null) { final int id = user.getId(); while (wrapper.submissions.length == 1 && wrapper.submissions[0].status.equals("evaluation")) { @@ -244,8 +244,10 @@ public class EduAdaptiveStepicConnector { final CloseableHttpResponse httpResponse = client.execute(httpGet); wrapper = new Gson().fromJson(EntityUtils.toString(httpResponse.getEntity()), StepicWrappers.ResultSubmissionWrapper.class); } - final boolean isSolved = wrapper.submissions.length == 1 && !wrapper.submissions[0].status.equals("wrong"); - return Pair.create(isSolved, wrapper.submissions[0].hint); + if (wrapper.submissions.length == 1) { + final boolean isSolved = !wrapper.submissions[0].status.equals("wrong"); + return Pair.create(isSolved, wrapper.submissions[0].hint); + } } } } @@ -281,13 +283,13 @@ public class EduAdaptiveStepicConnector { return null; } - private static int getAttemptId(@NotNull Task task, String attempts) throws IOException { + private static int getAttemptId(@NotNull final Project project, @NotNull Task task, @NotNull final String attempts) throws IOException { final StepicWrappers.AttemptToPostWrapper attemptWrapper = new StepicWrappers.AttemptToPostWrapper(task.getStepicId()); final HttpPost post = new HttpPost(STEPIC_API_URL + attempts); post.setEntity(new StringEntity(new Gson().toJson(attemptWrapper))); - final CloseableHttpClient client = getHttpClient(); + final CloseableHttpClient client = getHttpClient(project); setHeaders(post, CONTENT_TYPE_APPL_JSON); final CloseableHttpResponse httpResponse = client.execute(post); final StepicWrappers.AttemptContainer container = diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java index 5e36808a8c0d..7e331d940a9c 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java @@ -16,6 +16,7 @@ import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileFilter; import com.intellij.util.net.ssl.CertificateManager; +import com.jetbrains.edu.learning.StudyTaskManager; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.core.EduUtils; import com.jetbrains.edu.learning.courseFormat.Course; @@ -51,12 +52,13 @@ import java.security.cert.X509Certificate; import java.util.*; public class EduStepicConnector { + private static final Logger LOG = Logger.getInstance(EduStepicConnector.class.getName()); private static final String stepicUrl = "https://stepic.org/"; private static final String stepicApiUrl = stepicUrl + "api/"; - private static final Logger LOG = Logger.getInstance(EduStepicConnector.class.getName()); private static String ourCSRFToken = ""; private static CloseableHttpClient ourClient; + //this prefix indicates that course can be opened by educational plugin public static final String PYCHARM_PREFIX = "pycharm"; private static BasicCookieStore ourCookieStore; @@ -64,17 +66,15 @@ public class EduStepicConnector { private EduStepicConnector() { } - public static boolean login(@NotNull final String username, @NotNull final String password) { + public static StepicUser login(@NotNull final String username, @NotNull final String password) { initializeClient(); if (postCredentials(username, password)) { final StepicWrappers.AuthorWrapper stepicUserWrapper = getCurrentUser(); if (stepicUserWrapper != null && stepicUserWrapper.users.size() == 1) { - final StepicUser user = stepicUserWrapper.users.get(0); - StudySettings.getInstance().setUser(user); + return stepicUserWrapper.users.get(0); } - return true; } - return false; + return null; } @Nullable @@ -210,9 +210,9 @@ public class EduStepicConnector { } @NotNull - public static CloseableHttpClient getHttpClient() { + public static CloseableHttpClient getHttpClient(@NotNull final Project project) { if (ourClient == null) { - login(); + login(project); initializeClient(); } return ourClient; @@ -242,9 +242,8 @@ public class EduStepicConnector { final String courseType = info.getType(); if (!info.isAdaptive() && StringUtil.isEmptyOrSpaces(courseType)) continue; final List typeLanguage = StringUtil.split(courseType, " "); - // TODO: should adaptive course be of PyCharmType + // TODO: should adaptive course be of PyCharmType ? if (info.isAdaptive() || (typeLanguage.size() == 2 && PYCHARM_PREFIX.equals(typeLanguage.get(0)))) { - for (Integer instructor : info.instructors) { final StepicUser author = getFromStepic("users/" + String.valueOf(instructor), StepicWrappers.AuthorWrapper.class).users.get(0); info.addAuthor(author); @@ -256,17 +255,17 @@ public class EduStepicConnector { return coursesContainer.meta.containsKey("has_next") && coursesContainer.meta.get("has_next") == Boolean.TRUE; } - public static Course getCourse(@NotNull final CourseInfo info) { + public static Course getCourse(@NotNull final Project project, @NotNull final CourseInfo info, @NotNull final String username) { final Course course = new Course(); course.setAuthors(info.getAuthors()); course.setDescription(info.getDescription()); - course.setName(info.getName()); course.setAdaptive(info.isAdaptive()); course.setId(info.id); course.setUpToDate(true); // TODO: get from stepic if (!course.isAdaptive()) { String courseType = info.getType(); + course.setName(info.getName()); course.setLanguage(courseType.substring(PYCHARM_PREFIX.length() + 1)); try { for (Integer section : info.sections) { @@ -280,10 +279,11 @@ public class EduStepicConnector { } else { final Lesson lesson = new Lesson(); + course.setName(info.getName()); //TODO: more specific name? lesson.setName("Adaptive"); course.addLesson(lesson); - final Task recommendation = EduAdaptiveStepicConnector.getNextRecommendation(course); + final Task recommendation = EduAdaptiveStepicConnector.getNextRecommendation(project, course); if (recommendation != null) { lesson.addTask(recommendation); } @@ -364,8 +364,7 @@ public class EduStepicConnector { return; } else { - final boolean success = login(login, password); - if (!success) return; + if (login(login, password) == null) return; } } @@ -428,7 +427,7 @@ public class EduStepicConnector { indicator.setText("Uploading course to " + stepicUrl); final HttpPost request = new HttpPost(stepicApiUrl + "courses"); if (ourClient == null || !relogin) { - if (!login()) return; + if (!login(project)) return; } final StepicWrappers.AuthorWrapper user = getCurrentUser(); if (user != null) { @@ -446,7 +445,7 @@ public class EduStepicConnector { final StatusLine line = response.getStatusLine(); if (line.getStatusCode() != HttpStatus.SC_CREATED) { if (!relogin) { - login(); + login(project); postCourse(project, course, true, indicator); } LOG.error("Failed to push " + responseString); @@ -474,14 +473,13 @@ public class EduStepicConnector { } } - private static boolean login() { - final String login = StudySettings.getInstance().getLogin(); + private static boolean login(@NotNull final Project project) { + final String login = StudyTaskManager.getInstance(project).getLogin(); if (StringUtil.isEmptyOrSpaces(login)) { return showLoginDialog(); } else { - boolean success = login(login, StudySettings.getInstance().getPassword()); - if (!success) { + if (login(login, StudyTaskManager.getInstance(project).getPassword()) == null) { return showLoginDialog(); } } @@ -586,10 +584,10 @@ public class EduStepicConnector { return -1; } - public static int updateLesson(Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) { + public static int updateLesson(@NotNull final Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) { final HttpPut request = new HttpPut(stepicApiUrl + "lessons/" + String.valueOf(lesson.id)); if (ourClient == null) { - if (!login()) { + if (!login(project)) { LOG.error("Failed to push lesson"); return 0; } @@ -625,10 +623,10 @@ public class EduStepicConnector { return -1; } - public static int postLesson(Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) { + public static int postLesson(@NotNull final Project project, @NotNull final Lesson lesson, ProgressIndicator indicator) { final HttpPost request = new HttpPost(stepicApiUrl + "lessons"); if (ourClient == null) { - login(); + login(project); } setHeaders(request, "application/json"); diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/LoginDialog.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/LoginDialog.java index bb95494d1540..c4805500fe09 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/LoginDialog.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/LoginDialog.java @@ -40,14 +40,12 @@ public class LoginDialog extends DialogWrapper { @Override protected void doOKAction() { AuthDataHolder authData = myLoginPanel.getAuthData(); - final boolean success = EduStepicConnector.login(authData.email, authData.password); - if (!success) { - setErrorText("Login failed"); + final StepicUser user = EduStepicConnector.login(authData.email, authData.password); + if (user != null) { + super.doOKAction(); } else { - StudySettings.getInstance().setLogin(authData.email); - StudySettings.getInstance().setPassword(authData.password); - super.doOKAction(); + setErrorText("Login failed"); } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicStudyOptions.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicStudyOptions.java index c3994496fef7..097c4a64ef9c 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicStudyOptions.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicStudyOptions.java @@ -15,8 +15,12 @@ */ package com.jetbrains.edu.learning.stepic; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; import com.intellij.ui.DocumentAdapter; +import com.jetbrains.edu.learning.StudyTaskManager; +import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.settings.StudyOptionsProvider; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -31,6 +35,7 @@ import java.awt.event.FocusListener; public class StepicStudyOptions implements StudyOptionsProvider { private static final String DEFAULT_PASSWORD_TEXT = "************"; + private static final Logger LOG = Logger.getInstance(StepicStudyOptions.class); private JTextField myLoginTextField; private JPasswordField myPasswordField; private JPanel myPane; @@ -98,13 +103,19 @@ public class StepicStudyOptions implements StudyOptionsProvider { myPasswordField.setText(StringUtil.isEmpty(password) ? null : password); } - + @Override public void reset() { - final StudySettings studySettings = StudySettings.getInstance(); - setLogin(studySettings.getLogin()); - setPassword(DEFAULT_PASSWORD_TEXT); + Project project = StudyUtils.getStudyProject(); + if (project != null) { + final StudyTaskManager studySettings = StudyTaskManager.getInstance(project); + setLogin(studySettings.getLogin()); + setPassword(DEFAULT_PASSWORD_TEXT); - resetCredentialsModification(); + resetCredentialsModification(); + } + else { + LOG.warn("No study object is opened"); + } } @Override @@ -112,13 +123,20 @@ public class StepicStudyOptions implements StudyOptionsProvider { } + @Override public void apply() { if (myCredentialsModified) { - final StudySettings studySettings = StudySettings.getInstance(); - studySettings.setLogin(getLogin()); - studySettings.setPassword(getPassword()); - if (!StringUtil.isEmptyOrSpaces(getLogin()) && !StringUtil.isEmptyOrSpaces(getPassword())) { - EduStepicConnector.login(getLogin(), getPassword()); + final Project project = StudyUtils.getStudyProject(); + if (project != null) { + final StudyTaskManager studyTaskManager = StudyTaskManager.getInstance(project); + studyTaskManager.setLogin(getLogin()); + studyTaskManager.setPassword(getPassword()); + if (!StringUtil.isEmptyOrSpaces(getLogin()) && !StringUtil.isEmptyOrSpaces(getPassword())) { + EduStepicConnector.login(getLogin(), getPassword()); + } + } + else { + LOG.warn("No study object is opened"); } } resetCredentialsModification(); diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicUser.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicUser.java index 05365f40ae8e..ff40e3428d37 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicUser.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicUser.java @@ -1,29 +1,26 @@ package com.jetbrains.edu.learning.stepic; +import com.intellij.ide.passwordSafe.PasswordSafe; +import com.intellij.ide.passwordSafe.PasswordSafeException; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.text.StringUtil; +import com.jetbrains.edu.learning.StudyTaskManager; public class StepicUser { + private static final String STEPIC_SETTINGS_PASSWORD_KEY = "STEPIC_SETTINGS_PASSWORD_KEY"; + private static final Logger LOG = Logger.getInstance(StepicUser.class); int id; - String first_name; + String firstName; String last_name; String email; - String password; - - public StepicUser(int id, String first_name, String last_name, String email, String password) { - this.id = id; - this.first_name = first_name; - this.last_name = last_name; - this.email = email; - this.password = password; - } - - public StepicUser(String email, String password) { - this.email = email; - this.password = password; - } public StepicUser() { } + + public StepicUser(String email, String password) { + this.email = email; + setPassword(password); + } public int getId() { return id; @@ -33,19 +30,19 @@ public class StepicUser { this.id = id; } - public String getFirst_name() { - return first_name; + public String getFirstName() { + return firstName; } - public void setFirst_name(String first_name) { - this.first_name = first_name; + public void setFirstName(String firstName) { + this.firstName = firstName; } - public String getLast_name() { + public String getLastName() { return last_name; } - public void setLast_name(String last_name) { + public void setLastName(String last_name) { this.last_name = last_name; } @@ -58,14 +55,31 @@ public class StepicUser { } public String getPassword() { - return password; + final String login = getEmail(); + if (StringUtil.isEmptyOrSpaces(login)) return ""; + + String password; + try { + password = PasswordSafe.getInstance().getPassword(null, StudyTaskManager.class, STEPIC_SETTINGS_PASSWORD_KEY + login); + } + catch (PasswordSafeException e) { + LOG.info("Couldn't get password for key [" + STEPIC_SETTINGS_PASSWORD_KEY + "]", e); + password = ""; + } + + return StringUtil.notNullize(password); } public void setPassword(String password) { - this.password = password; + try { + PasswordSafe.getInstance().storePassword(null, StudyTaskManager.class, STEPIC_SETTINGS_PASSWORD_KEY + getEmail(), password); + } + catch (PasswordSafeException e) { + LOG.info("Couldn't set password for key [" + STEPIC_SETTINGS_PASSWORD_KEY + getEmail() + "]", e); + } } public String getName() { - return StringUtil.join(new String[]{first_name, last_name}, " "); + return StringUtil.join(new String[]{firstName, last_name}, " "); } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StudySettings.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StudySettings.java deleted file mode 100644 index c07279896732..000000000000 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StudySettings.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2000-2015 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.jetbrains.edu.learning.stepic; - -import com.intellij.ide.passwordSafe.PasswordSafe; -import com.intellij.ide.passwordSafe.PasswordSafeException; -import com.intellij.openapi.components.PersistentStateComponent; -import com.intellij.openapi.components.ServiceManager; -import com.intellij.openapi.components.State; -import com.intellij.openapi.components.Storage; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.util.text.StringUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -@SuppressWarnings("MethodMayBeStatic") -@State(name = "StudySettings", storages = @Storage("stepic_settings.xml")) -public class StudySettings implements PersistentStateComponent { - - private State myState = new State(); - - public static class State { - @Nullable public String LOGIN = null; - @Nullable public StepicUser myUser = null; - } - - private static final String STEPIC_SETTINGS_PASSWORD_KEY = "STEPIC_SETTINGS_PASSWORD_KEY"; - private static final Logger LOG = Logger.getInstance(StudySettings.class.getName()); - - public static StudySettings getInstance() { - return ServiceManager.getService(StudySettings.class); - } - - @NotNull - public String getPassword() { - final String login = getLogin(); - if (StringUtil.isEmptyOrSpaces(login)) return ""; - - String password; - try { - password = PasswordSafe.getInstance().getPassword(null, StudySettings.class, STEPIC_SETTINGS_PASSWORD_KEY); - } - catch (PasswordSafeException e) { - LOG.info("Couldn't get password for key [" + STEPIC_SETTINGS_PASSWORD_KEY + "]", e); - password = ""; - } - - return StringUtil.notNullize(password); - } - - public void setPassword(@NotNull String password) { - try { - PasswordSafe.getInstance().storePassword(null, StudySettings.class, STEPIC_SETTINGS_PASSWORD_KEY, password); - } - catch (PasswordSafeException e) { - LOG.info("Couldn't set password for key [" + STEPIC_SETTINGS_PASSWORD_KEY + "]", e); - } - } - - @Nullable - public String getLogin() { - return myState.LOGIN; - } - - public void setLogin(@Nullable String login) { - myState.LOGIN = login; - } - - @Nullable - public StepicUser getUser() { - return myState.myUser; - } - - public void setUser(@NotNull final StepicUser user) { - myState.myUser = user; - } - - @Nullable - @Override - public StudySettings.State getState() { - return myState; - } - - @Override - public void loadState(StudySettings.State state) { - myState = state; - } -} \ No newline at end of file diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java index c9bc67faaae6..2c8ee7708bb1 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java @@ -22,7 +22,7 @@ import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator; import com.jetbrains.edu.learning.stepic.CourseInfo; import com.jetbrains.edu.learning.stepic.EduStepicConnector; -import com.jetbrains.edu.learning.stepic.StudySettings; +import com.jetbrains.edu.learning.stepic.StepicUser; import icons.InteractiveLearningIcons; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -277,10 +277,10 @@ public class StudyNewProjectPanel { super.doOKAction(); final ProgressManager progressManager = ProgressManager.getInstance(); progressManager.runProcessWithProgressSynchronously(() -> { - final Future future = ApplicationManager.getApplication().executeOnPooledThread( - new Callable() { + final Future future = ApplicationManager.getApplication().executeOnPooledThread( + new Callable() { @Override - public Boolean call() throws Exception { + public StepicUser call() throws Exception { return EduStepicConnector.login(myRemoteCourse.getLogin(), myRemoteCourse.getPassword()); } }); @@ -296,15 +296,15 @@ public class StudyNewProjectPanel { } try { - final boolean isSuccess = future.get(); - - if (!isSuccess) { - setError("Failed to log in"); + final StepicUser stepicUser = future.get(); + if (stepicUser != null) { + stepicUser.setEmail(myRemoteCourse.getLogin()); + stepicUser.setPassword(myRemoteCourse.getPassword()); + myGenerator.myUser = stepicUser; + ApplicationManager.getApplication().invokeLater(StudyNewProjectPanel.this::refreshCoursesList); } else { - StudySettings.getInstance().setLogin(myRemoteCourse.getLogin()); - StudySettings.getInstance().setPassword(myRemoteCourse.getPassword()); - ApplicationManager.getApplication().invokeLater(StudyNewProjectPanel.this::refreshCoursesList); + setError("Failed to log in"); } } catch (InterruptedException e) { From e3a79e71dff4b2ba2757b513d808540a5edfaca4 Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Tue, 26 Apr 2016 16:25:17 +0200 Subject: [PATCH 11/44] Fix task description panel layout problems --- .../com/jetbrains/edu/learning/ui/StudyToolWindow.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java index 87b0930f33e0..054b2d0a35ab 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java @@ -29,7 +29,6 @@ import com.intellij.openapi.progress.util.ProgressIndicatorBase; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectUtil; import com.intellij.openapi.ui.SimpleToolWindowPanel; -import com.intellij.openapi.ui.VerticalFlowLayout; import com.intellij.ui.JBCardLayout; import com.intellij.ui.JBColor; import com.intellij.ui.OnePixelSplitter; @@ -70,12 +69,13 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D JPanel toolbarPanel = createToolbarPanel(project); setToolbar(toolbarPanel); - final JPanel panel = new JPanel(new VerticalFlowLayout()); + final JPanel panel = new JPanel(new BorderLayout()); final Course course = StudyTaskManager.getInstance(project).getCourse(); if (course != null && course.isAdaptive()) { - panel.add(createReactionPanel()); + panel.add(createReactionPanel(), BorderLayout.NORTH); } - panel.add(createTaskInfoPanel(taskText, project)); + JComponent taskInfoPanel = createTaskInfoPanel(taskText, project); + panel.add(taskInfoPanel, BorderLayout.CENTER); myContentPanel.add(TASK_INFO_ID, panel); mySplitPane.setFirstComponent(myContentPanel); addAdditionalPanels(project); From 21b64c480904189a9a514bff6e0eb2050fd35eb5 Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Tue, 26 Apr 2016 16:53:13 +0200 Subject: [PATCH 12/44] Minor cleanup --- .../edu/learning/stepic/EduAdaptiveStepicConnector.java | 6 ++++-- .../jetbrains/edu/learning/ui/StudyJavaFxToolWindow.java | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java index 01e3a63290e2..ee80ac733d98 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java @@ -62,7 +62,9 @@ public class EduAdaptiveStepicConnector { public static Task getNextRecommendation(@NotNull final Project project, @NotNull Course course) { try { final CloseableHttpClient client = getHttpClient(project); - final URI uri = new URIBuilder(STEPIC_API_URL + RECOMMENDATIONS_URL).addParameter("course", String.valueOf(course.getId())).build(); + final URI uri = new URIBuilder(STEPIC_API_URL + RECOMMENDATIONS_URL) + .addParameter("course", String.valueOf(course.getId())) + .build(); final HttpGet request = new HttpGet(uri); setHeaders(request, CONTENT_TYPE_APPL_JSON); @@ -186,7 +188,7 @@ public class EduAdaptiveStepicConnector { } @NotNull - private static Task getTaskFromStep(@NotNull String name, int lessonID, StepicWrappers.Step step) { + private static Task getTaskFromStep(@NotNull String name, int lessonID, @NotNull final StepicWrappers.Step step) { final Task task = new Task(); task.setName(name); task.setStepicId(lessonID); diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyJavaFxToolWindow.java b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyJavaFxToolWindow.java index 98bec3d50589..2bf50d63bc6f 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyJavaFxToolWindow.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyJavaFxToolWindow.java @@ -42,6 +42,6 @@ public class StudyJavaFxToolWindow extends StudyToolWindow { @Override public void setTaskText(String text) { StudyPluginConfigurator configurator = StudyUtils.getConfigurator(ProjectUtil.guessCurrentProject(this)); - myBrowserWindow.loadContent(text, configurator); + myBrowserWindow.loadContent(text, configurator); } } From 44326df3241e99dbb4b0e746e6e321c23ec793f3 Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Thu, 28 Apr 2016 14:28:39 +0300 Subject: [PATCH 13/44] Fix NPE on Add Task File action update, when trying to show item in files --- .../src/com/jetbrains/edu/coursecreator/CCProjectService.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCProjectService.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCProjectService.java index ba9886692671..642b15d21c67 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCProjectService.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCProjectService.java @@ -62,6 +62,9 @@ public class CCProjectService implements PersistentStateComponent Date: Thu, 28 Apr 2016 15:15:27 +0300 Subject: [PATCH 14/44] Minor Remove unused argument --- .../edu/learning/courseGeneration/StudyProjectGenerator.java | 2 +- .../com/jetbrains/edu/learning/stepic/EduStepicConnector.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java b/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java index 66c9c0152e3c..3f892dcde93d 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java @@ -105,7 +105,7 @@ public class StudyProjectGenerator { finally { StudyUtils.closeSilently(reader); } - final Course course = EduStepicConnector.getCourse(project, mySelectedCourseInfo, myUser.getEmail()); + final Course course = EduStepicConnector.getCourse(project, mySelectedCourseInfo); if (course != null) { flushCourse(project, course); } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java index 7e331d940a9c..c7169e83269a 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java @@ -255,7 +255,7 @@ public class EduStepicConnector { return coursesContainer.meta.containsKey("has_next") && coursesContainer.meta.get("has_next") == Boolean.TRUE; } - public static Course getCourse(@NotNull final Project project, @NotNull final CourseInfo info, @NotNull final String username) { + public static Course getCourse(@NotNull final Project project, @NotNull final CourseInfo info) { final Course course = new Course(); course.setAuthors(info.getAuthors()); course.setDescription(info.getDescription()); From 2c958c2b57fff0351d6656add7112e3e63eacd0d Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Thu, 28 Apr 2016 15:17:24 +0300 Subject: [PATCH 15/44] Add tests from samples provided for step and show test result in separate tool window --- .../student/resources/META-INF/plugin.xml | 1 + .../learning/StudyBasePluginConfigurator.java | 1 + .../edu/learning/checker/StudyCheckTask.java | 88 +++++++------ .../edu/learning/checker/StudyCheckUtils.java | 53 +++++--- .../checker/StudyTestsOutputParser.java | 4 +- .../courseGeneration/StudyGenerator.java | 59 +++++---- .../stepic/EduAdaptiveStepicConnector.java | 121 ++++++++++++------ .../edu/learning/stepic/StepicWrappers.java | 2 + .../edu/learning/ui/StudyBrowserWindow.java | 2 +- .../learning/ui/StudyTestResultsToolWindow.kt | 20 +++ .../ui/StudyTestResultsToolWindowFactory.kt | 18 +++ .../edu/learning/ui/StudyToolWindow.java | 2 +- .../learning/ui/StudyToolWindowFactory.java | 3 +- .../fileTemplates/internal/test_helper.py.ft | 33 ++++- .../edu/learning/PyStudyCheckAction.java | 56 ++++---- 15 files changed, 303 insertions(+), 160 deletions(-) create mode 100644 python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindow.kt create mode 100644 python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindowFactory.kt diff --git a/python/educational-core/student/resources/META-INF/plugin.xml b/python/educational-core/student/resources/META-INF/plugin.xml index 2d5b272bd2bb..5652e347f6d6 100644 --- a/python/educational-core/student/resources/META-INF/plugin.xml +++ b/python/educational-core/student/resources/META-INF/plugin.xml @@ -85,6 +85,7 @@ + diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/StudyBasePluginConfigurator.java b/python/educational-core/student/src/com/jetbrains/edu/learning/StudyBasePluginConfigurator.java index 74410d74ce84..07a3b31e8514 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/StudyBasePluginConfigurator.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/StudyBasePluginConfigurator.java @@ -69,6 +69,7 @@ public abstract class StudyBasePluginConfigurator implements StudyPluginConfigur Task task = getTask(file); setTaskText(task, file.getParent()); } + toolWindow.setBottomComponent(null); } @Nullable diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java index 29a4011c9306..61b7f3718596 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java @@ -25,6 +25,7 @@ import com.jetbrains.edu.learning.courseFormat.Task; import com.jetbrains.edu.learning.stepic.EduAdaptiveStepicConnector; import com.jetbrains.edu.learning.stepic.EduStepicConnector; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroundable { @@ -75,21 +76,36 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun public void run(@NotNull ProgressIndicator indicator) { final Course course = StudyTaskManager.getInstance(myProject).getCourse(); if (course != null && course.isAdaptive()) { - checkAdaptiveCourse(indicator); + checkForAdaptiveCourse(indicator); } else { - if (checkCourse(indicator)) return; + checkForEduCourse(indicator); } - runAfterTaskSolvedActions(); } - private boolean checkCourse(@NotNull ProgressIndicator indicator) { + private void checkForEduCourse(@NotNull ProgressIndicator indicator) { + final StudyTestsOutputParser.TestsOutput testsOutput = getTestOutput(indicator); + + postAttemptToStepic(testsOutput); + + if (testsOutput != null) { + if (testsOutput.isSuccess()) { + onTaskSolved(testsOutput.getMessage()); + } + else { + onTaskFailed(testsOutput.getMessage()); + } + runAfterTaskCheckedActions(); + } + } + + @Nullable + private StudyTestsOutputParser.TestsOutput getTestOutput(@NotNull ProgressIndicator indicator) { final CapturingProcessHandler handler = new CapturingProcessHandler(myTestProcess, null, myCommandLine); final ProcessOutput output = handler.runProcessWithProgressIndicator(indicator); if (indicator.isCanceled()) { ApplicationManager.getApplication().invokeLater( () -> StudyCheckUtils.showTestResultPopUp("Check cancelled", MessageType.WARNING.getPopupBackground(), myProject)); - return true; } @@ -102,64 +118,64 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun myProject)); //log error output of tests LOG.info("#educational " + stderr); - return true; + return null; } - - postAttemptToStepic(testsOutput); - - - if (testsOutput.isSuccess()) { - onTaskSolved(testsOutput.getMessage()); - } - else { - onTaskFailed(testsOutput.getMessage()); - } - return false; + return testsOutput; } - private void checkAdaptiveCourse(ProgressIndicator indicator) { + private void checkForAdaptiveCourse(ProgressIndicator indicator) { ProgressManager.getInstance().runProcessWithProgressAsynchronously(new Backgroundable(myProject, "Checking Task") { @Override public void run(@NotNull ProgressIndicator indicator) { - final Pair pair = EduAdaptiveStepicConnector.checkTask(myProject, myTask); - if (pair != null) { - final String checkMessage = pair.getSecond(); - if (pair.getFirst()) { - onTaskSolved(checkMessage); + final StudyTestsOutputParser.TestsOutput testOutput = getTestOutput(indicator); + if (testOutput != null) { + if (testOutput.isSuccess()) { + final Pair pair = EduAdaptiveStepicConnector.checkTask(myProject, myTask); + if (pair != null && !pair.getSecond().isEmpty()) { + final String checkMessage = pair.getSecond(); + if (pair.getFirst()) { + onTaskSolved(checkMessage); + } + else { + onTaskFailed(checkMessage); + } + runAfterTaskCheckedActions(); + } + else { + ApplicationManager.getApplication().invokeLater(() -> + StudyCheckUtils.showTestResultPopUp("Failed to launch checking", + MessageType.WARNING + .getPopupBackground(), + myProject)); + } } else { - onTaskFailed(checkMessage); + onTaskFailed(testOutput.getMessage()); } } - else { - ApplicationManager.getApplication().invokeLater(() -> - StudyCheckUtils.showTestResultPopUp("Failed to launch checking", - MessageType.WARNING.getPopupBackground(), - myProject)); - } } }, indicator); - } - protected void onTaskFailed( String message) { + protected void onTaskFailed(String message) { myTaskManger.setStatus(myTask, StudyStatus.Failed); ApplicationManager.getApplication().invokeLater( - () -> StudyCheckUtils.showTestResultPopUp(message, MessageType.ERROR.getPopupBackground(), myProject)); + () -> StudyCheckUtils.showTestResults(myProject, message)); } protected void onTaskSolved(String message) { myTaskManger.setStatus(myTask, StudyStatus.Solved); + ApplicationManager.getApplication().invokeLater( - () -> StudyCheckUtils.showTestResultPopUp(message, MessageType.INFO.getPopupBackground(), myProject)); + () -> StudyCheckUtils.showTestResults(myProject, message)); } - private void runAfterTaskSolvedActions() { + private void runAfterTaskCheckedActions() { StudyPluginConfigurator configurator = StudyUtils.getConfigurator(myProject); if (configurator != null) { StudyAfterCheckAction[] checkActions = configurator.getAfterCheckActions(); if (checkActions != null) { - for (StudyAfterCheckAction action: checkActions) { + for (StudyAfterCheckAction action : checkActions) { action.run(myProject, myTask, myStatusBeforeCheck); } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java index 9fe0ae84ae6b..c719388b61ac 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java @@ -15,23 +15,25 @@ import com.intellij.openapi.ui.popup.BalloonBuilder; import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.util.Pair; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.wm.IdeFocusManager; -import com.intellij.openapi.wm.IdeFrame; -import com.intellij.openapi.wm.WindowManager; +import com.intellij.openapi.wm.*; import com.intellij.openapi.wm.ex.StatusBarEx; import com.intellij.openapi.wm.ex.WindowManagerEx; +import com.intellij.ui.content.Content; +import com.jetbrains.edu.learning.StudyState; +import com.jetbrains.edu.learning.StudyTaskManager; +import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.core.EduDocumentListener; import com.jetbrains.edu.learning.core.EduUtils; import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; import com.jetbrains.edu.learning.courseFormat.Task; import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.StudyState; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.editor.StudyEditor; import com.jetbrains.edu.learning.navigation.StudyNavigator; +import com.jetbrains.edu.learning.ui.StudyTestResultsToolWindow; +import com.jetbrains.edu.learning.ui.StudyTestResultsToolWindowFactory; import org.jetbrains.annotations.NotNull; +import javax.swing.*; import java.awt.*; import java.io.IOException; import java.util.List; @@ -61,9 +63,9 @@ public class StudyCheckUtils { } public static void navigateToFailedPlaceholder(@NotNull final StudyState studyState, - @NotNull final Task task, - @NotNull final VirtualFile taskDir, - @NotNull final Project project) { + @NotNull final Task task, + @NotNull final VirtualFile taskDir, + @NotNull final Project project) { TaskFile selectedTaskFile = studyState.getTaskFile(); Editor editor = studyState.getEditor(); TaskFile taskFileToNavigate = selectedTaskFile; @@ -109,10 +111,10 @@ public class StudyCheckUtils { public static void runSmartTestProcess(@NotNull final VirtualFile taskDir, - @NotNull final StudyTestRunner testRunner, - final String taskFileName, - @NotNull final TaskFile taskFile, - @NotNull final Project project) { + @NotNull final StudyTestRunner testRunner, + final String taskFileName, + @NotNull final TaskFile taskFile, + @NotNull final Project project) { final TaskFile answerTaskFile = new TaskFile(); answerTaskFile.name = taskFileName; final VirtualFile virtualFile = taskDir.findChild(taskFileName); @@ -135,11 +137,10 @@ public class StudyCheckUtils { } - private static VirtualFile getCopyWithAnswers(@NotNull final VirtualFile taskDir, - @NotNull final VirtualFile file, - @NotNull final TaskFile source, - @NotNull final TaskFile target) { + @NotNull final VirtualFile file, + @NotNull final TaskFile source, + @NotNull final TaskFile target) { VirtualFile copy = null; try { @@ -193,4 +194,22 @@ public class StudyCheckUtils { EduUtils.flushWindows(taskFile, virtualFile, true); } } + + public static void showTestResults(@NotNull final Project project, @NotNull final String message) { + final ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project); + ToolWindow window = toolWindowManager.getToolWindow("Test Results"); + if (window == null) { + toolWindowManager.registerToolWindow("Test Results", true, ToolWindowAnchor.BOTTOM); + window = toolWindowManager.getToolWindow("Test Results"); + new StudyTestResultsToolWindowFactory().createToolWindowContent(project, window); + } + final Content[] contents = window.getContentManager().getContents(); + for (Content content : contents) { + final JComponent component = content.getComponent(); + if (component instanceof StudyTestResultsToolWindow) { + ((StudyTestResultsToolWindow)component).setText(message); + component.setVisible(true); + } + } + } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyTestsOutputParser.java b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyTestsOutputParser.java index 55bfe7273bac..921ee4cba82c 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyTestsOutputParser.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyTestsOutputParser.java @@ -4,7 +4,7 @@ import com.intellij.execution.process.ProcessOutput; import org.jetbrains.annotations.NotNull; public class StudyTestsOutputParser { - private static final String ourStudyPrefix = "#educational_plugin"; + private static final String STUDY_PREFIX = "#educational_plugin"; public static final String TEST_OK = "test OK"; private static final String TEST_FAILED = "FAILED + "; private static final String CONGRATS_MESSAGE = "CONGRATS_MESSAGE "; @@ -32,7 +32,7 @@ public class StudyTestsOutputParser { public static TestsOutput getTestsOutput(@NotNull final ProcessOutput processOutput) { String congratulations = CONGRATULATIONS; for (String line : processOutput.getStdoutLines()) { - if (line.startsWith(ourStudyPrefix)) { + if (line.startsWith(STUDY_PREFIX)) { if (line.contains(TEST_OK)) { continue; } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyGenerator.java b/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyGenerator.java index 9af4dc7af1a5..fe5cd4e65c40 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyGenerator.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyGenerator.java @@ -4,13 +4,13 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VirtualFile; +import com.jetbrains.edu.learning.StudyTaskManager; +import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseFormat.Lesson; import com.jetbrains.edu.learning.courseFormat.Task; import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.StudyTaskManager; -import com.jetbrains.edu.learning.StudyUtils; import org.jetbrains.annotations.NotNull; import java.io.File; @@ -23,6 +23,7 @@ public class StudyGenerator { private StudyGenerator() { } + private static final Logger LOG = Logger.getInstance(StudyGenerator.class.getName()); /** @@ -107,34 +108,32 @@ public class StudyGenerator { public static void createCourse(@NotNull final Course course, @NotNull final VirtualFile baseDir, @NotNull final File resourceRoot, @NotNull final Project project) { - try { - final List lessons = course.getLessons(); - for (int i = 1; i <= lessons.size(); i++) { - Lesson lesson = lessons.get(i - 1); - lesson.setIndex(i); - createLesson(lesson, baseDir, resourceRoot, project); - } - baseDir.createChildDirectory(project, EduNames.SANDBOX_DIR); - File[] files = resourceRoot.listFiles(new FilenameFilter() { - @Override - public boolean accept(File dir, String name) { - return !name.contains(EduNames.LESSON) && !name.equals(EduNames.COURSE_META_FILE) && !name.equals(EduNames.HINTS); - } - }); - for (File file : files) { - File dir = new File(baseDir.getPath(), file.getName()); - if (file.isDirectory()) { - FileUtil.copyDir(file, dir); - continue; - } + try { + final List lessons = course.getLessons(); + for (int i = 1; i <= lessons.size(); i++) { + Lesson lesson = lessons.get(i - 1); + lesson.setIndex(i); + createLesson(lesson, baseDir, resourceRoot, project); + } + baseDir.createChildDirectory(project, EduNames.SANDBOX_DIR); + File[] files = resourceRoot.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return !name.contains(EduNames.LESSON) && !name.equals(EduNames.COURSE_META_FILE) && !name.equals(EduNames.HINTS); + } + }); + for (File file : files) { + File dir = new File(baseDir.getPath(), file.getName()); + if (file.isDirectory()) { + FileUtil.copyDir(file, dir); + continue; + } - FileUtil.copy(file, dir); - - } - } - catch (IOException e) { - LOG.error(e); - } + FileUtil.copy(file, dir); + } + } + catch (IOException e) { + LOG.error(e); + } } - } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java index ee80ac733d98..cad89ec26265 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java @@ -9,6 +9,7 @@ import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; 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.StudyTaskManager; @@ -42,6 +43,7 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; +import java.util.List; import java.util.concurrent.TimeUnit; import static com.jetbrains.edu.learning.stepic.EduStepicConnector.getHttpClient; @@ -57,6 +59,8 @@ public class EduAdaptiveStepicConnector { private static final String RECOMMENDATION_REACTIONS_URL = "recommendation-reactions"; private static final String ATTEMPTS_URL = "attempts"; private static final String SUBMISSION_URL = "submissions"; + private static final String PYTHON2 = "python2"; + private static final String PYTHON3 = "python3"; @Nullable public static Task getNextRecommendation(@NotNull final Project project, @NotNull Course course) { @@ -88,9 +92,11 @@ public class EduAdaptiveStepicConnector { for (int stepId : realLesson.steps) { final StepicWrappers.Step step = EduStepicConnector.getStep(stepId); if (step.name.equals("code")) { - return getTaskFromStep(realLesson.getName(), stepId, step); + return getTaskFromStep(project, stepId, step, realLesson.getName()); } } + + LOG.warn("Got a lesson without code part as a recommendation"); } } else { @@ -106,8 +112,8 @@ public class EduAdaptiveStepicConnector { return null; } - public static boolean postRecommendationReaction(@NotNull final Project project, @NotNull final String lessonId, - @NotNull final String user,int reaction) { + public static boolean postRecommendationReaction(@NotNull final Project project, @NotNull final String lessonId, + @NotNull final String user, int reaction) { final HttpPost post = new HttpPost(STEPIC_API_URL + RECOMMENDATION_REACTIONS_URL); final String json = new Gson() @@ -132,7 +138,7 @@ public class EduAdaptiveStepicConnector { // TODO: get user from settings final StepicUser user = StudyTaskManager.getInstance(project).getUser(); if (user != null && - postRecommendationReaction(project, String.valueOf(editor.getTaskFile().getTask().getLesson().id), + postRecommendationReaction(project, String.valueOf(editor.getTaskFile().getTask().getLesson().id), String.valueOf(user.id), reaction)) { final Task task = getNextRecommendation(project, course); @@ -188,16 +194,37 @@ public class EduAdaptiveStepicConnector { } @NotNull - private static Task getTaskFromStep(@NotNull String name, int lessonID, @NotNull final StepicWrappers.Step step) { + private static Task getTaskFromStep(Project project, + int lessonID, + @NotNull final StepicWrappers.Step step, @NotNull String name) { final Task task = new Task(); task.setName(name); task.setStepicId(lessonID); task.setText(step.text); + if (step.options.samples != null) { + final StringBuilder builder = new StringBuilder(); + for (List sample : step.options.samples) { + if (sample.size() == 2) { + builder.append("Sample Input:
"); + builder.append(sample.get(0)); + builder.append("
"); + builder.append("Sample Output:
"); + builder.append(sample.get(1)); + builder.append("

"); + } + } + task.setText(task.getText() + "
" + builder.toString()); + } if (step.options.test != null) { for (StepicWrappers.TestFileWrapper wrapper : step.options.test) { task.addTestsTexts(wrapper.name, wrapper.text); } } + else { + if (step.options.samples != null) { + createTestFileFromSamples(project, task, step.options.samples); + } + } task.taskFiles = new HashMap(); // TODO: it looks like we don't need taskFiles as map anymore if (step.options.files != null) { @@ -218,37 +245,39 @@ public class EduAdaptiveStepicConnector { public static Pair checkTask(@NotNull final Project project, @NotNull final Task task) { try { final int attemptId = getAttemptId(project, task, ATTEMPTS_URL); - final Editor editor = StudyUtils.getSelectedEditor(project); - String language = getLanguageString(task, project); - if (editor != null && language != null) { - final StepicWrappers.SubmissionToPostWrapper submissionToPostWrapper = - new StepicWrappers.SubmissionToPostWrapper(String.valueOf(attemptId), "python3", editor.getDocument().getText()); - final HttpPost httpPost = new HttpPost(STEPIC_API_URL + SUBMISSION_URL); - httpPost.setEntity(new StringEntity(new Gson().toJson(submissionToPostWrapper))); - final CloseableHttpClient client = getHttpClient(project); - setHeaders(httpPost, CONTENT_TYPE_APPL_JSON); - final CloseableHttpResponse execute = client.execute(httpPost); - StepicWrappers.ResultSubmissionWrapper wrapper = - new Gson().fromJson(EntityUtils.toString(execute.getEntity()), StepicWrappers.ResultSubmissionWrapper.class); + if (attemptId != -1) { + final Editor editor = StudyUtils.getSelectedEditor(project); + String language = getLanguageString(task, project); + if (editor != null && language != null) { + final StepicWrappers.SubmissionToPostWrapper submissionToPostWrapper = + new StepicWrappers.SubmissionToPostWrapper(String.valueOf(attemptId), language, editor.getDocument().getText()); + final HttpPost httpPost = new HttpPost(STEPIC_API_URL + SUBMISSION_URL); + httpPost.setEntity(new StringEntity(new Gson().toJson(submissionToPostWrapper))); + final CloseableHttpClient client = getHttpClient(project); + setHeaders(httpPost, CONTENT_TYPE_APPL_JSON); + final CloseableHttpResponse execute = client.execute(httpPost); + StepicWrappers.ResultSubmissionWrapper wrapper = + new Gson().fromJson(EntityUtils.toString(execute.getEntity()), StepicWrappers.ResultSubmissionWrapper.class); - final StepicUser user = StudyTaskManager.getInstance(project).getUser(); - if (user != null) { - final int id = user.getId(); - while (wrapper.submissions.length == 1 && wrapper.submissions[0].status.equals("evaluation")) { - TimeUnit.MILLISECONDS.sleep(500); - final URI submissionURI = new URIBuilder(STEPIC_API_URL + SUBMISSION_URL) - .addParameter("attempt", String.valueOf(attemptId)) - .addParameter("order", "desc") - .addParameter("user", String.valueOf(id)) - .build(); - final HttpGet httpGet = new HttpGet(submissionURI); - setHeaders(httpGet, CONTENT_TYPE_APPL_JSON); - final CloseableHttpResponse httpResponse = client.execute(httpGet); - wrapper = new Gson().fromJson(EntityUtils.toString(httpResponse.getEntity()), StepicWrappers.ResultSubmissionWrapper.class); - } - if (wrapper.submissions.length == 1) { - final boolean isSolved = !wrapper.submissions[0].status.equals("wrong"); - return Pair.create(isSolved, wrapper.submissions[0].hint); + final StepicUser user = StudyTaskManager.getInstance(project).getUser(); + if (user != null) { + final int id = user.getId(); + while (wrapper.submissions.length == 1 && wrapper.submissions[0].status.equals("evaluation")) { + TimeUnit.MILLISECONDS.sleep(500); + final URI submissionURI = new URIBuilder(STEPIC_API_URL + SUBMISSION_URL) + .addParameter("attempt", String.valueOf(attemptId)) + .addParameter("order", "desc") + .addParameter("user", String.valueOf(id)) + .build(); + final HttpGet httpGet = new HttpGet(submissionURI); + setHeaders(httpGet, CONTENT_TYPE_APPL_JSON); + final CloseableHttpResponse httpResponse = client.execute(httpGet); + wrapper = new Gson().fromJson(EntityUtils.toString(httpResponse.getEntity()), StepicWrappers.ResultSubmissionWrapper.class); + } + if (wrapper.submissions.length == 1) { + final boolean isSolved = !wrapper.submissions[0].status.equals("wrong"); + return Pair.create(isSolved, wrapper.submissions[0].hint); + } } } } @@ -272,11 +301,12 @@ public class EduAdaptiveStepicConnector { private static String getLanguageString(@NotNull Task task, @NotNull Project project) { final Sdk sdk = StudyUtils.findSdk(task, project); if (sdk != null) { - if (sdk.getVersionString() != null && sdk.getVersionString().startsWith("3")) { - return "python3"; - } - else { - return "python2"; + final String versionString = sdk.getVersionString(); + if (versionString != null ) { + final List versionStringParts = StringUtil.split(versionString, " "); + if (versionStringParts.size() == 2) { + return versionStringParts.get(1).startsWith("2") ? PYTHON2 : PYTHON3; + } } } else { @@ -296,6 +326,15 @@ public class EduAdaptiveStepicConnector { final CloseableHttpResponse httpResponse = client.execute(post); final StepicWrappers.AttemptContainer container = new Gson().fromJson(EntityUtils.toString(httpResponse.getEntity()), StepicWrappers.AttemptContainer.class); - return container.attempts.get(0).id; + return (container.attempts != null && !container.attempts.isEmpty()) ? container.attempts.get(0).id : -1; + } + + private static void createTestFileFromSamples(@NotNull final Project project, + @NotNull final Task task, + @NotNull final List> samples) { + String testText = "from test_helper import check_samples\n\n" + + "if __name__ == '__main__':\n" + + " check_samples(samples=" + new GsonBuilder().create().toJson(samples) + ")"; + task.addTestsTexts("tests.py", testText); } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java index 8a02678c182c..69f1ea7d514a 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java @@ -47,6 +47,8 @@ public class StepicWrappers { @Expose String title; @Expose List files; @Expose String text; + @Expose List> samples; + public static StepOptions fromTask(final Project project, @NotNull final Task task) { final StepOptions source = new StepOptions(); diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyBrowserWindow.java b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyBrowserWindow.java index fd7a12a5e01e..75d6146c48c9 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyBrowserWindow.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyBrowserWindow.java @@ -35,7 +35,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; -class StudyBrowserWindow extends JFrame { +public class StudyBrowserWindow extends JFrame { private static final Logger LOG = Logger.getInstance(StudyToolWindow.class); private static final String EVENT_TYPE_CLICK = "click"; private JFXPanel myPanel; diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindow.kt b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindow.kt new file mode 100644 index 000000000000..17ec0d915507 --- /dev/null +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindow.kt @@ -0,0 +1,20 @@ +package com.jetbrains.edu.learning.ui + +import com.intellij.openapi.ui.SimpleToolWindowPanel +import java.awt.BorderLayout +import javax.swing.JPanel + + +class StudyTestResultsToolWindow: SimpleToolWindowPanel(false) { + private val studyBrowserWindow = StudyBrowserWindow(false, false) + fun init() { + val panel = JPanel(BorderLayout()) + studyBrowserWindow.loadContent("", null) + panel.add(studyBrowserWindow.panel, BorderLayout.CENTER) + setContent(panel) + } + + fun setText(text: String) { + studyBrowserWindow.loadContent(text, null) + } +} \ No newline at end of file diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindowFactory.kt b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindowFactory.kt new file mode 100644 index 000000000000..53d073ae4bbc --- /dev/null +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindowFactory.kt @@ -0,0 +1,18 @@ +package com.jetbrains.edu.learning.ui + +import com.intellij.openapi.project.Project +import com.intellij.openapi.wm.ToolWindow + + +class StudyTestResultsToolWindowFactory: StudyToolWindowFactory() { + override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) { + val testResultsToolWindow = StudyTestResultsToolWindow() + testResultsToolWindow.init() + + val contentManager = toolWindow.contentManager + val content = contentManager.factory.createContent(testResultsToolWindow, null, false) + contentManager.addContent(content) + } +} + + diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java index 054b2d0a35ab..d8261baece92 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java @@ -219,5 +219,5 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D } } - public abstract void setTaskText(String text) ; + public abstract void setTaskText(String text); } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindowFactory.java b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindowFactory.java index db026cea6bad..23385fd077ce 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindowFactory.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindowFactory.java @@ -7,9 +7,9 @@ import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.ToolWindowFactory; import com.intellij.ui.content.Content; import com.intellij.ui.content.ContentManager; -import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.StudyProjectComponent; import com.jetbrains.edu.learning.StudyTaskManager; +import com.jetbrains.edu.learning.courseFormat.Course; import icons.InteractiveLearningIcons; import org.jetbrains.annotations.NotNull; @@ -23,7 +23,6 @@ public class StudyToolWindowFactory implements ToolWindowFactory, DumbAware { StudyTaskManager taskManager = StudyTaskManager.getInstance(project); final Course course = taskManager.getCourse(); if (course != null) { - final StudyToolWindow studyToolWindow; if (StudyProjectComponent.getInstance(project).useJavaFx()) { studyToolWindow = new StudyJavaFxToolWindow(); diff --git a/python/educational-python/resources/fileTemplates/internal/test_helper.py.ft b/python/educational-python/resources/fileTemplates/internal/test_helper.py.ft index d27866c23f01..a0ee26851527 100644 --- a/python/educational-python/resources/fileTemplates/internal/test_helper.py.ft +++ b/python/educational-python/resources/fileTemplates/internal/test_helper.py.ft @@ -9,7 +9,7 @@ def get_file_text(path): return text -def get_file_output(encoding="utf-8", path=sys.argv[-1]): +def get_file_output(encoding="utf-8", path=sys.argv[-1], arg_string=""): """ Returns answer file output :param encoding: to decode output in python3 @@ -18,7 +18,13 @@ def get_file_output(encoding="utf-8", path=sys.argv[-1]): """ import subprocess - proc = subprocess.Popen([sys.executable, path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + proc = subprocess.Popen([sys.executable, path], stdin=subprocess.PIPE, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + if arg_string: + for arg in arg_string.split("\n"): + proc.stdin.write(bytearray(str(arg) + "\n", encoding)) + proc.stdin.flush() + return list(map(lambda x: str(x.decode(encoding)), proc.communicate()[0].splitlines())) @@ -31,7 +37,8 @@ def test_file_importable(): parent = os.path.abspath(os.path.join(path, os.pardir)) python_files = [f for f in os.listdir(parent) if os.path.isfile(os.path.join(parent, f)) and f.endswith(".py")] for python_file in python_files: - if python_file == "tests.py": continue + if python_file == "tests.py": + continue check_importable_path(os.path.join(parent, python_file)) return check_importable_path(path) @@ -187,7 +194,25 @@ def get_answer_placeholders(): return windows -def run_common_tests(error_text="Please, reload file and try again"): +def check_samples(samples=()): + """ + Check script output for all samples. Sample is a two element list, where the first is input and + the second is output. + """ + for sample in samples: + if len(sample) == 2: + output = get_file_output(arg_string=str(sample[0])) + if "\n".join(output) != sample[1]: + failed( + "Input:
{}

Expected:
{}

Your result:
{}".format( + sample[0].replace("\n", "
"), sample[1].replace("\n", "
"), "\n".join(output))) + return + set_congratulation_message("All test from samples passed. Now we are checking your solution on Stepic server.") + + passed() + + +def run_common_tests(): test_is_initial_text() test_is_not_empty() test_answer_placeholders_text_deleted() diff --git a/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java b/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java index 1b238004fd57..34e59c4027b1 100644 --- a/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java +++ b/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java @@ -7,7 +7,6 @@ import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.IdeFocusManager; import com.jetbrains.edu.learning.actions.StudyCheckAction; @@ -15,10 +14,12 @@ import com.jetbrains.edu.learning.actions.StudyRunAction; import com.jetbrains.edu.learning.checker.StudyCheckTask; import com.jetbrains.edu.learning.checker.StudyCheckUtils; import com.jetbrains.edu.learning.checker.StudyTestRunner; +import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseFormat.StudyStatus; import com.jetbrains.edu.learning.courseFormat.Task; import com.jetbrains.edu.learning.courseFormat.TaskFile; import com.jetbrains.edu.learning.editor.StudyEditor; +import com.jetbrains.edu.learning.ui.StudyToolWindow; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -40,8 +41,8 @@ public class PyStudyCheckAction extends StudyCheckAction { } if (StudyCheckUtils.hasBackgroundProcesses(project)) return; - - if (!runTask(project)) return; + final Course course = StudyTaskManager.getInstance(project).getCourse(); + if (course != null && !course.isAdaptive() && !runTask(project)) return; final Task task = studyState.getTask(); final VirtualFile taskDir = studyState.getTaskDir(); @@ -90,34 +91,37 @@ public class PyStudyCheckAction extends StudyCheckAction { final Process testProcess, final String commandLine) { return new StudyCheckTask(project, studyState, myCheckInProgress, testProcess, commandLine) { - @Override - protected void onTaskFailed(String message) { - ApplicationManager.getApplication().invokeLater(() -> { - if (myTaskDir == null) return; - myTaskManger.setStatus(myTask, StudyStatus.Failed); - for (Map.Entry entry : myTask.getTaskFiles().entrySet()) { - final String name = entry.getKey(); - final TaskFile taskFile = entry.getValue(); - if (taskFile.getAnswerPlaceholders().size() < 2) { - myTaskManger.setStatus(taskFile, StudyStatus.Failed); - continue; - } - CommandProcessor.getInstance().runUndoTransparentAction(() -> ApplicationManager.getApplication().runWriteAction(() -> { - StudyCheckUtils.runSmartTestProcess(myTaskDir, testRunner, name, taskFile, project); - })); - } - StudyCheckUtils.showTestResultPopUp(message, MessageType.ERROR.getPopupBackground(), project); - StudyCheckUtils.navigateToFailedPlaceholder(myStudyState, myTask, myTaskDir, project); - }); + @Override + protected void onTaskFailed(String message) { + ApplicationManager.getApplication().invokeLater(() -> { + if (myTaskDir == null) return; + myTaskManger.setStatus(myTask, StudyStatus.Failed); + for (Map.Entry entry : myTask.getTaskFiles().entrySet()) { + final String name = entry.getKey(); + final TaskFile taskFile = entry.getValue(); + if (taskFile.getAnswerPlaceholders().size() < 2) { + myTaskManger.setStatus(taskFile, StudyStatus.Failed); + continue; } - }; + CommandProcessor.getInstance().runUndoTransparentAction(() -> ApplicationManager.getApplication().runWriteAction(() -> { + StudyCheckUtils.runSmartTestProcess(myTaskDir, testRunner, name, taskFile, project); + })); + } + final StudyToolWindow toolWindow = StudyUtils.getStudyToolWindow(project); + if (toolWindow != null) { + StudyCheckUtils.showTestResults(project, message); + StudyCheckUtils.navigateToFailedPlaceholder(myStudyState, myTask, myTaskDir, project); + } + }); + } + }; } @Nullable private static VirtualFile getTaskVirtualFile(@NotNull final StudyState studyState, - @NotNull final Task task, - @NotNull final VirtualFile taskDir) { + @NotNull final Task task, + @NotNull final VirtualFile taskDir) { VirtualFile taskVirtualFile = studyState.getVirtualFile(); for (Map.Entry entry : task.getTaskFiles().entrySet()) { String name = entry.getKey(); @@ -131,7 +135,7 @@ public class PyStudyCheckAction extends StudyCheckAction { } return taskVirtualFile; } - + @NotNull @Override public String getActionId() { From f0f5e1a35c6a5dcd873d69bef25e7379a4f419e5 Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Mon, 2 May 2016 16:41:33 +0300 Subject: [PATCH 16/44] Remove unnecessary execution with progress --- .../edu/learning/checker/StudyCheckTask.java | 47 ++++++++----------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java index 61b7f3718596..ada2a7edacab 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java @@ -6,7 +6,6 @@ import com.intellij.ide.projectView.ProjectView; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.util.Pair; @@ -124,37 +123,31 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun } private void checkForAdaptiveCourse(ProgressIndicator indicator) { - ProgressManager.getInstance().runProcessWithProgressAsynchronously(new Backgroundable(myProject, "Checking Task") { - @Override - public void run(@NotNull ProgressIndicator indicator) { - final StudyTestsOutputParser.TestsOutput testOutput = getTestOutput(indicator); - if (testOutput != null) { - if (testOutput.isSuccess()) { - final Pair pair = EduAdaptiveStepicConnector.checkTask(myProject, myTask); - if (pair != null && !pair.getSecond().isEmpty()) { - final String checkMessage = pair.getSecond(); - if (pair.getFirst()) { - onTaskSolved(checkMessage); - } - else { - onTaskFailed(checkMessage); - } - runAfterTaskCheckedActions(); - } - else { - ApplicationManager.getApplication().invokeLater(() -> - StudyCheckUtils.showTestResultPopUp("Failed to launch checking", + final StudyTestsOutputParser.TestsOutput testOutput = getTestOutput(indicator); + if (testOutput != null) { + if (testOutput.isSuccess()) { + final Pair pair = EduAdaptiveStepicConnector.checkTask(myProject, myTask); + if (pair != null && !pair.getSecond().isEmpty()) { + final String checkMessage = pair.getSecond(); + if (pair.getFirst()) { + onTaskSolved(checkMessage); + } + else { + onTaskFailed(checkMessage); + } + runAfterTaskCheckedActions(); + } + else { + ApplicationManager.getApplication().invokeLater(() -> StudyCheckUtils.showTestResultPopUp("Failed to launch checking", MessageType.WARNING .getPopupBackground(), myProject)); - } - } - else { - onTaskFailed(testOutput.getMessage()); - } } } - }, indicator); + else { + onTaskFailed(testOutput.getMessage()); + } + } } protected void onTaskFailed(String message) { From 197bc94ba1f3b699188d0f7df94886be4d70edf5 Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Mon, 2 May 2016 17:46:57 +0300 Subject: [PATCH 17/44] Change failed message for check from samples --- .../resources/fileTemplates/internal/test_helper.py.ft | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/python/educational-python/resources/fileTemplates/internal/test_helper.py.ft b/python/educational-python/resources/fileTemplates/internal/test_helper.py.ft index a0ee26851527..c0fa0a682ce8 100644 --- a/python/educational-python/resources/fileTemplates/internal/test_helper.py.ft +++ b/python/educational-python/resources/fileTemplates/internal/test_helper.py.ft @@ -204,8 +204,14 @@ def check_samples(samples=()): output = get_file_output(arg_string=str(sample[0])) if "\n".join(output) != sample[1]: failed( - "Input:
{}

Expected:
{}

Your result:
{}".format( - sample[0].replace("\n", "
"), sample[1].replace("\n", "
"), "\n".join(output))) + "Test from samples failed:

" + "Input:
{} " + "

" + "Expected:
{} " + "

" + "Your result:
{}".format(sample[0].replace("\n", "
"), + sample[1].replace("\n", "
"), + "\n".join(output))) return set_congratulation_message("All test from samples passed. Now we are checking your solution on Stepic server.") From bf648a827169a4d77fa8ac4bcd1902ffb907453d Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Mon, 2 May 2016 17:49:03 +0300 Subject: [PATCH 18/44] Properly show and hide test results tool window --- .../edu/learning/checker/StudyCheckUtils.java | 5 +++- .../ui/StudyTestResultsToolWindowFactory.kt | 25 +++++++++++++++++++ python/educational-core/student/student.iml | 1 + 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java index c719388b61ac..a41a9ee495d6 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java @@ -203,12 +203,15 @@ public class StudyCheckUtils { window = toolWindowManager.getToolWindow("Test Results"); new StudyTestResultsToolWindowFactory().createToolWindowContent(project, window); } + final Content[] contents = window.getContentManager().getContents(); for (Content content : contents) { final JComponent component = content.getComponent(); if (component instanceof StudyTestResultsToolWindow) { ((StudyTestResultsToolWindow)component).setText(message); - component.setVisible(true); + window.setAvailable(true, () -> {}); + window.show(() -> {}); + return; } } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindowFactory.kt b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindowFactory.kt index 53d073ae4bbc..65c9855d8262 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindowFactory.kt +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindowFactory.kt @@ -1,6 +1,10 @@ package com.jetbrains.edu.learning.ui +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.fileEditor.FileEditorManagerEvent +import com.intellij.openapi.fileEditor.FileEditorManagerListener import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.wm.ToolWindow @@ -8,10 +12,31 @@ class StudyTestResultsToolWindowFactory: StudyToolWindowFactory() { override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) { val testResultsToolWindow = StudyTestResultsToolWindow() testResultsToolWindow.init() + + toolWindow.isToHideOnEmptyContent = true val contentManager = toolWindow.contentManager val content = contentManager.factory.createContent(testResultsToolWindow, null, false) contentManager.addContent(content) + + project.messageBus.connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, getFileEditorManagerListener(toolWindow)) + } + + fun getFileEditorManagerListener(toolWindow: ToolWindow): FileEditorManagerListener { + + return object : FileEditorManagerListener { + + override fun fileOpened(source: FileEditorManager, file: VirtualFile) { + } + + override fun fileClosed(source: FileEditorManager, file: VirtualFile) { + toolWindow.setAvailable(false, {}) + } + + override fun selectionChanged(event: FileEditorManagerEvent) { + toolWindow.setAvailable(false, {}) + } + } } } diff --git a/python/educational-core/student/student.iml b/python/educational-core/student/student.iml index fed7c3cddcf5..6baed82fb927 100644 --- a/python/educational-core/student/student.iml +++ b/python/educational-core/student/student.iml @@ -21,5 +21,6 @@ + \ No newline at end of file From c26f0586dcbf0d6215c5b31f2a7adf90c4130330 Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Wed, 4 May 2016 11:47:41 +0300 Subject: [PATCH 19/44] In task text for adaptive courses display multiline samples correctly --- .../edu/learning/stepic/EduAdaptiveStepicConnector.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java index cad89ec26265..b67d2ff23b10 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java @@ -206,10 +206,10 @@ public class EduAdaptiveStepicConnector { for (List sample : step.options.samples) { if (sample.size() == 2) { builder.append("Sample Input:
"); - builder.append(sample.get(0)); + builder.append(StringUtil.replace(sample.get(0), "\n", "
")); builder.append("
"); builder.append("Sample Output:
"); - builder.append(sample.get(1)); + builder.append(StringUtil.replace(sample.get(1), "\n", "
")); builder.append("

"); } } From 3b5617668e3ac717701ece6c18097d2f467226c1 Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Wed, 4 May 2016 14:04:54 +0300 Subject: [PATCH 20/44] Set codeTemplates from step as task text Show time and memory limit on task description panel --- .../stepic/EduAdaptiveStepicConnector.java | 62 ++++++++++++++----- .../edu/learning/stepic/StepicWrappers.java | 23 ++++++- 2 files changed, 68 insertions(+), 17 deletions(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java index b67d2ff23b10..ccbe0cd932a8 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java @@ -3,6 +3,7 @@ package com.jetbrains.edu.learning.stepic; import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.intellij.lang.Language; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; @@ -14,6 +15,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import com.jetbrains.edu.learning.StudyTaskManager; import com.jetbrains.edu.learning.StudyUtils; +import com.jetbrains.edu.learning.checker.StudyExecutor; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseFormat.Lesson; @@ -50,6 +52,9 @@ import static com.jetbrains.edu.learning.stepic.EduStepicConnector.getHttpClient import static com.jetbrains.edu.learning.stepic.EduStepicConnector.setHeaders; public class EduAdaptiveStepicConnector { + public static final String PYTHON27 = "python27"; + public static final String PYTHON3 = "python3"; + private static final Logger LOG = Logger.getInstance(EduAdaptiveStepicConnector.class); private static final String STEPIC_URL = "https://stepic.org/"; private static final String STEPIC_API_URL = STEPIC_URL + "api/"; @@ -59,8 +64,6 @@ public class EduAdaptiveStepicConnector { private static final String RECOMMENDATION_REACTIONS_URL = "recommendation-reactions"; private static final String ATTEMPTS_URL = "attempts"; private static final String SUBMISSION_URL = "submissions"; - private static final String PYTHON2 = "python2"; - private static final String PYTHON3 = "python3"; @Nullable public static Task getNextRecommendation(@NotNull final Project project, @NotNull Course course) { @@ -215,6 +218,17 @@ public class EduAdaptiveStepicConnector { } task.setText(task.getText() + "
" + builder.toString()); } + + if (step.options.executionMemoryLimit != null && step.options.executionTimeLimit != null) { + String builder = "Memory limit: " + + step.options.executionMemoryLimit + " Mb" + + "
" + + "Time limit: " + + step.options.executionTimeLimit + "s" + + "

"; + task.setText(task.getText() + builder); + } + if (step.options.test != null) { for (StepicWrappers.TestFileWrapper wrapper : step.options.test) { task.addTestsTexts(wrapper.name, wrapper.text); @@ -222,7 +236,7 @@ public class EduAdaptiveStepicConnector { } else { if (step.options.samples != null) { - createTestFileFromSamples(project, task, step.options.samples); + createTestFileFromSamples(task, step.options.samples); } } @@ -235,11 +249,25 @@ public class EduAdaptiveStepicConnector { else { final TaskFile taskFile = new TaskFile(); taskFile.name = "code"; - taskFile.text = "# write your answer here \n"; + final String templateForTask = getCodeTemplateForTask(step.options.codeTemplates, task, project); + taskFile.text = templateForTask == null ? "# write your answer here \n" : templateForTask; task.taskFiles.put("code.py", taskFile); } return task; } + + private static String getCodeTemplateForTask(@Nullable StepicWrappers.CodeTemplatesWrapper codeTemplates, + @NotNull final Task task, @NotNull final Project project) { + + if (codeTemplates != null) { + final String languageString = getLanguageString(task, project); + if (languageString != null) { + return codeTemplates.getTemplateForLanguage(languageString); + } + } + + return null; + } @Nullable public static Pair checkTask(@NotNull final Project project, @NotNull final Task task) { @@ -299,18 +327,21 @@ public class EduAdaptiveStepicConnector { @Nullable private static String getLanguageString(@NotNull Task task, @NotNull Project project) { - final Sdk sdk = StudyUtils.findSdk(task, project); - if (sdk != null) { - final String versionString = sdk.getVersionString(); - if (versionString != null ) { - final List versionStringParts = StringUtil.split(versionString, " "); - if (versionStringParts.size() == 2) { - return versionStringParts.get(1).startsWith("2") ? PYTHON2 : PYTHON3; + final Language pythonLanguage = Language.findLanguageByID("Python"); + if (pythonLanguage != null) { + final Sdk language = StudyExecutor.INSTANCE.forLanguage(pythonLanguage).findSdk(project); + if (language != null) { + final String versionString = language.getVersionString(); + if (versionString != null ) { + final List versionStringParts = StringUtil.split(versionString, " "); + if (versionStringParts.size() == 2) { + return versionStringParts.get(1).startsWith("2") ? PYTHON27 : PYTHON3; + } } } - } - else { - StudyUtils.showNoSdkNotification(task, project); + else { + StudyUtils.showNoSdkNotification(task, project); + } } return null; } @@ -329,8 +360,7 @@ public class EduAdaptiveStepicConnector { return (container.attempts != null && !container.attempts.isEmpty()) ? container.attempts.get(0).id : -1; } - private static void createTestFileFromSamples(@NotNull final Project project, - @NotNull final Task task, + private static void createTestFileFromSamples(@NotNull final Task task, @NotNull final List> samples) { String testText = "from test_helper import check_samples\n\n" + "if __name__ == '__main__':\n" + diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java index 69f1ea7d514a..c40fdd3f48c4 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java @@ -14,6 +14,7 @@ import com.jetbrains.edu.learning.courseFormat.Task; import com.jetbrains.edu.learning.courseFormat.TaskFile; import org.apache.commons.codec.binary.Base64; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.util.ArrayList; @@ -48,8 +49,10 @@ public class StepicWrappers { @Expose List files; @Expose String text; @Expose List> samples; + @Expose Integer executionMemoryLimit; + @Expose Integer executionTimeLimit; + @Expose CodeTemplatesWrapper codeTemplates; - public static StepOptions fromTask(final Project project, @NotNull final Task task) { final StepOptions source = new StepOptions(); setTests(task, source, project); @@ -110,6 +113,24 @@ public class StepicWrappers { } } } + + static class CodeTemplatesWrapper { + String python3; + String python27; + + @Nullable + public String getTemplateForLanguage(@NotNull final String langauge) { + if (langauge.equals(EduAdaptiveStepicConnector.PYTHON27)) { + return python27; + } + + if (langauge.equals(EduAdaptiveStepicConnector.PYTHON3)) { + return python3; + } + + return null; + } + } static class CoursesContainer { public List courses; From 83b34339c89abe6196e402381abcc4b71ffec992 Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Wed, 4 May 2016 16:27:34 +0300 Subject: [PATCH 21/44] Use python console to display test output --- .../jetbrains/edu/learning/StudyUtils.java | 18 ++++++++++++- .../edu/learning/checker/StudyCheckTask.java | 4 +-- .../edu/learning/checker/StudyCheckUtils.java | 15 ++++++++--- .../checker/StudyTestsOutputParser.java | 19 ++++++++++++-- .../learning/ui/StudyTestResultsToolWindow.kt | 20 -------------- .../ui/StudyTestResultsToolWindowFactory.kt | 26 +++++++++++++------ python/educational-core/student/student.iml | 1 + .../fileTemplates/internal/test_helper.py.ft | 14 +++++----- .../edu/learning/PyStudyCheckAction.java | 2 +- 9 files changed, 74 insertions(+), 45 deletions(-) delete mode 100644 python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindow.kt diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java b/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java index 6a233d40602c..a7146cf9d039 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java @@ -473,7 +473,7 @@ public class StudyUtils { return null; } - + @Nullable public static String getTaskText(@NotNull final Project project) { VirtualFile[] files = FileEditorManager.getInstance(project).getSelectedFiles(); TaskFile taskFile = null; @@ -492,6 +492,22 @@ public class StudyUtils { } return null; } + + @Nullable + public static Task getCurrentTask(@NotNull final Project project) { + VirtualFile[] files = FileEditorManager.getInstance(project).getSelectedFiles(); + TaskFile taskFile = null; + for (VirtualFile file : files) { + taskFile = getTaskFile(project, file); + if (taskFile != null) { + break; + } + } + if (taskFile != null) { + return taskFile.getTask(); + } + return null; + } public static void update(Project project) { final StudyToolWindow studyToolWindow = getStudyToolWindow(project); diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java index ada2a7edacab..c80c76f8f7b1 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java @@ -153,14 +153,14 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun protected void onTaskFailed(String message) { myTaskManger.setStatus(myTask, StudyStatus.Failed); ApplicationManager.getApplication().invokeLater( - () -> StudyCheckUtils.showTestResults(myProject, message)); + () -> StudyCheckUtils.showTestResults(myProject, message, false)); } protected void onTaskSolved(String message) { myTaskManger.setStatus(myTask, StudyStatus.Solved); ApplicationManager.getApplication().invokeLater( - () -> StudyCheckUtils.showTestResults(myProject, message)); + () -> StudyCheckUtils.showTestResults(myProject, message, true)); } private void runAfterTaskCheckedActions() { diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java index a41a9ee495d6..10433f001e3f 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java @@ -1,5 +1,6 @@ package com.jetbrains.edu.learning.checker; +import com.intellij.execution.ui.ConsoleViewContentType; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; @@ -29,8 +30,8 @@ import com.jetbrains.edu.learning.courseFormat.Task; import com.jetbrains.edu.learning.courseFormat.TaskFile; import com.jetbrains.edu.learning.editor.StudyEditor; import com.jetbrains.edu.learning.navigation.StudyNavigator; -import com.jetbrains.edu.learning.ui.StudyTestResultsToolWindow; import com.jetbrains.edu.learning.ui.StudyTestResultsToolWindowFactory; +import com.jetbrains.python.console.PythonConsoleView; import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -195,7 +196,7 @@ public class StudyCheckUtils { } } - public static void showTestResults(@NotNull final Project project, @NotNull final String message) { + public static void showTestResults(@NotNull final Project project, @NotNull final String message, boolean solved) { final ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project); ToolWindow window = toolWindowManager.getToolWindow("Test Results"); if (window == null) { @@ -207,8 +208,14 @@ public class StudyCheckUtils { final Content[] contents = window.getContentManager().getContents(); for (Content content : contents) { final JComponent component = content.getComponent(); - if (component instanceof StudyTestResultsToolWindow) { - ((StudyTestResultsToolWindow)component).setText(message); + if (component instanceof PythonConsoleView) { + ((PythonConsoleView)component).clear(); + if (!solved) { + ((PythonConsoleView)component).print(message, ConsoleViewContentType.ERROR_OUTPUT); + } + else { + ((PythonConsoleView)component).print(message, ConsoleViewContentType.NORMAL_OUTPUT); + } window.setAvailable(true, () -> {}); window.show(() -> {}); return; diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyTestsOutputParser.java b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyTestsOutputParser.java index 921ee4cba82c..fc21b40bc579 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyTestsOutputParser.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyTestsOutputParser.java @@ -3,6 +3,8 @@ package com.jetbrains.edu.learning.checker; import com.intellij.execution.process.ProcessOutput; import org.jetbrains.annotations.NotNull; +import java.util.List; + public class StudyTestsOutputParser { private static final String STUDY_PREFIX = "#educational_plugin"; public static final String TEST_OK = "test OK"; @@ -31,7 +33,9 @@ public class StudyTestsOutputParser { @NotNull public static TestsOutput getTestsOutput(@NotNull final ProcessOutput processOutput) { String congratulations = CONGRATULATIONS; - for (String line : processOutput.getStdoutLines()) { + final List lines = processOutput.getStdoutLines(); + for (int i = 0; i < lines.size(); i++) { + final String line = lines.get(i); if (line.startsWith(STUDY_PREFIX)) { if (line.contains(TEST_OK)) { continue; @@ -42,7 +46,18 @@ public class StudyTestsOutputParser { } if (line.contains(TEST_FAILED)) { - return new TestsOutput(false, line.substring(line.indexOf(TEST_FAILED) + TEST_FAILED.length())); + final StringBuilder builder = new StringBuilder(line.substring(line.indexOf(TEST_FAILED) + TEST_FAILED.length()) + "\n"); + for (int j = i + 1; j < lines.size(); j++) { + final String failedTextLine = lines.get(j); + if (!failedTextLine.contains(STUDY_PREFIX) || !failedTextLine.contains(CONGRATS_MESSAGE)) { + builder.append(failedTextLine); + builder.append("\n"); + } + else { + break; + } + } + return new TestsOutput(false, builder.toString()); } } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindow.kt b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindow.kt deleted file mode 100644 index 17ec0d915507..000000000000 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindow.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.jetbrains.edu.learning.ui - -import com.intellij.openapi.ui.SimpleToolWindowPanel -import java.awt.BorderLayout -import javax.swing.JPanel - - -class StudyTestResultsToolWindow: SimpleToolWindowPanel(false) { - private val studyBrowserWindow = StudyBrowserWindow(false, false) - fun init() { - val panel = JPanel(BorderLayout()) - studyBrowserWindow.loadContent("", null) - panel.add(studyBrowserWindow.panel, BorderLayout.CENTER) - setContent(panel) - } - - fun setText(text: String) { - studyBrowserWindow.loadContent(text, null) - } -} \ No newline at end of file diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindowFactory.kt b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindowFactory.kt index 65c9855d8262..0035ab8a71ab 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindowFactory.kt +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindowFactory.kt @@ -6,20 +6,30 @@ import com.intellij.openapi.fileEditor.FileEditorManagerListener import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.wm.ToolWindow +import com.jetbrains.edu.learning.StudyUtils +import com.jetbrains.python.console.PythonConsoleView class StudyTestResultsToolWindowFactory: StudyToolWindowFactory() { override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) { - val testResultsToolWindow = StudyTestResultsToolWindow() - testResultsToolWindow.init() + val currentTask = StudyUtils.getCurrentTask(project) + if (currentTask != null) { + val sdk = StudyUtils.findSdk(currentTask, project) + if (sdk != null) { + val testResultsToolWindow = PythonConsoleView(project, "Local test results", sdk); - toolWindow.isToHideOnEmptyContent = true - - val contentManager = toolWindow.contentManager - val content = contentManager.factory.createContent(testResultsToolWindow, null, false) - contentManager.addContent(content) + toolWindow.isToHideOnEmptyContent = true - project.messageBus.connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, getFileEditorManagerListener(toolWindow)) + val contentManager = toolWindow.contentManager + val content = contentManager.factory.createContent(testResultsToolWindow.component, null, false) + contentManager.addContent(content) + + project.messageBus.connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, getFileEditorManagerListener(toolWindow)) + } + else { + StudyUtils.showNoSdkNotification(currentTask, project) + } + } } fun getFileEditorManagerListener(toolWindow: ToolWindow): FileEditorManagerListener { diff --git a/python/educational-core/student/student.iml b/python/educational-core/student/student.iml index 6baed82fb927..c25ee848ee7b 100644 --- a/python/educational-core/student/student.iml +++ b/python/educational-core/student/student.iml @@ -22,5 +22,6 @@
+ \ No newline at end of file diff --git a/python/educational-python/resources/fileTemplates/internal/test_helper.py.ft b/python/educational-python/resources/fileTemplates/internal/test_helper.py.ft index c0fa0a682ce8..c31d2fa2ed7c 100644 --- a/python/educational-python/resources/fileTemplates/internal/test_helper.py.ft +++ b/python/educational-python/resources/fileTemplates/internal/test_helper.py.ft @@ -204,13 +204,13 @@ def check_samples(samples=()): output = get_file_output(arg_string=str(sample[0])) if "\n".join(output) != sample[1]: failed( - "Test from samples failed:

" - "Input:
{} " - "

" - "Expected:
{} " - "

" - "Your result:
{}".format(sample[0].replace("\n", "
"), - sample[1].replace("\n", "
"), + "Test from samples failed: \n \n" + "Input:\n {}" + "\n \n" + "Expected:\n {}" + "\n \n" + "Your result:\n {}".format(sample[0], + sample[1], "\n".join(output))) return set_congratulation_message("All test from samples passed. Now we are checking your solution on Stepic server.") diff --git a/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java b/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java index 34e59c4027b1..d8e4f3df2f2e 100644 --- a/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java +++ b/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java @@ -109,7 +109,7 @@ public class PyStudyCheckAction extends StudyCheckAction { } final StudyToolWindow toolWindow = StudyUtils.getStudyToolWindow(project); if (toolWindow != null) { - StudyCheckUtils.showTestResults(project, message); + StudyCheckUtils.showTestResults(project, message, false); StudyCheckUtils.navigateToFailedPlaceholder(myStudyState, myTask, myTaskDir, project); } }); From 1bc255e1215f82d67cdb2efd5bc2f48ea76285f6 Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Wed, 11 May 2016 14:29:15 +0300 Subject: [PATCH 22/44] Minor Small refactoring --- .../stepic/EduAdaptiveStepicConnector.java | 130 ++++++++++++------ 1 file changed, 85 insertions(+), 45 deletions(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java index ccbe0cd932a8..56b6e80619b9 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java @@ -28,7 +28,6 @@ import com.jetbrains.edu.learning.ui.StudyToolWindow; import org.apache.http.HttpEntity; import org.apache.http.HttpStatus; import org.apache.http.StatusLine; -import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; @@ -42,6 +41,7 @@ import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; +import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; @@ -54,7 +54,7 @@ import static com.jetbrains.edu.learning.stepic.EduStepicConnector.setHeaders; public class EduAdaptiveStepicConnector { public static final String PYTHON27 = "python27"; public static final String PYTHON3 = "python3"; - + private static final Logger LOG = Logger.getInstance(EduAdaptiveStepicConnector.class); private static final String STEPIC_URL = "https://stepic.org/"; private static final String STEPIC_API_URL = STEPIC_URL + "api/"; @@ -228,7 +228,7 @@ public class EduAdaptiveStepicConnector { "

"; task.setText(task.getText() + builder); } - + if (step.options.test != null) { for (StepicWrappers.TestFileWrapper wrapper : step.options.test) { task.addTestsTexts(wrapper.name, wrapper.text); @@ -255,7 +255,7 @@ public class EduAdaptiveStepicConnector { } return task; } - + private static String getCodeTemplateForTask(@Nullable StepicWrappers.CodeTemplatesWrapper codeTemplates, @NotNull final Task task, @NotNull final Project project) { @@ -265,52 +265,95 @@ public class EduAdaptiveStepicConnector { return codeTemplates.getTemplateForLanguage(languageString); } } - + return null; } @Nullable public static Pair checkTask(@NotNull final Project project, @NotNull final Task task) { + int attemptId = 0; try { - final int attemptId = getAttemptId(project, task, ATTEMPTS_URL); - if (attemptId != -1) { - final Editor editor = StudyUtils.getSelectedEditor(project); - String language = getLanguageString(task, project); - if (editor != null && language != null) { - final StepicWrappers.SubmissionToPostWrapper submissionToPostWrapper = - new StepicWrappers.SubmissionToPostWrapper(String.valueOf(attemptId), language, editor.getDocument().getText()); - final HttpPost httpPost = new HttpPost(STEPIC_API_URL + SUBMISSION_URL); - httpPost.setEntity(new StringEntity(new Gson().toJson(submissionToPostWrapper))); - final CloseableHttpClient client = getHttpClient(project); - setHeaders(httpPost, CONTENT_TYPE_APPL_JSON); - final CloseableHttpResponse execute = client.execute(httpPost); - StepicWrappers.ResultSubmissionWrapper wrapper = - new Gson().fromJson(EntityUtils.toString(execute.getEntity()), StepicWrappers.ResultSubmissionWrapper.class); - - final StepicUser user = StudyTaskManager.getInstance(project).getUser(); - if (user != null) { - final int id = user.getId(); - while (wrapper.submissions.length == 1 && wrapper.submissions[0].status.equals("evaluation")) { - TimeUnit.MILLISECONDS.sleep(500); - final URI submissionURI = new URIBuilder(STEPIC_API_URL + SUBMISSION_URL) - .addParameter("attempt", String.valueOf(attemptId)) - .addParameter("order", "desc") - .addParameter("user", String.valueOf(id)) - .build(); - final HttpGet httpGet = new HttpGet(submissionURI); - setHeaders(httpGet, CONTENT_TYPE_APPL_JSON); - final CloseableHttpResponse httpResponse = client.execute(httpGet); - wrapper = new Gson().fromJson(EntityUtils.toString(httpResponse.getEntity()), StepicWrappers.ResultSubmissionWrapper.class); - } - if (wrapper.submissions.length == 1) { - final boolean isSolved = !wrapper.submissions[0].status.equals("wrong"); - return Pair.create(isSolved, wrapper.submissions[0].hint); - } + attemptId = getAttemptId(project, task, ATTEMPTS_URL); + } + catch (IOException e) { + LOG.warn(e.getMessage()); + } + if (attemptId != -1) { + final Editor editor = StudyUtils.getSelectedEditor(project); + String language = getLanguageString(task, project); + if (editor != null && language != null) { + final CloseableHttpClient client = getHttpClient(project); + StepicWrappers.ResultSubmissionWrapper wrapper = postResultsForCheck(client, attemptId, language, editor.getDocument().getText()); + + final StepicUser user = StudyTaskManager.getInstance(project).getUser(); + if (user != null) { + final int id = user.getId(); + wrapper = getCheckResults(attemptId, id, client, wrapper); + if (wrapper.submissions.length == 1) { + final boolean isSolved = !wrapper.submissions[0].status.equals("wrong"); + return Pair.create(isSolved, wrapper.submissions[0].hint); } + else { + LOG.warn("Got a submission wrapper with incorrect submissions numer: " + wrapper.submissions.length); + } + } + else { + LOG.warn("User is null"); } } } - catch (ClientProtocolException e) { + else { + LOG.warn("Got an incorrect attempt id: " + attemptId); + } + return Pair.create(false, ""); + } + + @Nullable + private static StepicWrappers.ResultSubmissionWrapper postResultsForCheck(@NotNull final CloseableHttpClient client, + final int attemptId, + @NotNull final String language, + @NotNull final String text) { + final CloseableHttpResponse response; + try { + final StepicWrappers.SubmissionToPostWrapper submissionToPostWrapper = + new StepicWrappers.SubmissionToPostWrapper(String.valueOf(attemptId), language, text); + final HttpPost httpPost = new HttpPost(STEPIC_API_URL + SUBMISSION_URL); + setHeaders(httpPost, CONTENT_TYPE_APPL_JSON); + try { + httpPost.setEntity(new StringEntity(new Gson().toJson(submissionToPostWrapper))); + } + catch (UnsupportedEncodingException e) { + LOG.warn(e.getMessage()); + } + response = client.execute(httpPost); + return new Gson().fromJson(EntityUtils.toString(response.getEntity()), StepicWrappers.ResultSubmissionWrapper.class); + } + catch (IOException e) { + LOG.warn(e.getMessage()); + } + return null; + } + + @NotNull + private static StepicWrappers.ResultSubmissionWrapper getCheckResults(int attemptId, + int id, + CloseableHttpClient client, + StepicWrappers.ResultSubmissionWrapper wrapper) { + try { + while (wrapper.submissions.length == 1 && wrapper.submissions[0].status.equals("evaluation")) { + TimeUnit.MILLISECONDS.sleep(500); + final URI submissionURI = new URIBuilder(STEPIC_API_URL + SUBMISSION_URL) + .addParameter("attempt", String.valueOf(attemptId)) + .addParameter("order", "desc") + .addParameter("user", String.valueOf(id)) + .build(); + final HttpGet httpGet = new HttpGet(submissionURI); + setHeaders(httpGet, CONTENT_TYPE_APPL_JSON); + final CloseableHttpResponse httpResponse = client.execute(httpGet); + wrapper = new Gson().fromJson(EntityUtils.toString(httpResponse.getEntity()), StepicWrappers.ResultSubmissionWrapper.class); + } + } + catch (InterruptedException e) { LOG.warn(e.getMessage()); } catch (IOException e) { @@ -319,10 +362,7 @@ public class EduAdaptiveStepicConnector { catch (URISyntaxException e) { LOG.warn(e.getMessage()); } - catch (InterruptedException e) { - LOG.warn(e.getMessage()); - } - return Pair.create(false, ""); + return wrapper; } @Nullable @@ -332,7 +372,7 @@ public class EduAdaptiveStepicConnector { final Sdk language = StudyExecutor.INSTANCE.forLanguage(pythonLanguage).findSdk(project); if (language != null) { final String versionString = language.getVersionString(); - if (versionString != null ) { + if (versionString != null) { final List versionStringParts = StringUtil.split(versionString, " "); if (versionStringParts.size() == 2) { return versionStringParts.get(1).startsWith("2") ? PYTHON27 : PYTHON3; From 9538890b87da70016d52369dd18e59ba40cb490b Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Thu, 12 May 2016 15:23:59 +0300 Subject: [PATCH 23/44] Add recommended task after solving current task --- .../edu/learning/checker/StudyCheckTask.java | 9 +- .../stepic/EduAdaptiveStepicConnector.java | 86 ++++++++++++++----- .../edu/learning/stepic/StepicWrappers.java | 57 ++++++++---- 3 files changed, 110 insertions(+), 42 deletions(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java index c80c76f8f7b1..7fe00c813235 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java @@ -127,12 +127,15 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun if (testOutput != null) { if (testOutput.isSuccess()) { final Pair pair = EduAdaptiveStepicConnector.checkTask(myProject, myTask); - if (pair != null && !pair.getSecond().isEmpty()) { - final String checkMessage = pair.getSecond(); + if (pair != null && !(!pair.getFirst() && pair.getSecond().isEmpty())) { if (pair.getFirst()) { - onTaskSolved(checkMessage); + onTaskSolved("Congratulations! Remote tests passed."); + if (myStatusBeforeCheck != StudyStatus.Solved) { + EduAdaptiveStepicConnector.addNextRecommendedTask(myProject, 2); + } } else { + final String checkMessage = pair.getSecond(); onTaskFailed(checkMessage); } runAfterTaskCheckedActions(); diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java index 56b6e80619b9..ff204480c17a 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java @@ -48,8 +48,7 @@ import java.util.HashMap; import java.util.List; import java.util.concurrent.TimeUnit; -import static com.jetbrains.edu.learning.stepic.EduStepicConnector.getHttpClient; -import static com.jetbrains.edu.learning.stepic.EduStepicConnector.setHeaders; +import static com.jetbrains.edu.learning.stepic.EduStepicConnector.*; public class EduAdaptiveStepicConnector { public static final String PYTHON27 = "python27"; @@ -64,6 +63,9 @@ public class EduAdaptiveStepicConnector { private static final String RECOMMENDATION_REACTIONS_URL = "recommendation-reactions"; private static final String ATTEMPTS_URL = "attempts"; private static final String SUBMISSION_URL = "submissions"; + private static final String ASSIGNMENT_URL = "/assignments"; + private static final String VIEWS_URL = "/views"; + private static final String UNITS_URL = "/units"; @Nullable public static Task getNextRecommendation(@NotNull final Project project, @NotNull Course course) { @@ -81,25 +83,33 @@ public class EduAdaptiveStepicConnector { final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : ""; if (statusLine.getStatusCode() == HttpStatus.SC_OK) { - Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); + final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); final StepicWrappers.RecommendationWrapper recomWrapper = gson.fromJson(responseString, StepicWrappers.RecommendationWrapper.class); if (recomWrapper.recommendations.length != 0) { final StepicWrappers.Recommendation recommendation = recomWrapper.recommendations[0]; final String lessonId = recommendation.lesson; final StepicWrappers.LessonContainer - lessonContainer = EduStepicConnector.getFromStepic(LESSON_URL + lessonId, StepicWrappers.LessonContainer.class); - final Lesson realLesson = lessonContainer.lessons.get(0); - course.getLessons().get(0).id = Integer.parseInt(lessonId); + lessonContainer = getFromStepic(LESSON_URL + lessonId, StepicWrappers.LessonContainer.class); + if (lessonContainer.lessons.size() == 1) { + final Lesson realLesson = lessonContainer.lessons.get(0); + course.getLessons().get(0).id = Integer.parseInt(lessonId); - for (int stepId : realLesson.steps) { - final StepicWrappers.Step step = EduStepicConnector.getStep(stepId); - if (step.name.equals("code")) { - return getTaskFromStep(project, stepId, step, realLesson.getName()); + viewAllSteps(client, realLesson.id); + + for (int stepId : realLesson.steps) { + final StepicWrappers.Step step = getStep(stepId); + if (step.name.equals("code")) { + return getTaskFromStep(project, stepId, step, realLesson.getName()); + } } - } - LOG.warn("Got a lesson without code part as a recommendation"); + LOG.warn("Got a lesson without code part as a recommendation"); + } + else { + LOG.warn("Got unexpected number of lessons: " + lessonContainer.lessons.size()); + } + VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); } } else { @@ -115,6 +125,37 @@ public class EduAdaptiveStepicConnector { return null; } + private static void viewAllSteps(CloseableHttpClient client, int lessonId) throws URISyntaxException, IOException { + final URI unitsUrl = new URIBuilder(UNITS_URL).addParameter("lesson", String.valueOf(lessonId)).build(); + final StepicWrappers.UnitContainer unitContainer = getFromStepic(unitsUrl.toString(), StepicWrappers.UnitContainer.class); + if (unitContainer.units.size() != 1) { + LOG.warn("Got unexpected numbers of units: " + unitContainer.units.size()); + return; + } + + final URIBuilder builder = new URIBuilder(ASSIGNMENT_URL); + for (Integer step : unitContainer.units.get(0).assignments) { + builder.addParameter("ids[]", String.valueOf(step)); + } + final URI assignmentUrl = builder.build(); + final StepicWrappers.AssignmentsWrapper assignments = getFromStepic(assignmentUrl.toString(), StepicWrappers.AssignmentsWrapper.class); + if (assignments.assignments.size() > 0) { + for (StepicWrappers.Assignment assignment : assignments.assignments) { + final HttpPost post = new HttpPost(STEPIC_API_URL + VIEWS_URL); + final StepicWrappers.ViewsWrapper viewsWrapper = new StepicWrappers.ViewsWrapper(assignment.id, assignment.step); + post.setEntity(new StringEntity(new Gson().toJson(viewsWrapper))); + setHeaders(post, CONTENT_TYPE_APPL_JSON); + final CloseableHttpResponse viewPostResult = client.execute(post); + if (viewPostResult.getStatusLine().getStatusCode() != HttpStatus.SC_CREATED) { + LOG.warn("Error while Views post, code: " + viewPostResult.getStatusLine().getStatusCode()); + } + } + } + else { + LOG.warn("Got assignments of incorrect length: " + assignments.assignments.size()); + } + } + public static boolean postRecommendationReaction(@NotNull final Project project, @NotNull final String lessonId, @NotNull final String user, int reaction) { @@ -138,7 +179,6 @@ public class EduAdaptiveStepicConnector { final StudyEditor editor = StudyUtils.getSelectedStudyEditor(project); final Course course = StudyTaskManager.getInstance(project).getCourse(); if (course != null && editor != null && editor.getTaskFile() != null) { - // TODO: get user from settings final StepicUser user = StudyTaskManager.getInstance(project).getUser(); if (user != null && postRecommendationReaction(project, String.valueOf(editor.getTaskFile().getTask().getLesson().id), @@ -148,11 +188,12 @@ public class EduAdaptiveStepicConnector { if (task != null) { final Lesson adaptive = course.getLessons().get(0); if (reaction == 0 || reaction == -1) { - final Task unsolvedTask = adaptive.getTaskList().get(0); + final Task unsolvedTask = adaptive.getTaskList().get(adaptive.getTaskList().size() - 1); unsolvedTask.setName(task.getName()); unsolvedTask.setStepicId(task.getStepicId()); unsolvedTask.setText(task.getText()); unsolvedTask.getTaskFiles().clear(); + unsolvedTask.getTestsText().clear(); unsolvedTask.getTaskFiles().putAll(task.getTaskFiles()); final File lessonDirectory = new File(course.getCourseDirectory(), EduNames.LESSON + String.valueOf(adaptive.getIndex())); @@ -164,7 +205,6 @@ public class EduAdaptiveStepicConnector { } adaptive.initLesson(course, false); - VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); } else { adaptive.addTask(task); @@ -172,23 +212,23 @@ public class EduAdaptiveStepicConnector { final VirtualFile lessonDir = project.getBaseDir().findChild(EduNames.LESSON + String.valueOf(adaptive.getIndex())); if (lessonDir != null) { - ApplicationManager.getApplication().runWriteAction(() -> { + ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> { try { StudyGenerator.createTask(task, lessonDir, new File(course.getCourseDirectory(), lessonDir.getName()), project); } catch (IOException e) { LOG.warn(e.getMessage()); } - }); + })); } final File lessonDirectory = new File(course.getCourseDirectory(), EduNames.LESSON + String.valueOf(adaptive.getIndex())); final File taskDirectory = new File(lessonDirectory, EduNames.TASK + String.valueOf(adaptive.getTaskList().size())); StudyProjectGenerator.flushTask(task, taskDirectory); adaptive.initLesson(course, false); - VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); } } + VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); } else { LOG.warn("Recommendation reactions weren't posted"); @@ -271,7 +311,7 @@ public class EduAdaptiveStepicConnector { @Nullable public static Pair checkTask(@NotNull final Project project, @NotNull final Task task) { - int attemptId = 0; + int attemptId = -1; try { attemptId = getAttemptId(project, task, ATTEMPTS_URL); } @@ -284,7 +324,7 @@ public class EduAdaptiveStepicConnector { if (editor != null && language != null) { final CloseableHttpClient client = getHttpClient(project); StepicWrappers.ResultSubmissionWrapper wrapper = postResultsForCheck(client, attemptId, language, editor.getDocument().getText()); - + final StepicUser user = StudyTaskManager.getInstance(project).getUser(); if (user != null) { final int id = user.getId(); @@ -350,7 +390,8 @@ public class EduAdaptiveStepicConnector { final HttpGet httpGet = new HttpGet(submissionURI); setHeaders(httpGet, CONTENT_TYPE_APPL_JSON); final CloseableHttpResponse httpResponse = client.execute(httpGet); - wrapper = new Gson().fromJson(EntityUtils.toString(httpResponse.getEntity()), StepicWrappers.ResultSubmissionWrapper.class); + final String entity = EntityUtils.toString(httpResponse.getEntity()); + wrapper = new Gson().fromJson(entity, StepicWrappers.ResultSubmissionWrapper.class); } } catch (InterruptedException e) { @@ -395,8 +436,9 @@ public class EduAdaptiveStepicConnector { final CloseableHttpClient client = getHttpClient(project); setHeaders(post, CONTENT_TYPE_APPL_JSON); final CloseableHttpResponse httpResponse = client.execute(post); + final String entity = EntityUtils.toString(httpResponse.getEntity()); final StepicWrappers.AttemptContainer container = - new Gson().fromJson(EntityUtils.toString(httpResponse.getEntity()), StepicWrappers.AttemptContainer.class); + new Gson().fromJson(entity, StepicWrappers.AttemptContainer.class); return (container.attempts != null && !container.attempts.isEmpty()) ? container.attempts.get(0).id : -1; } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java index c40fdd3f48c4..c8e970fdb07f 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicWrappers.java @@ -24,7 +24,7 @@ import java.util.Map; public class StepicWrappers { private static final Logger LOG = Logger.getInstance(StepOptions.class); - + static class StepContainer { List steps; } @@ -52,7 +52,7 @@ public class StepicWrappers { @Expose Integer executionMemoryLimit; @Expose Integer executionTimeLimit; @Expose CodeTemplatesWrapper codeTemplates; - + public static StepOptions fromTask(final Project project, @NotNull final Task task) { final StepOptions source = new StepOptions(); setTests(task, source, project); @@ -113,21 +113,21 @@ public class StepicWrappers { } } } - + static class CodeTemplatesWrapper { String python3; String python27; - + @Nullable public String getTemplateForLanguage(@NotNull final String langauge) { if (langauge.equals(EduAdaptiveStepicConnector.PYTHON27)) { return python27; } - + if (langauge.equals(EduAdaptiveStepicConnector.PYTHON3)) { return python3; } - + return null; } } @@ -218,6 +218,7 @@ public class StepicWrappers { int section; int lesson; int position; + List assignments; } static class UnitContainer { @@ -225,7 +226,7 @@ public class StepicWrappers { List units; } - static class UnitWrapper{ + static class UnitWrapper { Unit unit; } @@ -238,6 +239,7 @@ public class StepicWrappers { int step; int id; } + public AttemptWrapper(int step) { attempt = new Attempt(step); } @@ -254,11 +256,12 @@ public class StepicWrappers { String time_left; String user; String user_id; - + public Attempt(int step) { this.step = step; } } + public AttemptToPostWrapper(int step) { attempt = new Attempt(step); } @@ -294,7 +297,6 @@ public class StepicWrappers { static class Submission { int attempt; - private final Reply reply; public Submission(String score, int attempt, ArrayList files) { @@ -312,7 +314,6 @@ public class StepicWrappers { } } } - } static class UserWrapper { @@ -351,18 +352,15 @@ public class StepicWrappers { String id; String lesson; } - static class SubmissionToPostWrapper { Submission submission; - - public SubmissionToPostWrapper(@NotNull String attemptId, @NotNull String language, @NotNull String code) { submission = new Submission(attemptId, new Submission.Reply(language, code)); } - + static class Submission { String attempt; Reply reply; @@ -382,10 +380,9 @@ public class StepicWrappers { } } } - } - - static class ResultSubmissionWrapper { + + static class ResultSubmissionWrapper { ResultSubmission[] submissions; static class ResultSubmission { @@ -394,5 +391,31 @@ public class StepicWrappers { String hint; } } + + static class AssignmentsWrapper { + List assignments; + } + static class Assignment { + int id; + int step; + } + + static class ViewsWrapper { + View view; + + public ViewsWrapper(final int assignment, final int step) { + this.view = new View(assignment, step); + } + } + + static class View { + int assignment; + int step; + + public View(int assignment, int step) { + this.assignment = assignment; + this.step = step; + } + } } From 8d56a442a2772c5409fd02d7d7b4f2844b880fc9 Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Fri, 13 May 2016 11:51:31 +0300 Subject: [PATCH 24/44] Add title to stepic login form --- .../src/com/jetbrains/edu/learning/stepic/LoginDialog.java | 1 + 1 file changed, 1 insertion(+) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/LoginDialog.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/LoginDialog.java index c4805500fe09..09a132765873 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/LoginDialog.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/LoginDialog.java @@ -14,6 +14,7 @@ public class LoginDialog extends DialogWrapper { myLoginPanel = new LoginPanel(this); setTitle("Login to Stepic"); setOKButtonText("Login"); + setTitle("Login to Stepic"); init(); } From 190b9618a8185fccb032c50a8c0990b6517feac5 Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Fri, 13 May 2016 21:31:55 +0300 Subject: [PATCH 25/44] Minor refactoring --- .../jetbrains/edu/learning/editor/StudyEditor.java | 12 ++++++------ .../edu/learning/editor/StudyFileEditorProvider.java | 8 ++++---- .../edu/learning/ui/StudyJavaFxToolWindow.java | 3 ++- .../edu/learning/ui/StudySwingToolWindow.java | 3 ++- .../jetbrains/edu/learning/ui/StudyToolWindow.java | 2 +- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/editor/StudyEditor.java b/python/educational-core/student/src/com/jetbrains/edu/learning/editor/StudyEditor.java index 50f8ad8cf759..2985e125d38a 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/editor/StudyEditor.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/editor/StudyEditor.java @@ -5,9 +5,9 @@ import com.intellij.openapi.fileEditor.impl.text.PsiAwareTextEditorImpl; import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; +import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.core.EduDocumentListener; import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.StudyUtils; import org.jetbrains.annotations.NotNull; import java.util.HashMap; @@ -21,6 +21,11 @@ public class StudyEditor extends PsiAwareTextEditorImpl { private final TaskFile myTaskFile; private static final Map myDocumentListeners = new HashMap(); + public StudyEditor(@NotNull final Project project, @NotNull final VirtualFile file) { + super(project, file, TextEditorProvider.getInstance()); + myTaskFile = StudyUtils.getTaskFile(project, file); + } + public TaskFile getTaskFile() { return myTaskFile; } @@ -30,11 +35,6 @@ public class StudyEditor extends PsiAwareTextEditorImpl { myDocumentListeners.put(document, listener); } - public StudyEditor(@NotNull final Project project, @NotNull final VirtualFile file) { - super(project, file, TextEditorProvider.getInstance()); - myTaskFile = StudyUtils.getTaskFile(project, file); - } - public static void removeListener(Document document) { final EduDocumentListener listener = myDocumentListeners.get(document); if (listener != null) { diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/editor/StudyFileEditorProvider.java b/python/educational-core/student/src/com/jetbrains/edu/learning/editor/StudyFileEditorProvider.java index cdfd8d4535db..34ee809bb0cf 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/editor/StudyFileEditorProvider.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/editor/StudyFileEditorProvider.java @@ -8,14 +8,14 @@ import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.edu.learning.courseFormat.TaskFile; import com.jetbrains.edu.learning.StudyUtils; +import com.jetbrains.edu.learning.courseFormat.TaskFile; import org.jdom.Element; import org.jetbrains.annotations.NotNull; -class StudyFileEditorProvider implements FileEditorProvider, DumbAware { - static final private String EDITOR_TYPE_ID = "StudyEditor"; - final private FileEditorProvider defaultTextEditorProvider = TextEditorProvider.getInstance(); +public class StudyFileEditorProvider implements FileEditorProvider, DumbAware { + public static final String EDITOR_TYPE_ID = "StudyEditor"; + private final FileEditorProvider defaultTextEditorProvider = TextEditorProvider.getInstance(); @Override public boolean accept(@NotNull Project project, @NotNull VirtualFile file) { diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyJavaFxToolWindow.java b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyJavaFxToolWindow.java index 2bf50d63bc6f..a1f20ef32283 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyJavaFxToolWindow.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyJavaFxToolWindow.java @@ -18,6 +18,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectUtil; import com.jetbrains.edu.learning.StudyPluginConfigurator; import com.jetbrains.edu.learning.StudyUtils; +import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -40,7 +41,7 @@ public class StudyJavaFxToolWindow extends StudyToolWindow { } @Override - public void setTaskText(String text) { + public void setTaskText(@NotNull String text) { StudyPluginConfigurator configurator = StudyUtils.getConfigurator(ProjectUtil.guessCurrentProject(this)); myBrowserWindow.loadContent(text, configurator); } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudySwingToolWindow.java b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudySwingToolWindow.java index ae4914313f10..3e88467cf9a4 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudySwingToolWindow.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudySwingToolWindow.java @@ -20,6 +20,7 @@ import com.intellij.openapi.project.Project; import com.intellij.ui.BrowserHyperlinkListener; import com.intellij.ui.ColorUtil; import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; import javax.swing.*; import javax.swing.border.EmptyBorder; @@ -59,7 +60,7 @@ public class StudySwingToolWindow extends StudyToolWindow { return myTaskTextPane; } - public void setTaskText(String text) { + public void setTaskText(@NotNull String text) { myTaskTextPane.setText(text); } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java index d8261baece92..a6bcb056660a 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java @@ -219,5 +219,5 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D } } - public abstract void setTaskText(String text); + public abstract void setTaskText(@NotNull String text); } From db8c7c02462a828a6c86767a947546d443022c7c Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Fri, 13 May 2016 21:39:57 +0300 Subject: [PATCH 26/44] Update task tests properly for recommended task: 1) flush task to recreate test files cache with new data 2) properly set course directory for adaptive courses --- .../jetbrains/edu/learning/StudyUtils.java | 11 ++- .../StudyProjectGenerator.java | 29 ++++---- .../stepic/EduAdaptiveStepicConnector.java | 72 +++++++++++++++++-- 3 files changed, 89 insertions(+), 23 deletions(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java b/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java index a7146cf9d039..eee4250e8f6c 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java @@ -513,7 +513,12 @@ public class StudyUtils { final StudyToolWindow studyToolWindow = getStudyToolWindow(project); if (studyToolWindow != null) { String taskText = getTaskText(project); - studyToolWindow.setTaskText(taskText); + if (taskText != null) { + studyToolWindow.setTaskText(taskText); + } + else { + LOG.warn("Task text is null"); + } } } @@ -537,12 +542,12 @@ public class StudyUtils { public static File getCourseDirectory(@NotNull Project project, Course course) { final File courseDirectory; if (course.isAdaptive()) { - courseDirectory = new File(StudyProjectGenerator.ourCoursesDir, + courseDirectory = new File(StudyProjectGenerator.OUR_COURSES_DIR, StudyProjectGenerator.ADAPTIVE_COURSE_PREFIX + course.getName() + "_" + StudyTaskManager.getInstance(project).getUser().getEmail()); } else { - courseDirectory = new File(StudyProjectGenerator.ourCoursesDir, course.getName()); + courseDirectory = new File(StudyProjectGenerator.OUR_COURSES_DIR, course.getName()); } return courseDirectory; } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java b/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java index 3f892dcde93d..caef41383c1a 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java @@ -48,7 +48,7 @@ public class StudyProjectGenerator { public static final String AUTHOR_ATTRIBUTE = "authors"; public static final String LANGUAGE_ATTRIBUTE = "language"; public static final String ADAPTIVE_COURSE_PREFIX = "__AdaptivePyCharmPython__"; - public static final File ourCoursesDir = new File(PathManager.getConfigPath(), "courses"); + public static final File OUR_COURSES_DIR = new File(PathManager.getConfigPath(), "courses"); private static final Logger LOG = Logger.getInstance(StudyProjectGenerator.class.getName()); private static final String COURSE_NAME_ATTRIBUTE = "name"; private static final String COURSE_DESCRIPTION = "description"; @@ -69,6 +69,7 @@ public class StudyProjectGenerator { public void generateProject(@NotNull final Project project, @NotNull final VirtualFile baseDir) { StudyTaskManager.getInstance(project).setUser(myUser); final Course course = getCourse(project); + final File courseDirectory = StudyUtils.getCourseDirectory(project, course); if (course == null) { LOG.warn("Course is null"); return; @@ -78,9 +79,8 @@ public class StudyProjectGenerator { () -> DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, () -> ApplicationManager.getApplication().runWriteAction(() -> { course.initCourse(false); - StudyGenerator.createCourse(course, baseDir, StudyUtils - .getCourseDirectory(project, course), project); - course.setCourseDirectory(new File(ourCoursesDir, mySelectedCourseInfo.getName()).getAbsolutePath()); + StudyGenerator.createCourse(course, baseDir, courseDirectory, project); + course.setCourseDirectory(courseDirectory.getAbsolutePath()); VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); StudyProjectComponent.getInstance(project).registerStudyToolWindow(course); openFirstTask(course, project); @@ -90,7 +90,7 @@ public class StudyProjectGenerator { protected Course getCourse(@NotNull final Project project) { Reader reader = null; try { - final File courseFile = new File(new File(ourCoursesDir, mySelectedCourseInfo.getName()), EduNames.COURSE_META_FILE); + final File courseFile = new File(new File(OUR_COURSES_DIR, mySelectedCourseInfo.getName()), EduNames.COURSE_META_FILE); if (courseFile.exists()) { reader = new InputStreamReader(new FileInputStream(courseFile), "UTF-8"); Gson gson = new GsonBuilder().create(); @@ -221,6 +221,9 @@ public class StudyProjectGenerator { final Map testsText = task.getTestsText(); for (Map.Entry entry : testsText.entrySet()) { final File testsFile = new File(taskDirectory, entry.getKey()); + if (testsFile.exists()) { + FileUtil.delete(testsFile); + } FileUtil.createIfDoesntExist(testsFile); try { FileUtil.writeToFile(testsFile, entry.getValue()); @@ -273,7 +276,7 @@ public class StudyProjectGenerator { */ @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") public static void flushCache(List courses) { - File cacheFile = new File(ourCoursesDir, CACHE_NAME); + File cacheFile = new File(OUR_COURSES_DIR, CACHE_NAME); PrintWriter writer = null; try { if (!createCacheFile(cacheFile)) return; @@ -294,8 +297,8 @@ public class StudyProjectGenerator { } private static boolean createCacheFile(File cacheFile) throws IOException { - if (!ourCoursesDir.exists()) { - final boolean created = ourCoursesDir.mkdirs(); + if (!OUR_COURSES_DIR.exists()) { + final boolean created = OUR_COURSES_DIR.mkdirs(); if (!created) { LOG.error("Cannot flush courses cache. Can't create courses directory"); return false; @@ -312,7 +315,7 @@ public class StudyProjectGenerator { } public List getCourses(boolean force) { - if (ourCoursesDir.exists()) { + if (OUR_COURSES_DIR.exists()) { myCourses = getCoursesFromCache(); } if (force || myCourses.isEmpty()) { @@ -340,7 +343,7 @@ public class StudyProjectGenerator { } public static List getBundledIntro() { - final File introCourse = new File(ourCoursesDir, "Introduction to Python"); + final File introCourse = new File(OUR_COURSES_DIR, "Introduction to Python"); if (introCourse.exists()) { final CourseInfo courseInfo = getCourseInfo(introCourse); @@ -351,7 +354,7 @@ public class StudyProjectGenerator { public static List getCoursesFromCache() { List courses = new ArrayList<>(); - final File cacheFile = new File(ourCoursesDir, CACHE_NAME); + final File cacheFile = new File(OUR_COURSES_DIR, CACHE_NAME); if (!cacheFile.exists()) { return courses; } @@ -395,13 +398,13 @@ public class StudyProjectGenerator { try { String fileName = file.getName(); String unzippedName = fileName.substring(0, fileName.indexOf(".")); - File courseDir = new File(ourCoursesDir, unzippedName); + File courseDir = new File(OUR_COURSES_DIR, unzippedName); ZipUtil.unzip(null, courseDir, file, null, null, true); CourseInfo courseName = addCourse(myCourses, courseDir); flushCache(myCourses); if (courseName != null && !courseName.getName().equals(unzippedName)) { //noinspection ResultOfMethodCallIgnored - courseDir.renameTo(new File(ourCoursesDir, courseName.getName())); + courseDir.renameTo(new File(OUR_COURSES_DIR, courseName.getName())); //noinspection ResultOfMethodCallIgnored courseDir.delete(); } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java index ff204480c17a..da81b0081e27 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java @@ -10,7 +10,9 @@ import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import com.jetbrains.edu.learning.StudyTaskManager; @@ -46,6 +48,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; import static com.jetbrains.edu.learning.stepic.EduStepicConnector.*; @@ -66,6 +69,7 @@ public class EduAdaptiveStepicConnector { private static final String ASSIGNMENT_URL = "/assignments"; private static final String VIEWS_URL = "/views"; private static final String UNITS_URL = "/units"; + private static final String DEFAULT_TASKFILE_NAME = "code.py"; @Nullable public static Task getNextRecommendation(@NotNull final Project project, @NotNull Course course) { @@ -180,9 +184,11 @@ public class EduAdaptiveStepicConnector { final Course course = StudyTaskManager.getInstance(project).getCourse(); if (course != null && editor != null && editor.getTaskFile() != null) { final StepicUser user = StudyTaskManager.getInstance(project).getUser(); - if (user != null && - postRecommendationReaction(project, String.valueOf(editor.getTaskFile().getTask().getLesson().id), - String.valueOf(user.id), reaction)) { + + final boolean recommendationReaction = + user != null && postRecommendationReaction(project, String.valueOf(editor.getTaskFile().getTask().getLesson().id), + String.valueOf(user.id), reaction); + if (recommendationReaction) { final Task task = getNextRecommendation(project, course); if (task != null) { @@ -192,19 +198,39 @@ public class EduAdaptiveStepicConnector { unsolvedTask.setName(task.getName()); unsolvedTask.setStepicId(task.getStepicId()); unsolvedTask.setText(task.getText()); - unsolvedTask.getTaskFiles().clear(); unsolvedTask.getTestsText().clear(); - unsolvedTask.getTaskFiles().putAll(task.getTaskFiles()); + final Map testsText = task.getTestsText(); + for (String testName: testsText.keySet()) { + unsolvedTask.addTestsTexts(testName, testsText.get(testName)); + } + final Map taskFiles = task.getTaskFiles(); + if (taskFiles.size() == 1) { + final TaskFile taskFile = editor.getTaskFile(); + taskFile.text = ((TaskFile)taskFiles.values().toArray()[0]).text; + ApplicationManager.getApplication().invokeLater(() -> + ApplicationManager.getApplication().runWriteAction(() -> + editor.getEditor() + .getDocument() + .setText( + taskFiles.get( + DEFAULT_TASKFILE_NAME).text))); + } + else { + LOG.warn("Got task without unexpected number of task files: " + taskFiles.size()); + } final File lessonDirectory = new File(course.getCourseDirectory(), EduNames.LESSON + String.valueOf(adaptive.getIndex())); final File taskDirectory = new File(lessonDirectory, EduNames.TASK + String.valueOf(adaptive.getTaskList().size())); StudyProjectGenerator.flushTask(task, taskDirectory); + final VirtualFile lessonDir = project.getBaseDir().findChild(EduNames.LESSON + String.valueOf(adaptive.getIndex())); + + if (lessonDir != null) { + createTestFiles(course, task, unsolvedTask, lessonDir); + } final StudyToolWindow window = StudyUtils.getStudyToolWindow(project); if (window != null) { window.setTaskText(unsolvedTask.getText()); } - - adaptive.initLesson(course, false); } else { adaptive.addTask(task); @@ -236,6 +262,37 @@ public class EduAdaptiveStepicConnector { } } + private static void createTestFiles(Course course, Task task, Task unsolvedTask, VirtualFile lessonDir) { + ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> { + try { + final VirtualFile taskDir = VfsUtil + .findFileByIoFile(new File(lessonDir.getCanonicalPath(), EduNames.TASK + unsolvedTask.getIndex()), true); + final File resourceRoot = new File(course.getCourseDirectory(), lessonDir.getName()); + File newResourceRoot = null; + if (taskDir != null) { + newResourceRoot = new File(resourceRoot, taskDir.getName()); + File[] filesInTask = newResourceRoot.listFiles(); + if (filesInTask != null) { + for (File file : filesInTask) { + String fileName = file.getName(); + if (!task.isTaskFile(fileName)) { + File resourceFile = new File(newResourceRoot, fileName); + File fileInProject = new File(taskDir.getCanonicalPath(), fileName); + FileUtil.copy(resourceFile, fileInProject); + } + } + } + } + else { + LOG.warn("Task directory is null"); + } + } + catch (IOException e) { + LOG.warn(e.getMessage()); + } + })); + } + @NotNull private static Task getTaskFromStep(Project project, int lessonID, @@ -444,6 +501,7 @@ public class EduAdaptiveStepicConnector { private static void createTestFileFromSamples(@NotNull final Task task, @NotNull final List> samples) { + String testText = "from test_helper import check_samples\n\n" + "if __name__ == '__main__':\n" + " check_samples(samples=" + new GsonBuilder().create().toJson(samples) + ")"; From 20eac8501468fd2cd3e9fc243ce20b00a96ac3d2 Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Mon, 16 May 2016 16:41:41 +0300 Subject: [PATCH 27/44] Flush course to json after adding new recommended task --- .../StudyProjectGenerator.java | 55 +++++++++++++------ .../stepic/EduAdaptiveStepicConnector.java | 30 +++++++--- 2 files changed, 58 insertions(+), 27 deletions(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java b/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java index caef41383c1a..7f8a01b3cec7 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java @@ -69,16 +69,15 @@ public class StudyProjectGenerator { public void generateProject(@NotNull final Project project, @NotNull final VirtualFile baseDir) { StudyTaskManager.getInstance(project).setUser(myUser); final Course course = getCourse(project); - final File courseDirectory = StudyUtils.getCourseDirectory(project, course); if (course == null) { LOG.warn("Course is null"); return; } + final File courseDirectory = StudyUtils.getCourseDirectory(project, course); StudyTaskManager.getInstance(project).setCourse(course); ApplicationManager.getApplication().invokeLater( () -> DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, () -> ApplicationManager.getApplication().runWriteAction(() -> { - course.initCourse(false); StudyGenerator.createCourse(course, baseDir, courseDirectory, project); course.setCourseDirectory(courseDirectory.getAbsolutePath()); VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); @@ -87,29 +86,49 @@ public class StudyProjectGenerator { }))); } + @Nullable protected Course getCourse(@NotNull final Project project) { - Reader reader = null; - try { final File courseFile = new File(new File(OUR_COURSES_DIR, mySelectedCourseInfo.getName()), EduNames.COURSE_META_FILE); if (courseFile.exists()) { - reader = new InputStreamReader(new FileInputStream(courseFile), "UTF-8"); - Gson gson = new GsonBuilder().create(); - final Course course = gson.fromJson(reader, Course.class); - course.initCourse(false); - return course; + return readCourseFromCache(courseFile); } + else { + final File adaptiveCourseFile = new File(new File(OUR_COURSES_DIR, ADAPTIVE_COURSE_PREFIX + + mySelectedCourseInfo.getName() + "_" + + myUser.getEmail()), EduNames.COURSE_META_FILE); + if (adaptiveCourseFile.exists()) { + return readCourseFromCache(adaptiveCourseFile); + } + + } + final Course course = EduStepicConnector.getCourse(project, mySelectedCourseInfo); + if (course != null) { + flushCourse(project, course); + course.initCourse(false); } - catch (FileNotFoundException | UnsupportedEncodingException e) { - LOG.error(e); + return course; + } + + @Nullable + private static Course readCourseFromCache(File courseFile) { + Reader reader = null; + try { + reader = new InputStreamReader(new FileInputStream(courseFile), "UTF-8"); + Gson gson = new GsonBuilder().create(); + final Course course = gson.fromJson(reader, Course.class); + course.initCourse(true); + return course; + } + catch (UnsupportedEncodingException e) { + LOG.warn(e.getMessage()); + } + catch (FileNotFoundException e) { + LOG.warn(e.getMessage()); } finally { StudyUtils.closeSilently(reader); } - final Course course = EduStepicConnector.getCourse(project, mySelectedCourseInfo); - if (course != null) { - flushCourse(project, course); - } - return course; + return null; } public static void openFirstTask(@NotNull final Course course, @NotNull final Project project) { @@ -146,7 +165,7 @@ public class StudyProjectGenerator { } } - public void flushCourse(@NotNull final Project project, @NotNull final Course course) { + public static void flushCourse(@NotNull final Project project, @NotNull final Course course) { final File courseDirectory = StudyUtils.getCourseDirectory(project, course); FileUtil.createDirectory(courseDirectory); flushCourseJson(course, courseDirectory); @@ -242,7 +261,7 @@ public class StudyProjectGenerator { } } - private static void flushCourseJson(@NotNull final Course course, @NotNull final File courseDirectory) { + public static void flushCourseJson(@NotNull final Course course, @NotNull final File courseDirectory) { final Gson gson = new GsonBuilder().setPrettyPrinting().create(); final String json = gson.toJson(course); final File courseJson = new File(courseDirectory, EduNames.COURSE_META_FILE); diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java index da81b0081e27..91f91ae8050c 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java @@ -17,12 +17,10 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import com.jetbrains.edu.learning.StudyTaskManager; import com.jetbrains.edu.learning.StudyUtils; +import com.jetbrains.edu.learning.actions.StudyNextStudyTaskAction; import com.jetbrains.edu.learning.checker.StudyExecutor; import com.jetbrains.edu.learning.core.EduNames; -import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.Lesson; -import com.jetbrains.edu.learning.courseFormat.Task; -import com.jetbrains.edu.learning.courseFormat.TaskFile; +import com.jetbrains.edu.learning.courseFormat.*; import com.jetbrains.edu.learning.courseGeneration.StudyGenerator; import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator; import com.jetbrains.edu.learning.editor.StudyEditor; @@ -193,8 +191,8 @@ public class EduAdaptiveStepicConnector { if (task != null) { final Lesson adaptive = course.getLessons().get(0); + final Task unsolvedTask = adaptive.getTaskList().get(adaptive.getTaskList().size() - 1); if (reaction == 0 || reaction == -1) { - final Task unsolvedTask = adaptive.getTaskList().get(adaptive.getTaskList().size() - 1); unsolvedTask.setName(task.getName()); unsolvedTask.setStepicId(task.getStepicId()); unsolvedTask.setText(task.getText()); @@ -222,6 +220,7 @@ public class EduAdaptiveStepicConnector { final File lessonDirectory = new File(course.getCourseDirectory(), EduNames.LESSON + String.valueOf(adaptive.getIndex())); final File taskDirectory = new File(lessonDirectory, EduNames.TASK + String.valueOf(adaptive.getTaskList().size())); StudyProjectGenerator.flushTask(task, taskDirectory); + flushAdaptiveCourse(course, adaptive, unsolvedTask); final VirtualFile lessonDir = project.getBaseDir().findChild(EduNames.LESSON + String.valueOf(adaptive.getIndex())); if (lessonDir != null) { @@ -249,12 +248,13 @@ public class EduAdaptiveStepicConnector { } final File lessonDirectory = new File(course.getCourseDirectory(), EduNames.LESSON + String.valueOf(adaptive.getIndex())); - final File taskDirectory = new File(lessonDirectory, EduNames.TASK + String.valueOf(adaptive.getTaskList().size())); - StudyProjectGenerator.flushTask(task, taskDirectory); - adaptive.initLesson(course, false); + StudyProjectGenerator.flushLesson(lessonDirectory, adaptive); + flushAdaptiveCourse(course, adaptive, unsolvedTask); + adaptive.initLesson(course, true); + ApplicationManager.getApplication().invokeLater(()->new StudyNextStudyTaskAction().navigateTask(project)); } } - VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); + ApplicationManager.getApplication().invokeLater(() -> VirtualFileManager.getInstance().refreshWithoutFileWatcher(false)); } else { LOG.warn("Recommendation reactions weren't posted"); @@ -262,6 +262,17 @@ public class EduAdaptiveStepicConnector { } } + private static void flushAdaptiveCourse(Course course, Lesson adaptive, Task unsolvedTask) { + unsolvedTask.setLesson(null); + final TaskFile file = unsolvedTask.getTaskFile(DEFAULT_TASKFILE_NAME); + if (file != null) { + file.setTask(null); + } + adaptive.setCourse(null); + StudyProjectGenerator.flushCourseJson(course, new File(course.getCourseDirectory())); + course.initCourse(true); + } + private static void createTestFiles(Course course, Task task, Task unsolvedTask, VirtualFile lessonDir) { ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> { try { @@ -301,6 +312,7 @@ public class EduAdaptiveStepicConnector { task.setName(name); task.setStepicId(lessonID); task.setText(step.text); + task.setStatus(StudyStatus.Unchecked); if (step.options.samples != null) { final StringBuilder builder = new StringBuilder(); for (List sample : step.options.samples) { From 0e60d9d0e2c9dcbcc71697dc7978111e9237317a Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Mon, 16 May 2016 17:09:03 +0300 Subject: [PATCH 28/44] Fix problems with checking local courses after fixes with flush adaptive courses --- .../learning/courseGeneration/StudyProjectGenerator.java | 8 ++++---- .../resources/fileTemplates/internal/test_helper.py.ft | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java b/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java index 7f8a01b3cec7..fa5d05fdc558 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java @@ -90,14 +90,14 @@ public class StudyProjectGenerator { protected Course getCourse(@NotNull final Project project) { final File courseFile = new File(new File(OUR_COURSES_DIR, mySelectedCourseInfo.getName()), EduNames.COURSE_META_FILE); if (courseFile.exists()) { - return readCourseFromCache(courseFile); + return readCourseFromCache(courseFile, false); } else { final File adaptiveCourseFile = new File(new File(OUR_COURSES_DIR, ADAPTIVE_COURSE_PREFIX + mySelectedCourseInfo.getName() + "_" + myUser.getEmail()), EduNames.COURSE_META_FILE); if (adaptiveCourseFile.exists()) { - return readCourseFromCache(adaptiveCourseFile); + return readCourseFromCache(adaptiveCourseFile, true); } } @@ -110,13 +110,13 @@ public class StudyProjectGenerator { } @Nullable - private static Course readCourseFromCache(File courseFile) { + private static Course readCourseFromCache(@NotNull File courseFile, boolean isAdaptive) { Reader reader = null; try { reader = new InputStreamReader(new FileInputStream(courseFile), "UTF-8"); Gson gson = new GsonBuilder().create(); final Course course = gson.fromJson(reader, Course.class); - course.initCourse(true); + course.initCourse(isAdaptive); return course; } catch (UnsupportedEncodingException e) { diff --git a/python/educational-python/resources/fileTemplates/internal/test_helper.py.ft b/python/educational-python/resources/fileTemplates/internal/test_helper.py.ft index c31d2fa2ed7c..8d3dcab08947 100644 --- a/python/educational-python/resources/fileTemplates/internal/test_helper.py.ft +++ b/python/educational-python/resources/fileTemplates/internal/test_helper.py.ft @@ -218,7 +218,7 @@ def check_samples(samples=()): passed() -def run_common_tests(): +def run_common_tests(error_text="Please, reload file and try again"): test_is_initial_text() test_is_not_empty() test_answer_placeholders_text_deleted() From ff5fa40396bf4811e996a8add60c1a48163bbacb Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Tue, 17 May 2016 11:27:10 +0300 Subject: [PATCH 29/44] Minor Refresh project view on adding new recommendation, fix NPE in postAttemptToStepic --- .../com/jetbrains/edu/learning/checker/StudyCheckTask.java | 7 +++---- .../edu/learning/stepic/EduAdaptiveStepicConnector.java | 2 ++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java index 7fe00c813235..fc6d39649c6b 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java @@ -84,9 +84,7 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun private void checkForEduCourse(@NotNull ProgressIndicator indicator) { final StudyTestsOutputParser.TestsOutput testsOutput = getTestOutput(indicator); - - postAttemptToStepic(testsOutput); - + if (testsOutput != null) { if (testsOutput.isSuccess()) { onTaskSolved(testsOutput.getMessage()); @@ -95,6 +93,7 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun onTaskFailed(testsOutput.getMessage()); } runAfterTaskCheckedActions(); + postAttemptToStepic(testsOutput); } } @@ -181,7 +180,7 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun } } - protected void postAttemptToStepic(StudyTestsOutputParser.TestsOutput testsOutput) { + protected void postAttemptToStepic(@NotNull StudyTestsOutputParser.TestsOutput testsOutput) { final StudyTaskManager studySettings = StudyTaskManager.getInstance(myProject); final String login = studySettings.getLogin(); final String password = StringUtil.isEmptyOrSpaces(login) ? "" : studySettings.getPassword(); diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java index 91f91ae8050c..7a6caed8a3d7 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java @@ -3,6 +3,7 @@ package com.jetbrains.edu.learning.stepic; import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.intellij.ide.projectView.ProjectView; import com.intellij.lang.Language; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; @@ -112,6 +113,7 @@ public class EduAdaptiveStepicConnector { LOG.warn("Got unexpected number of lessons: " + lessonContainer.lessons.size()); } VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); + ProjectView.getInstance(project).refresh(); } } else { From 58a21fb8c30761f54d6d856e08aa86328cf9a75a Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Wed, 18 May 2016 13:50:36 +0300 Subject: [PATCH 30/44] Fix problems with new api after merge --- .../CCRefactoringElementListenerProvider.java | 6 ++++-- .../src/com/jetbrains/edu/coursecreator/CCUtils.java | 2 +- .../edu/coursecreator/actions/CCAddAnswerPlaceholder.java | 2 +- .../edu/coursecreator/actions/CCHideFromStudent.java | 2 +- .../student/src/com/jetbrains/edu/learning/StudyUtils.java | 6 +++--- .../learning/actions/StudyRefreshAnswerPlaceholder.java | 2 +- .../edu/learning/actions/StudyRefreshTaskFileAction.java | 7 ++++--- .../src/com/jetbrains/edu/learning/ui/StudyToolWindow.java | 5 +++-- 8 files changed, 18 insertions(+), 14 deletions(-) diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCRefactoringElementListenerProvider.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCRefactoringElementListenerProvider.java index 7dbd894d8980..7a26bd0b72cf 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCRefactoringElementListenerProvider.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCRefactoringElementListenerProvider.java @@ -17,6 +17,7 @@ package com.jetbrains.edu.coursecreator; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiElement; @@ -68,7 +69,8 @@ public class CCRefactoringElementListenerProvider implements RefactoringElementL private static void tryToRenameTaskFile(PsiFile file, String oldName) { final PsiDirectory taskDir = file.getContainingDirectory(); - Course course = StudyTaskManager.getInstance(file.getProject()).getCourse(); + final Project project = file.getProject(); + Course course = StudyTaskManager.getInstance(project).getCourse(); if (course == null) { return; } @@ -95,7 +97,7 @@ public class CCRefactoringElementListenerProvider implements RefactoringElementL ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { - VirtualFile patternFile = StudyUtils.getPatternFile(taskFile, oldName); + VirtualFile patternFile = StudyUtils.getPatternFile(project, taskFile, oldName); if (patternFile != null) { try { patternFile.delete(CCRefactoringElementListenerProvider.class); diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCUtils.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCUtils.java index f6dd7417a32d..aee3ddc9e1c4 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCUtils.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/CCUtils.java @@ -224,7 +224,7 @@ public class CCUtils { if (child == null) { continue; } - Document patternDocument = StudyUtils.getPatternDocument(entry.getValue(), name); + Document patternDocument = StudyUtils.getPatternDocument(project, entry.getValue(), name); Document document = FileDocumentManager.getInstance().getDocument(child); if (document == null || patternDocument == null) { return; diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCAddAnswerPlaceholder.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCAddAnswerPlaceholder.java index 5eae370909ee..254d64dd534c 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCAddAnswerPlaceholder.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCAddAnswerPlaceholder.java @@ -85,7 +85,7 @@ public class CCAddAnswerPlaceholder extends CCAnswerPlaceholderAction { } private static void computeInitialState(Project project, PsiFile file, TaskFile taskFile, Document document) { - Document patternDocument = StudyUtils.getPatternDocument(taskFile, file.getName()); + Document patternDocument = StudyUtils.getPatternDocument(project, taskFile, file.getName()); if (patternDocument == null) { return; } diff --git a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCHideFromStudent.java b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCHideFromStudent.java index 07cd46cfdf20..9ac18ba159a3 100644 --- a/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCHideFromStudent.java +++ b/python/educational-core/course-creator/src/com/jetbrains/edu/coursecreator/actions/CCHideFromStudent.java @@ -24,7 +24,7 @@ public class CCHideFromStudent extends CCTaskFileActionBase { return; } String name = file.getName(); - VirtualFile patternFile = StudyUtils.getPatternFile(taskFile, name); + VirtualFile patternFile = StudyUtils.getPatternFile(project, taskFile, name); ApplicationManager.getApplication().runWriteAction(() -> { if (patternFile != null) { try { diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java b/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java index 63f6d29180f4..6610bc37ba08 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java @@ -356,7 +356,7 @@ public class StudyUtils { @Nullable - public static VirtualFile getPatternFile(@NotNull TaskFile taskFile, String name) { + public static VirtualFile getPatternFile(@NotNull Project project, @NotNull TaskFile taskFile, String name) { Task task = taskFile.getTask(); String lessonDir = EduNames.LESSON + String.valueOf(task.getLesson().getIndex()); String taskDir = EduNames.TASK + String.valueOf(task.getIndex()); @@ -374,8 +374,8 @@ public class StudyUtils { } @Nullable - public static Document getPatternDocument(@NotNull final TaskFile taskFile, String name) { - VirtualFile patternFile = getPatternFile(taskFile, name); + public static Document getPatternDocument(@NotNull Project project, @NotNull final TaskFile taskFile, String name) { + VirtualFile patternFile = getPatternFile(project, taskFile, name); if (patternFile == null) { return null; } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshAnswerPlaceholder.java b/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshAnswerPlaceholder.java index 7fbd271f1db1..6d183b812bac 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshAnswerPlaceholder.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshAnswerPlaceholder.java @@ -45,7 +45,7 @@ public class StudyRefreshAnswerPlaceholder extends DumbAwareAction { } StudyEditor studyEditor = StudyUtils.getSelectedStudyEditor(project); final StudyState studyState = new StudyState(studyEditor); - Document patternDocument = StudyUtils.getPatternDocument(answerPlaceholder.getTaskFile(), studyState.getVirtualFile().getName()); + Document patternDocument = StudyUtils.getPatternDocument(project, answerPlaceholder.getTaskFile(), studyState.getVirtualFile().getName()); if (patternDocument == null) { return; } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshTaskFileAction.java b/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshTaskFileAction.java index 13eac8fc18ac..954aaf374227 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshTaskFileAction.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyRefreshTaskFileAction.java @@ -94,7 +94,7 @@ public class StudyRefreshTaskFileAction extends StudyActionWithShortcut { @NotNull final Project project, TaskFile taskFile, String name) { - if (!resetDocument(document, taskFile, name)) { + if (!resetDocument(project, document, taskFile, name)) { return false; } taskFile.getTask().setStatus(StudyStatus.Unchecked); @@ -123,10 +123,11 @@ public class StudyRefreshTaskFileAction extends StudyActionWithShortcut { } - private static boolean resetDocument(@NotNull final Document document, + private static boolean resetDocument(@NotNull final Project project, + @NotNull final Document document, @NotNull final TaskFile taskFile, String fileName) { - final Document patternDocument = StudyUtils.getPatternDocument(taskFile, fileName); + final Document patternDocument = StudyUtils.getPatternDocument(project, taskFile, fileName); if (patternDocument == null) { return false; } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java index 589d0179f6ad..fbc37c258096 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java @@ -38,11 +38,12 @@ import com.intellij.ui.JBCardLayout; import com.intellij.ui.JBColor; import com.intellij.ui.OnePixelSplitter; import com.intellij.util.ui.JBUI; +import com.intellij.util.ui.UIUtil; import com.jetbrains.edu.learning.*; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.stepic.EduAdaptiveStepicConnector; import com.jetbrains.edu.learning.courseFormat.TaskFile; +import com.jetbrains.edu.learning.stepic.EduAdaptiveStepicConnector; import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -78,7 +79,7 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D if (course != null && course.isAdaptive()) { panel.add(createReactionPanel(), BorderLayout.NORTH); } - JComponent taskInfoPanel = createTaskInfoPanel(taskText, project); + JComponent taskInfoPanel = createTaskInfoPanel(project); panel.add(taskInfoPanel, BorderLayout.CENTER); myContentPanel.add(TASK_INFO_ID, panel); mySplitPane.setFirstComponent(myContentPanel); From 0c1d2390d16f9a6a228e8f03d522d18edb17c6fe Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Wed, 18 May 2016 13:51:31 +0300 Subject: [PATCH 31/44] Minor Move project view update to a proper place --- .../edu/learning/stepic/EduAdaptiveStepicConnector.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java index 7a6caed8a3d7..7928a3aaf1bb 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java @@ -112,8 +112,6 @@ public class EduAdaptiveStepicConnector { else { LOG.warn("Got unexpected number of lessons: " + lessonContainer.lessons.size()); } - VirtualFileManager.getInstance().refreshWithoutFileWatcher(true); - ProjectView.getInstance(project).refresh(); } } else { @@ -230,7 +228,7 @@ public class EduAdaptiveStepicConnector { } final StudyToolWindow window = StudyUtils.getStudyToolWindow(project); if (window != null) { - window.setTaskText(unsolvedTask.getText()); + window.setTaskText(unsolvedTask.getText(), unsolvedTask.getTaskDir(project), project); } } else { @@ -256,7 +254,10 @@ public class EduAdaptiveStepicConnector { ApplicationManager.getApplication().invokeLater(()->new StudyNextStudyTaskAction().navigateTask(project)); } } - ApplicationManager.getApplication().invokeLater(() -> VirtualFileManager.getInstance().refreshWithoutFileWatcher(false)); + ApplicationManager.getApplication().invokeLater(() -> { + VirtualFileManager.getInstance().refreshWithoutFileWatcher(false); + ProjectView.getInstance(project).refresh(); + }); } else { LOG.warn("Recommendation reactions weren't posted"); From 2195eea07465201c3dc230a179306084c58708c8 Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Fri, 3 Jun 2016 16:11:00 +0300 Subject: [PATCH 32/44] Display progress for login and getting course list correctly --- .../edu/learning/ui/StudyNewProjectPanel.java | 89 ++++++++++--------- 1 file changed, 47 insertions(+), 42 deletions(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java index 89ef2ae07938..6159d56c00f3 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java @@ -8,6 +8,7 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileChooser.FileChooser; import com.intellij.openapi.fileChooser.FileChooserDescriptor; import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.DefaultProjectFactory; import com.intellij.openapi.project.DefaultProjectFactoryImpl; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; @@ -19,8 +20,9 @@ import com.intellij.openapi.ui.popup.util.BaseListPopupStep; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Consumer; -import com.jetbrains.edu.learning.courseFormat.Course; +import com.intellij.util.TimeoutUtil; import com.jetbrains.edu.learning.StudyUtils; +import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator; import com.jetbrains.edu.learning.stepic.CourseInfo; import com.jetbrains.edu.learning.stepic.EduStepicConnector; @@ -38,7 +40,6 @@ import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; /** * author: liana @@ -88,7 +89,6 @@ public class StudyNewProjectPanel { myLabel.setPreferredSize(new JLabel("Project name").getPreferredSize()); } - private void setupBrowseButton() { myBrowseButton.putClientProperty("JButton.buttonType", "null"); myBrowseButton.setIcon(InteractiveLearningIcons.InterpreterGear); @@ -190,12 +190,18 @@ public class StudyNewProjectPanel { private class RefreshActionListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { - refreshCoursesList(); + ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> { + ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true); + final List courses; + courses = execCancelable(() -> myGenerator.getCourses(true)); + if (courses != null) { + ApplicationManager.getApplication().invokeLater(() -> refreshCoursesList(courses)); + } + }, "Refreshing Course List", true, DefaultProjectFactory.getInstance().getDefaultProject()); } } - private void refreshCoursesList() { - final List courses = myGenerator.getCourses(true); + private void refreshCoursesList(@NotNull final List courses) { if (courses.isEmpty()) { setError(CONNECTION_ERROR); return; @@ -203,7 +209,9 @@ public class StudyNewProjectPanel { myCoursesComboBox.removeAllItems(); for (CourseInfo courseInfo : courses) { - myCoursesComboBox.addItem(courseInfo); + if (!courseInfo.isAdaptive()) { + myCoursesComboBox.addItem(courseInfo); + } } myGenerator.setSelectedCourse(StudyUtils.getFirst(courses)); @@ -269,6 +277,7 @@ public class StudyNewProjectPanel { @Override protected void doOKAction() { + super.doOKAction(); if (StringUtil.isEmptyOrSpaces(myRemoteCourse.getLogin())) { myRemoteCourse.setError("Please, enter your login"); return; @@ -278,46 +287,42 @@ public class StudyNewProjectPanel { return; } - super.doOKAction(); - final ProgressManager progressManager = ProgressManager.getInstance(); - progressManager.runProcessWithProgressSynchronously(() -> { - final Future future = ApplicationManager.getApplication().executeOnPooledThread( - new Callable() { - @Override - public StepicUser call() throws Exception { - return EduStepicConnector.login(myRemoteCourse.getLogin(), myRemoteCourse.getPassword()); - } - }); + ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> { + ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true); - while (!future.isCancelled() && !future.isDone()) { - progressManager.getProgressIndicator().checkCanceled(); - try { - TimeUnit.MILLISECONDS.sleep(500); - } - catch (final InterruptedException e) { - LOG.warn(e.getMessage()); - } - } + final StepicUser stepicUser = execCancelable(() -> EduStepicConnector.login(myRemoteCourse.getLogin(), + myRemoteCourse.getPassword())); + if (stepicUser != null) { + stepicUser.setEmail(myRemoteCourse.getLogin()); + stepicUser.setPassword(myRemoteCourse.getPassword()); + myGenerator.myUser = stepicUser; - try { - final StepicUser stepicUser = future.get(); - if (stepicUser != null) { - stepicUser.setEmail(myRemoteCourse.getLogin()); - stepicUser.setPassword(myRemoteCourse.getPassword()); - myGenerator.myUser = stepicUser; - ApplicationManager.getApplication().invokeLater(StudyNewProjectPanel.this::refreshCoursesList); + + final List courses = execCancelable(() -> myGenerator.getCourses(true)); + if (courses != null) { + ApplicationManager.getApplication().invokeLater(() -> refreshCoursesList(courses)); } - else { - setError("Failed to log in"); - } - } - catch (InterruptedException e) { - LOG.warn(e.getMessage()); - } - catch (ExecutionException e) { - LOG.warn(e.getMessage()); } }, "Signing In And Getting Stepic Course List", true, new DefaultProjectFactoryImpl().getDefaultProject()); } } + + @Nullable + private static T execCancelable(@NotNull final Callable callable) { + final Future future = + ApplicationManager.getApplication().executeOnPooledThread(callable); + + while (!future.isCancelled() && !future.isDone()) { + ProgressManager.checkCanceled(); + TimeoutUtil.sleep(500); + } + T result = null; + try { + result = future.get(); + } + catch (InterruptedException | ExecutionException e) { + LOG.warn(e.getMessage()); + } + return result; + } } From e813fd7f961cde67a90f834c8eea465b34da71d5 Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Mon, 6 Jun 2016 18:54:05 +0300 Subject: [PATCH 33/44] Fix problems with posting results after project reload Fix course serialization: serialize fields with @Expose annotation only --- .../edu/learning/courseFormat/Course.java | 31 ++++------- .../edu/learning/courseFormat/Lesson.java | 26 +++------ .../edu/learning/courseFormat/Task.java | 23 +++----- .../edu/learning/courseFormat/TaskFile.java | 19 +++---- .../StudyProjectGenerator.java | 2 +- .../edu/learning/stepic/CourseInfo.java | 54 +++++++++++-------- .../stepic/EduAdaptiveStepicConnector.java | 27 +++------- .../edu/learning/ui/StudyNewProjectPanel.java | 6 +++ 8 files changed, 80 insertions(+), 108 deletions(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Course.java b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Course.java index 6132952a8274..53d7a78b3034 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Course.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Course.java @@ -14,29 +14,20 @@ import java.util.ArrayList; import java.util.List; public class Course { - @Expose - private List lessons = new ArrayList(); - + @Expose private List lessons = new ArrayList(); + @Expose private List authors = new ArrayList(); @Expose private String description; @Expose private String name; - private String myCourseDirectory = ""; - @Expose private List authors = new ArrayList(); - private boolean myUpToDate; - - @Expose @SerializedName("language") - private String myLanguage = "Python"; - - //this field is used to distinguish ordinary and CheckIO projects - //"PyCharm" is used here for historical reasons - private String courseType = EduNames.PYCHARM; - - //this field is used to distinguish study and course creator modes - private String courseMode = EduNames.STUDY; - + @Expose private String myCourseDirectory = ""; + @Expose private int id; + @Expose private boolean myUpToDate; @Expose private boolean isAdaptive; - - @Expose - private int id; + @Expose @SerializedName("language") private String myLanguage = "Python"; + + //this field is used to distinguish ordinary and CheckIO projects, + //"PyCharm" is used here for historical reasons + @Expose private String courseType = EduNames.PYCHARM; + @Expose private String courseMode = EduNames.STUDY; //this field is used to distinguish study and course creator modes /** * Initializes state of course diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Lesson.java b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Lesson.java index 5b2f6acf9f22..4110dd621c28 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Lesson.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Lesson.java @@ -11,25 +11,13 @@ import java.util.ArrayList; import java.util.List; public class Lesson implements StudyItem { - public int id; - @Transient - public List steps; - @Transient - public List tags; - @Transient - Boolean is_public; - @Expose - @SerializedName("title") - private String name; - @Expose - @SerializedName("task_list") - public List taskList = new ArrayList(); - - @Transient - private Course myCourse = null; - - // index is visible to user number of lesson from 1 to lesson number - private int myIndex = -1; + @Expose public int id; + @Expose private int myIndex = -1; // index is visible to user number of lesson from 1 to lesson number + @Expose @Transient public List steps; + @Expose @SerializedName("title") private String name; + @Expose @SerializedName("task_list") public List taskList = new ArrayList(); + @Transient public List tags; + @Transient private Course myCourse = null; public void initLesson(final Course course, boolean isRestarted) { setCourse(course); diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Task.java b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Task.java index 3491f386dc65..bc76b2d4100d 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Task.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/Task.java @@ -19,22 +19,13 @@ import java.util.Map; * Implementation of task which contains task files, tests, input file for tests */ public class Task implements StudyItem { - @Expose - private String name; - - // index is visible to user number of task from 1 to task number - private int myIndex; - private StudyStatus myStatus = StudyStatus.Uninitialized; - - private int myStepicId; - - @Expose - @SerializedName("task_files") - public Map taskFiles = new HashMap(); - - private String text; - private Map testsText = new HashMap(); - + @Expose private String name; + @Expose private String text; + @Expose private int myStepicId; + @Expose private int myIndex; // index is visible to user number of task from 1 to task number + @Expose @SerializedName("task_files") public Map taskFiles = new HashMap(); + @Expose private Map testsText = new HashMap(); + @Expose private StudyStatus myStatus = StudyStatus.Uninitialized; @Transient private Lesson myLesson; public Task() {} diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/TaskFile.java b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/TaskFile.java index 33e00d574325..65ef81d5e6b5 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/TaskFile.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/courseFormat/TaskFile.java @@ -18,19 +18,14 @@ import java.util.List; */ public class TaskFile { - @SerializedName("placeholders") - @Expose - private List myAnswerPlaceholders = new ArrayList(); - private int myIndex = -1; - - @Expose - public String name; - @Expose - public String text; + @Expose public String name; + @Expose public String text; + @Expose private int myIndex = -1; + @Expose private boolean myUserCreated = false; + @Expose private boolean myTrackChanges = true; + @Expose private boolean myHighlightErrors = false; + @Expose @SerializedName("placeholders") private List myAnswerPlaceholders = new ArrayList(); @Transient private Task myTask; - private boolean myUserCreated = false; - private boolean myTrackChanges = true; - private boolean myHighlightErrors = false; public void initTaskFile(final Task task, boolean isRestarted) { setTask(task); diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java b/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java index fa5d05fdc558..5f10c102062c 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java @@ -262,7 +262,7 @@ public class StudyProjectGenerator { } public static void flushCourseJson(@NotNull final Course course, @NotNull final File courseDirectory) { - final Gson gson = new GsonBuilder().setPrettyPrinting().create(); + final Gson gson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create(); final String json = gson.toJson(course); final File courseJson = new File(courseDirectory, EduNames.COURSE_META_FILE); final FileOutputStream fileOutputStream; diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/CourseInfo.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/CourseInfo.java index eff3c6f3684e..c8c0c90aaf04 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/CourseInfo.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/CourseInfo.java @@ -3,6 +3,7 @@ package com.jetbrains.edu.learning.stepic; import com.google.gson.annotations.SerializedName; import com.intellij.openapi.util.text.StringUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; @@ -12,26 +13,20 @@ import java.util.List; * and when project is being created */ public class CourseInfo { - @SerializedName("title") - private String myName; - - @SerializedName("summary") - private String myDescription; - - @SerializedName("course_format") - //course type in format "pycharm " - private String myType = "pycharm Python"; - - private boolean isAdaptive; - - int id; - boolean is_public; - public List sections; - List instructors = new ArrayList(); - List myAuthors = new ArrayList<>(); - public static CourseInfo INVALID_COURSE = new CourseInfo(); + @SerializedName("title") private String myName; + int id; + boolean isAdaptive; + boolean isPublic; + List sections; + List instructors = new ArrayList(); + + List myAuthors = new ArrayList<>(); + @SerializedName("summary") private String myDescription; + @SerializedName("course_format") private String myType = "pycharm Python"; //course type in format "pycharm " + @Nullable private String username; + public String getName() { return myName; } @@ -51,7 +46,7 @@ public class CourseInfo { @Override public String toString() { - return myName; + return getName(); } @Override @@ -60,17 +55,26 @@ public class CourseInfo { if (o == null || getClass() != o.getClass()) return false; CourseInfo that = (CourseInfo)o; if (that.getName() == null || that.getDescription() == null) return false; - return that.getName().equals(myName) + return that.getName().equals(getName()) && that.getDescription().equals(myDescription); } @Override public int hashCode() { - int result = myName != null ? myName.hashCode() : 0; + int result = getName() != null ? getName().hashCode() : 0; result = 31 * result + (myDescription != null ? myDescription.hashCode() : 0); return result; } + @Nullable + public String getUsername() { + return username; + } + + public void setUsername(@Nullable String username) { + this.username = username; + } + public static class Author { int id; String first_name = ""; @@ -127,4 +131,12 @@ public class CourseInfo { public void setAdaptive(boolean adaptive) { isAdaptive = adaptive; } + + public boolean isPublic() { + return isPublic; + } + + public void setPublic(boolean aPublic) { + isPublic = aPublic; + } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java index 7928a3aaf1bb..a1a4a62c813b 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java @@ -134,7 +134,7 @@ public class EduAdaptiveStepicConnector { LOG.warn("Got unexpected numbers of units: " + unitContainer.units.size()); return; } - + final URIBuilder builder = new URIBuilder(ASSIGNMENT_URL); for (Integer step : unitContainer.units.get(0).assignments) { builder.addParameter("ids[]", String.valueOf(step)); @@ -182,7 +182,7 @@ public class EduAdaptiveStepicConnector { final Course course = StudyTaskManager.getInstance(project).getCourse(); if (course != null && editor != null && editor.getTaskFile() != null) { final StepicUser user = StudyTaskManager.getInstance(project).getUser(); - + final boolean recommendationReaction = user != null && postRecommendationReaction(project, String.valueOf(editor.getTaskFile().getTask().getLesson().id), String.valueOf(user.id), reaction); @@ -198,7 +198,7 @@ public class EduAdaptiveStepicConnector { unsolvedTask.setText(task.getText()); unsolvedTask.getTestsText().clear(); final Map testsText = task.getTestsText(); - for (String testName: testsText.keySet()) { + for (String testName : testsText.keySet()) { unsolvedTask.addTestsTexts(testName, testsText.get(testName)); } final Map taskFiles = task.getTaskFiles(); @@ -220,7 +220,7 @@ public class EduAdaptiveStepicConnector { final File lessonDirectory = new File(course.getCourseDirectory(), EduNames.LESSON + String.valueOf(adaptive.getIndex())); final File taskDirectory = new File(lessonDirectory, EduNames.TASK + String.valueOf(adaptive.getTaskList().size())); StudyProjectGenerator.flushTask(task, taskDirectory); - flushAdaptiveCourse(course, adaptive, unsolvedTask); + StudyProjectGenerator.flushCourseJson(course, new File(course.getCourseDirectory())); final VirtualFile lessonDir = project.getBaseDir().findChild(EduNames.LESSON + String.valueOf(adaptive.getIndex())); if (lessonDir != null) { @@ -249,9 +249,9 @@ public class EduAdaptiveStepicConnector { final File lessonDirectory = new File(course.getCourseDirectory(), EduNames.LESSON + String.valueOf(adaptive.getIndex())); StudyProjectGenerator.flushLesson(lessonDirectory, adaptive); - flushAdaptiveCourse(course, adaptive, unsolvedTask); + StudyProjectGenerator.flushCourseJson(course, new File(course.getCourseDirectory())); adaptive.initLesson(course, true); - ApplicationManager.getApplication().invokeLater(()->new StudyNextStudyTaskAction().navigateTask(project)); + ApplicationManager.getApplication().invokeLater(() -> new StudyNextStudyTaskAction().navigateTask(project)); } } ApplicationManager.getApplication().invokeLater(() -> { @@ -265,17 +265,6 @@ public class EduAdaptiveStepicConnector { } } - private static void flushAdaptiveCourse(Course course, Lesson adaptive, Task unsolvedTask) { - unsolvedTask.setLesson(null); - final TaskFile file = unsolvedTask.getTaskFile(DEFAULT_TASKFILE_NAME); - if (file != null) { - file.setTask(null); - } - adaptive.setCourse(null); - StudyProjectGenerator.flushCourseJson(course, new File(course.getCourseDirectory())); - course.initCourse(true); - } - private static void createTestFiles(Course course, Task task, Task unsolvedTask, VirtualFile lessonDir) { ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> { try { @@ -406,7 +395,7 @@ public class EduAdaptiveStepicConnector { return Pair.create(isSolved, wrapper.submissions[0].hint); } else { - LOG.warn("Got a submission wrapper with incorrect submissions numer: " + wrapper.submissions.length); + LOG.warn("Got a submission wrapper with incorrect submissions number: " + wrapper.submissions.length); } } else { @@ -516,7 +505,7 @@ public class EduAdaptiveStepicConnector { private static void createTestFileFromSamples(@NotNull final Task task, @NotNull final List> samples) { - + String testText = "from test_helper import check_samples\n\n" + "if __name__ == '__main__':\n" + " check_samples(samples=" + new GsonBuilder().create().toJson(samples) + ")"; diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java index 6159d56c00f3..dfc38c7506e9 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java @@ -212,6 +212,12 @@ public class StudyNewProjectPanel { if (!courseInfo.isAdaptive()) { myCoursesComboBox.addItem(courseInfo); } + else { + final boolean isLoggedIn = myGenerator.myUser != null; + if (courseInfo.isPublic() || isLoggedIn) { + myCoursesComboBox.addItem(courseInfo); + } + } } myGenerator.setSelectedCourse(StudyUtils.getFirst(courses)); From 1ea6652b05c68b5a4962853716c3aa1e6715f21e Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Tue, 7 Jun 2016 18:39:46 +0300 Subject: [PATCH 34/44] Disable reaction buttons for solved task Make buttons more pretty --- .../edu/learning/ui/StudyToolWindow.java | 136 +++++++++++++----- 1 file changed, 100 insertions(+), 36 deletions(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java index fbc37c258096..09cffcee930a 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java @@ -18,6 +18,7 @@ package com.jetbrains.edu.learning.ui; import com.intellij.ide.browsers.WebBrowserManager; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.EditorFactory; @@ -35,13 +36,13 @@ import com.intellij.openapi.ui.SimpleToolWindowPanel; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.JBCardLayout; -import com.intellij.ui.JBColor; import com.intellij.ui.OnePixelSplitter; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import com.jetbrains.edu.learning.*; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.courseFormat.Course; +import com.jetbrains.edu.learning.courseFormat.StudyStatus; import com.jetbrains.edu.learning.courseFormat.TaskFile; import com.jetbrains.edu.learning.stepic.EduAdaptiveStepicConnector; import org.jetbrains.annotations.NotNull; @@ -56,6 +57,12 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D private static final Logger LOG = Logger.getInstance(StudyToolWindow.class); private static final String TASK_INFO_ID = "taskInfo"; private static final String EMPTY_TASK_TEXT = "Please, open any task to see task description"; + private static final String HARD_REACTION = "Too Hard"; + private static final String BORING_REACTION = "Too Boring"; + private static final String SOLVED_TASK_TOOLTIP = "Task Is Solved"; + private static final String HARD_LABEL_TOOLTIP = "Click To Get An Easier Task"; + private static final String BORING_LABEL_TOOLTIP = "Click To Get A More Challenging Task"; + private final JBCardLayout myCardLayout; private final JPanel myContentPanel; private final OnePixelSplitter mySplitPane; @@ -117,63 +124,120 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D } public JPanel createReactionPanel() { - final JPanel panel = new JPanel(new GridBagLayout()); - panel.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, JBColor.border())); - final JPanel hardPanel = new JPanel(new BorderLayout()); - hardPanel.add(new JLabel("Too hard"), BorderLayout.CENTER); - hardPanel.setBorder(BorderFactory.createEtchedBorder()); - - final JPanel boringPanel = new JPanel(new BorderLayout()); - boringPanel.setBorder(BorderFactory.createEtchedBorder()); - boringPanel.add(new JLabel("Too boring"), BorderLayout.CENTER); + final JPanel mainPanel = new JPanel(new GridBagLayout()); + mainPanel.setBackground(UIUtil.getTextFieldBackground()); + final JPanel hardPanel = createReactionButtonPanel(HARD_REACTION, HARD_LABEL_TOOLTIP, SOLVED_TASK_TOOLTIP, 0); + final JPanel boringPanel = createReactionButtonPanel(BORING_REACTION, BORING_LABEL_TOOLTIP, SOLVED_TASK_TOOLTIP, -1); + final GridBagConstraints c = new GridBagConstraints(); c.fill = GridBagConstraints.HORIZONTAL; c.gridx = 0; c.gridy = 0; - c.weightx = 1; - panel.add(hardPanel, c); - c.gridx = 1; - panel.add(boringPanel, c); - c.gridy = 1; - c.weightx = 1; - panel.add(Box.createVerticalBox(), c); - - final Project project = ProjectUtil.guessCurrentProject(myContentPanel); - addMouseListener(hardPanel, () -> EduAdaptiveStepicConnector.addNextRecommendedTask(project, 0)); - addMouseListener(boringPanel, () -> EduAdaptiveStepicConnector.addNextRecommendedTask(project, -1)); + mainPanel.add(Box.createVerticalStrut(3), c); - return panel; + c.gridx = 1; + c.gridy = 1; + mainPanel.add(Box.createHorizontalStrut(3), c); + c.weightx = 1; + c.gridx = 2; + mainPanel.add(hardPanel, c); + c.gridx = 3; + c.weightx = 0; + mainPanel.add(Box.createHorizontalStrut(3), c); + c.weightx = 1; + c.gridx = 4; + mainPanel.add(boringPanel, c); + c.gridx = 5; + c.weightx = 0; + mainPanel.add(Box.createHorizontalStrut(3), c); + + return mainPanel; } - private static void addMouseListener(@NotNull final JPanel panel, @NotNull Runnable onClickAction ) { - panel.addMouseListener(new MouseAdapter() { + private JPanel createReactionButtonPanel(@NotNull final String text, + @NotNull final String enabledTooltip, + @NotNull final String disabledTooltip, + int reaction) { + final Project project = ProjectUtil.guessCurrentProject(myContentPanel); + final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getCurrentTask(project); + final boolean isEnabled = task != null && task.getStatus() != StudyStatus.Solved; + + final JLabel label = new JLabel(text); + label.setEnabled(isEnabled); + + final JPanel buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.PAGE_AXIS)); + buttonPanel.setToolTipText(isEnabled ? enabledTooltip : disabledTooltip); + buttonPanel.add(Box.createVerticalStrut(5)); + buttonPanel.add(label); + buttonPanel.add(Box.createVerticalStrut(5)); + + final JPanel mainPanel = new JPanel(new GridBagLayout()); + mainPanel.setBorder(BorderFactory.createEtchedBorder()); + mainPanel.add(buttonPanel); + addMouseListener(mainPanel, buttonPanel, label, () -> EduAdaptiveStepicConnector.addNextRecommendedTask(project, reaction)); + return mainPanel; + } + + private static void addMouseListener(@NotNull final JPanel panel, + @NotNull final JPanel buttonPanel, + @NotNull final JLabel textLabel, + @NotNull Runnable onClickAction) { + final MouseAdapter mouseAdapter = new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 1) { - final ProgressIndicatorBase progress = new ProgressIndicatorBase(); - progress.setText("Loading Next Recommendation"); - ProgressManager.getInstance().run(new Task.Backgroundable(ProjectUtil.guessCurrentProject(panel), - "Loading Next Recommendation") { + final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getCurrentTask(ProjectUtil.guessCurrentProject(panel)); + if (task != null && task.getStatus() != StudyStatus.Solved) { + final ProgressIndicatorBase progress = new ProgressIndicatorBase(); + progress.setText("Loading Next Recommendation"); + ProgressManager.getInstance().run(new Task.Backgroundable(ProjectUtil.guessCurrentProject(panel), + "Loading Next Recommendation") { - @Override - public void run(@NotNull ProgressIndicator indicator) { - onClickAction.run(); - } - }); + @Override + public void run(@NotNull ProgressIndicator indicator) { + setEnabled(false); + onClickAction.run(); + } + + @Override + protected void onFinished() { + setEnabled(true); + } + + private void setEnabled(final boolean enabled) { + ApplicationManager.getApplication().invokeLater(() -> { + panel.setEnabled(enabled); + buttonPanel.setEnabled(enabled); + textLabel.setEnabled(enabled); + }); + } + }); + } } } @Override public void mouseEntered(MouseEvent e) { - panel.setBackground(UIUtil.getTreeSelectionBackground()); + final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getCurrentTask(ProjectUtil.guessCurrentProject(panel)); + if (task != null && task.getStatus() != StudyStatus.Solved && panel.isEnabled()) { + setBackground(UIUtil.getButtonSelectColor()); + } } @Override public void mouseExited(MouseEvent e) { - panel.setBackground(UIUtil.getLabelBackground()); + setBackground(UIUtil.getLabelBackground()); } - }); + + private void setBackground(Color color) { + panel.setBackground(color); + buttonPanel.setBackground(color); + } + }; + panel.addMouseListener(mouseAdapter); + buttonPanel.addMouseListener(mouseAdapter); } public void dispose() { From ed045df848dd5ac2d8e8771eabc749bde37bc63c Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Wed, 8 Jun 2016 12:15:46 +0300 Subject: [PATCH 35/44] Fix edu new project panel freezing: get available courses under progress --- .../jetbrains/edu/learning/StudyUtils.java | 24 +++++ .../StudyProjectGenerator.java | 65 +++++++---- .../learning/stepic/EduStepicConnector.java | 2 +- .../edu/learning/ui/StudyNewProjectPanel.java | 102 +++++++++--------- .../PyStudyDirectoryProjectGenerator.java | 3 +- 5 files changed, 119 insertions(+), 77 deletions(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java b/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java index 6610bc37ba08..e36355be5850 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java @@ -20,6 +20,7 @@ import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.FileEditor; import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx; +import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.projectRoots.Sdk; @@ -36,6 +37,7 @@ import com.intellij.psi.PsiFile; import com.intellij.ui.JBColor; import com.intellij.ui.awt.RelativePoint; import com.intellij.ui.content.Content; +import com.intellij.util.TimeoutUtil; import com.intellij.util.ui.UIUtil; import com.jetbrains.edu.learning.checker.StudyExecutor; import com.jetbrains.edu.learning.checker.StudyTestRunner; @@ -57,6 +59,9 @@ import java.awt.*; import java.io.*; import java.util.Collection; import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; public class StudyUtils { private StudyUtils() { @@ -609,4 +614,23 @@ public class StudyUtils { } return null; } + + // supposd to be called under progress + @Nullable + public static T execCancelable(@NotNull final Callable callable) { + final Future future = ApplicationManager.getApplication().executeOnPooledThread(callable); + + while (!future.isCancelled() && !future.isDone()) { + ProgressManager.checkCanceled(); + TimeoutUtil.sleep(500); + } + T result = null; + try { + result = future.get(); + } + catch (InterruptedException | ExecutionException e) { + LOG.warn(e.getMessage()); + } + return result; + } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java b/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java index 5f10c102062c..f5dcb3c98e62 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/courseGeneration/StudyProjectGenerator.java @@ -8,10 +8,12 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.DumbModePermission; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.ThrowableComputable; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; @@ -39,10 +41,9 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.*; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; + +import static com.jetbrains.edu.learning.StudyUtils.execCancelable; public class StudyProjectGenerator { public static final String AUTHOR_ATTRIBUTE = "authors"; @@ -88,19 +89,18 @@ public class StudyProjectGenerator { @Nullable protected Course getCourse(@NotNull final Project project) { - final File courseFile = new File(new File(OUR_COURSES_DIR, mySelectedCourseInfo.getName()), EduNames.COURSE_META_FILE); - if (courseFile.exists()) { - return readCourseFromCache(courseFile, false); - } - else { - final File adaptiveCourseFile = new File(new File(OUR_COURSES_DIR, ADAPTIVE_COURSE_PREFIX + - mySelectedCourseInfo.getName() + "_" + - myUser.getEmail()), EduNames.COURSE_META_FILE); - if (adaptiveCourseFile.exists()) { - return readCourseFromCache(adaptiveCourseFile, true); - } - + final File courseFile = new File(new File(OUR_COURSES_DIR, mySelectedCourseInfo.getName()), EduNames.COURSE_META_FILE); + if (courseFile.exists()) { + return readCourseFromCache(courseFile, false); + } + else if (myUser != null) { + final File adaptiveCourseFile = new File(new File(OUR_COURSES_DIR, ADAPTIVE_COURSE_PREFIX + + mySelectedCourseInfo.getName() + "_" + + myUser.getEmail()), EduNames.COURSE_META_FILE); + if (adaptiveCourseFile.exists()) { + return readCourseFromCache(adaptiveCourseFile, true); } + } final Course course = EduStepicConnector.getCourse(project, mySelectedCourseInfo); if (course != null) { flushCourse(project, course); @@ -154,7 +154,8 @@ public class StudyProjectGenerator { final PsiFile file = PsiManager.getInstance(project).findFile(activeVirtualFile); ProjectView.getInstance(project).select(file, activeVirtualFile, true); FileEditorManager.getInstance(project).openFile(activeVirtualFile, true); - } else { + } + else { String first = StudyUtils.getFirst(taskFiles.keySet()); if (first != null) { NewVirtualFile firstFile = ((VirtualDirectoryImpl)taskDir).refreshAndFindChild(first); @@ -231,7 +232,6 @@ public class StudyProjectGenerator { else { FileUtil.writeToFile(file, taskFile.text); } - } catch (IOException e) { LOG.error("ERROR copying file " + name); @@ -245,7 +245,7 @@ public class StudyProjectGenerator { } FileUtil.createIfDoesntExist(testsFile); try { - FileUtil.writeToFile(testsFile, entry.getValue()); + FileUtil.writeToFile(testsFile, entry.getValue()); } catch (IOException e) { LOG.error("ERROR copying tests file"); @@ -333,12 +333,13 @@ public class StudyProjectGenerator { return true; } - public List getCourses(boolean force) { + // Supposed to be called under progress + public List getCoursesAsynchronouslyIfNeeded(boolean force) { if (OUR_COURSES_DIR.exists()) { myCourses = getCoursesFromCache(); } if (force || myCourses.isEmpty()) { - myCourses = EduStepicConnector.getCourses(); + myCourses = execCancelable(EduStepicConnector::getCourses); flushCache(myCourses); } if (myCourses.isEmpty()) { @@ -347,6 +348,22 @@ public class StudyProjectGenerator { return myCourses; } + public List getCoursesUnderProgress(boolean force, @NotNull final String progressTitle, @NotNull final Project project) { + try { + return ProgressManager.getInstance() + .runProcessWithProgressSynchronously(new ThrowableComputable, RuntimeException>() { + @Override + public List compute() throws RuntimeException { + ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true); + return getCoursesAsynchronouslyIfNeeded(force); + } + }, progressTitle, true, project); + } + catch (RuntimeException e) { + return Collections.singletonList(CourseInfo.INVALID_COURSE); + } + } + public void addSettingsStateListener(@NotNull SettingsListener listener) { myListeners.add(listener); } @@ -397,7 +414,8 @@ public class StudyProjectGenerator { finally { StudyUtils.closeSilently(reader); } - } finally { + } + finally { StudyUtils.closeSilently(inputStream); } } @@ -406,6 +424,7 @@ public class StudyProjectGenerator { } return courses; } + /** * Adds course from zip archive to courses * @@ -439,7 +458,6 @@ public class StudyProjectGenerator { /** * Adds course to courses specified in params * - * * @param courses * @param courseDir must be directory containing course file * @return added course name or null if course is invalid @@ -463,6 +481,7 @@ public class StudyProjectGenerator { } return null; } + /** * Parses course json meta file and finds course name * diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java index c7169e83269a..bfb69dc6bb94 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java @@ -231,7 +231,7 @@ public class EduStepicConnector { catch (IOException e) { LOG.error("Cannot load course list " + e.getMessage()); } - return Collections.emptyList(); + return Collections.singletonList(CourseInfo.INVALID_COURSE); } private static boolean addCoursesFromStepic(List result, int pageNumber) throws IOException { diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java index dfc38c7506e9..a23c136d4823 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyNewProjectPanel.java @@ -19,8 +19,8 @@ import com.intellij.openapi.ui.popup.PopupStep; import com.intellij.openapi.ui.popup.util.BaseListPopupStep; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.AncestorListenerAdapter; import com.intellij.util.Consumer; -import com.intellij.util.TimeoutUtil; import com.jetbrains.edu.learning.StudyUtils; import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator; @@ -32,14 +32,12 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; +import javax.swing.event.AncestorEvent; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; /** * author: liana @@ -60,19 +58,37 @@ public class StudyNewProjectPanel { private static final String CONNECTION_ERROR = "Failed to download courses.
Check your Internet connection."; private static final String INVALID_COURSE = "Selected course is invalid"; private FacetValidatorsManager myValidationManager; + private boolean isComboboxInitialized; - public StudyNewProjectPanel(StudyProjectGenerator generator) { + public StudyNewProjectPanel(@NotNull final StudyProjectGenerator generator) { myGenerator = generator; - myAvailableCourses = myGenerator.getCourses(false); myBrowseButton.setPreferredSize(new Dimension(28, 28)); myRefreshButton.setPreferredSize(new Dimension(28, 28)); - if (myAvailableCourses.isEmpty()) { + initListeners(); + myRefreshButton.setVisible(true); + myRefreshButton.putClientProperty("JButton.buttonType", "null"); + myRefreshButton.setIcon(AllIcons.Actions.Refresh); + + myLabel.setPreferredSize(new JLabel("Project name").getPreferredSize()); + myContentPanel.addAncestorListener(new AncestorListenerAdapter() { + @Override + public void ancestorMoved(AncestorEvent event) { + if (!isComboboxInitialized && myContentPanel.isVisible()) { + isComboboxInitialized = true; + initCoursesCombobox(); + } + } + }); + } + + private void initCoursesCombobox() { + myAvailableCourses = + myGenerator.getCoursesUnderProgress(false, "Getting Available Courses", ProjectManager.getInstance().getDefaultProject()); + if (myAvailableCourses == null || myAvailableCourses.isEmpty()) { setError(CONNECTION_ERROR); } else { - for (CourseInfo courseInfo : myAvailableCourses) { - myCoursesComboBox.addItem(courseInfo); - } + addCoursesToCombobox(myAvailableCourses); final CourseInfo selectedCourse = StudyUtils.getFirst(myAvailableCourses); final String authorsString = Course.getAuthorsString(selectedCourse.getAuthors()); myAuthorLabel.setText(!StringUtil.isEmptyOrSpaces(authorsString) ? "Author: " + authorsString : ""); @@ -82,13 +98,8 @@ public class StudyNewProjectPanel { myGenerator.setSelectedCourse(selectedCourse); setOK(); } - initListeners(); - myRefreshButton.setVisible(true); - myRefreshButton.putClientProperty("JButton.buttonType", "null"); - myRefreshButton.setIcon(AllIcons.Actions.Refresh); - - myLabel.setPreferredSize(new JLabel("Project name").getPreferredSize()); } + private void setupBrowseButton() { myBrowseButton.putClientProperty("JButton.buttonType", "null"); myBrowseButton.setIcon(InteractiveLearningIcons.InterpreterGear); @@ -190,14 +201,11 @@ public class StudyNewProjectPanel { private class RefreshActionListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { - ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> { - ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true); - final List courses; - courses = execCancelable(() -> myGenerator.getCourses(true)); - if (courses != null) { - ApplicationManager.getApplication().invokeLater(() -> refreshCoursesList(courses)); - } - }, "Refreshing Course List", true, DefaultProjectFactory.getInstance().getDefaultProject()); + final List courses = + myGenerator.getCoursesUnderProgress(true, "Refreshing Course List", DefaultProjectFactory.getInstance().getDefaultProject()); + if (courses != null) { + refreshCoursesList(courses); + } } } @@ -208,22 +216,26 @@ public class StudyNewProjectPanel { } myCoursesComboBox.removeAllItems(); + addCoursesToCombobox(courses); + myGenerator.setSelectedCourse(StudyUtils.getFirst(courses)); + + myGenerator.setCourses(courses); + myAvailableCourses = courses; + StudyProjectGenerator.flushCache(myAvailableCourses); + } + + private void addCoursesToCombobox(@NotNull List courses) { for (CourseInfo courseInfo : courses) { if (!courseInfo.isAdaptive()) { myCoursesComboBox.addItem(courseInfo); } else { final boolean isLoggedIn = myGenerator.myUser != null; - if (courseInfo.isPublic() || isLoggedIn) { + if (isLoggedIn) { myCoursesComboBox.addItem(courseInfo); } } } - myGenerator.setSelectedCourse(StudyUtils.getFirst(courses)); - - myGenerator.setCourses(courses); - myAvailableCourses = courses; - StudyProjectGenerator.flushCache(myAvailableCourses); } @@ -240,6 +252,7 @@ public class StudyNewProjectPanel { if (selectedCourse == null || selectedCourse.equals(CourseInfo.INVALID_COURSE)) { myAuthorLabel.setText(""); myDescriptionLabel.setText(""); + setError(INVALID_COURSE); return; } final String authorsString = Course.getAuthorsString(selectedCourse.getAuthors()); @@ -265,6 +278,7 @@ public class StudyNewProjectPanel { protected AddRemoteDialog() { super(null); + setTitle("Login To Stepic"); myRemoteCourse = new StudyAddRemoteCourse(); init(); } @@ -296,39 +310,23 @@ public class StudyNewProjectPanel { ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> { ProgressManager.getInstance().getProgressIndicator().setIndeterminate(true); - final StepicUser stepicUser = execCancelable(() -> EduStepicConnector.login(myRemoteCourse.getLogin(), - myRemoteCourse.getPassword())); + final StepicUser stepicUser = StudyUtils.execCancelable(() -> EduStepicConnector.login(myRemoteCourse.getLogin(), + myRemoteCourse.getPassword())); if (stepicUser != null) { stepicUser.setEmail(myRemoteCourse.getLogin()); stepicUser.setPassword(myRemoteCourse.getPassword()); myGenerator.myUser = stepicUser; - final List courses = execCancelable(() -> myGenerator.getCourses(true)); + final List courses = myGenerator.getCoursesAsynchronouslyIfNeeded(true); if (courses != null) { ApplicationManager.getApplication().invokeLater(() -> refreshCoursesList(courses)); } } + else { + setError("Failed to login"); + } }, "Signing In And Getting Stepic Course List", true, new DefaultProjectFactoryImpl().getDefaultProject()); } } - - @Nullable - private static T execCancelable(@NotNull final Callable callable) { - final Future future = - ApplicationManager.getApplication().executeOnPooledThread(callable); - - while (!future.isCancelled() && !future.isDone()) { - ProgressManager.checkCanceled(); - TimeoutUtil.sleep(500); - } - T result = null; - try { - result = future.get(); - } - catch (InterruptedException | ExecutionException e) { - LOG.warn(e.getMessage()); - } - return result; - } } diff --git a/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java b/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java index 551b831f1d5b..a3492b7247b2 100644 --- a/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java +++ b/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyDirectoryProjectGenerator.java @@ -11,6 +11,7 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.platform.DirectoryProjectGenerator; import com.intellij.psi.PsiDirectory; @@ -97,7 +98,7 @@ public class PyStudyDirectoryProjectGenerator extends PythonProjectGenerator imp } public List getCourses() { - return myGenerator.getCourses(false); + return myGenerator.getCoursesUnderProgress(false, "Getting Courses", ProjectManager.getInstance().getDefaultProject()); } public void setSelectedCourse(CourseInfo course) { From c2148fb2ad641296a1dfde73e1a8a6b0bacd1319 Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Wed, 8 Jun 2016 17:58:31 +0300 Subject: [PATCH 36/44] Fix typo in stepic api url --- .../com/jetbrains/edu/learning/stepic/EduStepicConnector.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java index bfb69dc6bb94..bb464476407a 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java @@ -368,7 +368,7 @@ public class EduStepicConnector { } } - final HttpPost attemptRequest = new HttpPost(stepicApiUrl + "ATTEMPTS_URL"); + final HttpPost attemptRequest = new HttpPost(stepicApiUrl + "attempts"); setHeaders(attemptRequest, "application/json"); String attemptRequestBody = new Gson().toJson(new StepicWrappers.AttemptWrapper(task.getStepicId())); attemptRequest.setEntity(new StringEntity(attemptRequestBody, ContentType.APPLICATION_JSON)); @@ -396,7 +396,7 @@ public class EduStepicConnector { } private static void postSubmission(boolean passed, StepicWrappers.AttemptWrapper.Attempt attempt, ArrayList files) throws IOException { - final HttpPost request = new HttpPost(stepicApiUrl + "SUBMISSION_URL"); + final HttpPost request = new HttpPost(stepicApiUrl + "submissions"); setHeaders(request, "application/json"); String requestBody = new Gson().toJson(new StepicWrappers.SubmissionWrapper(attempt.id, passed ? "1" : "0", files)); From ff7d3d1578c86f05b3a46ef21f6c460ce87255c5 Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Thu, 9 Jun 2016 14:37:41 +0300 Subject: [PATCH 37/44] Show test results in test results tool window and get multiline test output only for adaptive courses Show balloons with task check result for adaptive courses --- .../edu/learning/checker/StudyCheckTask.java | 60 ++++++++++++++----- .../edu/learning/checker/StudyCheckUtils.java | 2 +- .../learning/checker/StudySmartChecker.java | 20 ++++--- .../checker/StudyTestsOutputParser.java | 29 +++++---- .../edu/learning/PyStudyCheckAction.java | 13 +++- 5 files changed, 85 insertions(+), 39 deletions(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java index fc6d39649c6b..dccc94d3ff77 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java @@ -106,19 +106,22 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun () -> StudyCheckUtils.showTestResultPopUp("Check cancelled", MessageType.WARNING.getPopupBackground(), myProject)); } - - final StudyTestsOutputParser.TestsOutput testsOutput = StudyTestsOutputParser.getTestsOutput(output); - String stderr = output.getStderr(); - if (!stderr.isEmpty()) { - ApplicationManager.getApplication().invokeLater(() -> - StudyCheckUtils.showTestResultPopUp("Failed to launch checking", - MessageType.WARNING.getPopupBackground(), - myProject)); - //log error output of tests - LOG.info("#educational " + stderr); - return null; + final Course course = StudyTaskManager.getInstance(myProject).getCourse(); + if (course != null) { + final StudyTestsOutputParser.TestsOutput testsOutput = StudyTestsOutputParser.getTestsOutput(output, course.isAdaptive()); + String stderr = output.getStderr(); + if (!stderr.isEmpty()) { + ApplicationManager.getApplication().invokeLater(() -> + StudyCheckUtils.showTestResultPopUp("Failed to launch checking", + MessageType.WARNING.getPopupBackground(), + myProject)); + //log error output of tests + LOG.info("#educational " + stderr); + return null; + } + return testsOutput; } - return testsOutput; + return null; } private void checkForAdaptiveCourse(ProgressIndicator indicator) { @@ -154,15 +157,40 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun protected void onTaskFailed(String message) { myTaskManger.setStatus(myTask, StudyStatus.Failed); - ApplicationManager.getApplication().invokeLater( - () -> StudyCheckUtils.showTestResults(myProject, message, false)); + final Course course = StudyTaskManager.getInstance(myProject).getCourse(); + + if (course != null) { + if (course.isAdaptive()) { + ApplicationManager.getApplication().invokeLater( + () -> { + StudyCheckUtils.showTestResultPopUp("Failed", MessageType.ERROR.getPopupBackground(), myProject); + StudyCheckUtils.showTestResultsToolWindow(myProject, message, false); + }); + } + else { + ApplicationManager.getApplication() + .invokeLater(() -> StudyCheckUtils.showTestResultPopUp(message, MessageType.ERROR.getPopupBackground(), myProject)); + } + } } protected void onTaskSolved(String message) { myTaskManger.setStatus(myTask, StudyStatus.Solved); + final Course course = StudyTaskManager.getInstance(myProject).getCourse(); - ApplicationManager.getApplication().invokeLater( - () -> StudyCheckUtils.showTestResults(myProject, message, true)); + if (course != null) { + if (course.isAdaptive()) { + ApplicationManager.getApplication().invokeLater( + () -> { + StudyCheckUtils.showTestResultPopUp("Congratulations!", MessageType.ERROR.getPopupBackground(), myProject); + StudyCheckUtils.showTestResultsToolWindow(myProject, message, true); + }); + } + else { + ApplicationManager.getApplication() + .invokeLater(() -> StudyCheckUtils.showTestResultPopUp(message, MessageType.INFO.getPopupBackground(), myProject)); + } + } } private void runAfterTaskCheckedActions() { diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java index 38f6e8f91162..ce58a0e61261 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckUtils.java @@ -196,7 +196,7 @@ public class StudyCheckUtils { } } - public static void showTestResults(@NotNull final Project project, @NotNull final String message, boolean solved) { + public static void showTestResultsToolWindow(@NotNull final Project project, @NotNull final String message, boolean solved) { final ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project); ToolWindow window = toolWindowManager.getToolWindow("Test Results"); if (window == null) { diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudySmartChecker.java b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudySmartChecker.java index 14870203abac..b1bb10498731 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudySmartChecker.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudySmartChecker.java @@ -14,6 +14,7 @@ import com.jetbrains.edu.learning.core.EduDocumentListener; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.core.EduUtils; import com.jetbrains.edu.learning.courseFormat.AnswerPlaceholder; +import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseFormat.StudyStatus; import com.jetbrains.edu.learning.courseFormat.TaskFile; import com.jetbrains.edu.learning.StudyTaskManager; @@ -67,14 +68,17 @@ public class StudySmartChecker { Process smartTestProcess = testRunner.createCheckProcess(project, windowCopy.getPath()); final CapturingProcessHandler handler = new CapturingProcessHandler(smartTestProcess, null, windowCopy.getPath()); final ProcessOutput output = handler.runProcess(); - boolean res = StudyTestsOutputParser.getTestsOutput(output).isSuccess(); - StudyTaskManager.getInstance(project).setStatus(userAnswerPlaceholder, res ? StudyStatus.Solved : StudyStatus.Failed); - StudyUtils.deleteFile(windowCopy); - if (fileWindows != null) { - StudyUtils.deleteFile(fileWindows); - } - if (!resourceFile.delete()) { - LOG.error("failed to delete", resourceFile.getPath()); + final Course course = StudyTaskManager.getInstance(project).getCourse(); + if (course != null) { + boolean res = StudyTestsOutputParser.getTestsOutput(output, course.isAdaptive()).isSuccess(); + StudyTaskManager.getInstance(project).setStatus(userAnswerPlaceholder, res ? StudyStatus.Solved : StudyStatus.Failed); + StudyUtils.deleteFile(windowCopy); + if (fileWindows != null) { + StudyUtils.deleteFile(fileWindows); + } + if (!resourceFile.delete()) { + LOG.error("failed to delete", resourceFile.getPath()); + } } } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyTestsOutputParser.java b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyTestsOutputParser.java index fc21b40bc579..90cf27d4c958 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyTestsOutputParser.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyTestsOutputParser.java @@ -31,7 +31,7 @@ public class StudyTestsOutputParser { } @NotNull - public static TestsOutput getTestsOutput(@NotNull final ProcessOutput processOutput) { + public static TestsOutput getTestsOutput(@NotNull final ProcessOutput processOutput, final boolean isAdaptive) { String congratulations = CONGRATULATIONS; final List lines = processOutput.getStdoutLines(); for (int i = 0; i < lines.size(); i++) { @@ -46,18 +46,23 @@ public class StudyTestsOutputParser { } if (line.contains(TEST_FAILED)) { - final StringBuilder builder = new StringBuilder(line.substring(line.indexOf(TEST_FAILED) + TEST_FAILED.length()) + "\n"); - for (int j = i + 1; j < lines.size(); j++) { - final String failedTextLine = lines.get(j); - if (!failedTextLine.contains(STUDY_PREFIX) || !failedTextLine.contains(CONGRATS_MESSAGE)) { - builder.append(failedTextLine); - builder.append("\n"); - } - else { - break; - } + if (!isAdaptive) { + return new TestsOutput(false, line.substring(line.indexOf(TEST_FAILED) + TEST_FAILED.length())); } - return new TestsOutput(false, builder.toString()); + else { + final StringBuilder builder = new StringBuilder(line.substring(line.indexOf(TEST_FAILED) + TEST_FAILED.length()) + "\n"); + for (int j = i + 1; j < lines.size(); j++) { + final String failedTextLine = lines.get(j); + if (!failedTextLine.contains(STUDY_PREFIX) || !failedTextLine.contains(CONGRATS_MESSAGE)) { + builder.append(failedTextLine); + builder.append("\n"); + } + else { + break; + } + } + return new TestsOutput(false, builder.toString()); + } } } } diff --git a/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java b/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java index 0f5ab396df3f..14aac4c80d4e 100644 --- a/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java +++ b/python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyCheckAction.java @@ -7,6 +7,7 @@ import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.IdeFocusManager; import com.jetbrains.edu.learning.actions.StudyCheckAction; @@ -14,7 +15,6 @@ import com.jetbrains.edu.learning.actions.StudyRunAction; import com.jetbrains.edu.learning.checker.StudyCheckTask; import com.jetbrains.edu.learning.checker.StudyCheckUtils; import com.jetbrains.edu.learning.checker.StudyTestRunner; -import com.jetbrains.edu.learning.checker.StudyTestsOutputParser; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.courseFormat.Course; import com.jetbrains.edu.learning.courseFormat.StudyStatus; @@ -113,7 +113,16 @@ public class PyStudyCheckAction extends StudyCheckAction { } final StudyToolWindow toolWindow = StudyUtils.getStudyToolWindow(project); if (toolWindow != null) { - StudyCheckUtils.showTestResults(project, message, false); + final Course course = StudyTaskManager.getInstance(project).getCourse(); + if (course != null) { + if (course.isAdaptive()) { + StudyCheckUtils.showTestResultPopUp("Failed", MessageType.ERROR.getPopupBackground(), project); + StudyCheckUtils.showTestResultsToolWindow(project, message, false); + } + else { + StudyCheckUtils.showTestResultPopUp(message, MessageType.ERROR.getPopupBackground(), project); + } + } StudyCheckUtils.navigateToFailedPlaceholder(myStudyState, myTask, myTaskDir, project); } }); From 700205110d86a43a6198240501c8c0b0a25b78b3 Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Thu, 9 Jun 2016 15:25:29 +0300 Subject: [PATCH 38/44] Make test result tool window console component not editable and remove prompt --- .../edu/learning/ui/StudyTestResultsToolWindowFactory.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindowFactory.kt b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindowFactory.kt index 0035ab8a71ab..652d758ebfc6 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindowFactory.kt +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyTestResultsToolWindowFactory.kt @@ -17,7 +17,9 @@ class StudyTestResultsToolWindowFactory: StudyToolWindowFactory() { val sdk = StudyUtils.findSdk(currentTask, project) if (sdk != null) { val testResultsToolWindow = PythonConsoleView(project, "Local test results", sdk); - + testResultsToolWindow.isEditable = false + testResultsToolWindow.isConsoleEditorEnabled = false + testResultsToolWindow.prompt = null toolWindow.isToHideOnEmptyContent = true val contentManager = toolWindow.contentManager From f279b6861d160e582e7fd5b50c30200231acc7bb Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Thu, 9 Jun 2016 15:35:03 +0300 Subject: [PATCH 39/44] Minor Fix output format for adaptive courses check from samples --- .../resources/fileTemplates/internal/test_helper.py.ft | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/python/educational-python/resources/fileTemplates/internal/test_helper.py.ft b/python/educational-python/resources/fileTemplates/internal/test_helper.py.ft index 250f253d8dec..c858ac375be0 100644 --- a/python/educational-python/resources/fileTemplates/internal/test_helper.py.ft +++ b/python/educational-python/resources/fileTemplates/internal/test_helper.py.ft @@ -205,13 +205,11 @@ def check_samples(samples=()): if "\n".join(output) != sample[1]: failed( "Test from samples failed: \n \n" - "Input:\n {}" + "Input:\n{}" "\n \n" - "Expected:\n {}" + "Expected:\n{}" "\n \n" - "Your result:\n {}".format(sample[0], - sample[1], - "\n".join(output))) + "Your result:\n{}".format(str.strip(sample[0]), str.strip(sample[1]), "\n".join(output))) return set_congratulation_message("All test from samples passed. Now we are checking your solution on Stepic server.") From 685a400ca928aaf59cb3a0a2f8360c90635d9bca Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Thu, 9 Jun 2016 21:16:06 +0300 Subject: [PATCH 40/44] Extract adaptive buttons panel creation in separate class Add FileEditorManagerListener for reaction panel --- .../jetbrains/edu/learning/StudyUtils.java | 11 + .../stepic/StepicAdaptiveReactionsPanel.java | 197 ++++++++++++++++++ .../edu/learning/ui/StudyToolWindow.java | 146 +------------ 3 files changed, 216 insertions(+), 138 deletions(-) create mode 100644 python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicAdaptiveReactionsPanel.java diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java b/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java index e36355be5850..a96751532317 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/StudyUtils.java @@ -633,4 +633,15 @@ public class StudyUtils { } return result; } + + @Nullable + public static Task getTaskFromSelectedEditor(Project project) { + final StudyEditor editor = getSelectedStudyEditor(project); + Task task = null; + if (editor != null) { + final TaskFile file = editor.getTaskFile(); + task = file.getTask(); + } + return task; + } } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicAdaptiveReactionsPanel.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicAdaptiveReactionsPanel.java new file mode 100644 index 000000000000..9bb5afdbf63b --- /dev/null +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicAdaptiveReactionsPanel.java @@ -0,0 +1,197 @@ +package com.jetbrains.edu.learning.stepic; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.FileEditorManagerEvent; +import com.intellij.openapi.fileEditor.FileEditorManagerListener; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.progress.util.ProgressIndicatorBase; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.ui.UIUtil; +import com.jetbrains.edu.learning.StudyUtils; +import com.jetbrains.edu.learning.courseFormat.StudyStatus; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + + +public class StepicAdaptiveReactionsPanel extends JPanel { + private final ReactionButtonPanel myHardPanel; + private final ReactionButtonPanel myBoringPanel; + private static final int TOO_HARD_REACTION = 0; + private static final int TOO_BORING_REACTION = -1; + private static final String HARD_REACTION = "Too Hard"; + private static final String BORING_REACTION = "Too Boring"; + private static final String SOLVED_TASK_TOOLTIP = "Task Is Solved"; + private static final String HARD_LABEL_TOOLTIP = "Click To Get An Easier Task"; + private static final String BORING_LABEL_TOOLTIP = "Click To Get A More Challenging Task"; + + public StepicAdaptiveReactionsPanel() { + setLayout(new GridBagLayout()); + setBackground(UIUtil.getTextFieldBackground()); + + myHardPanel = new ReactionButtonPanel(HARD_REACTION, HARD_LABEL_TOOLTIP, SOLVED_TASK_TOOLTIP, TOO_HARD_REACTION); + myBoringPanel = new ReactionButtonPanel(BORING_REACTION, BORING_LABEL_TOOLTIP, SOLVED_TASK_TOOLTIP, TOO_BORING_REACTION); + addFileListener(); + + final GridBagConstraints c = new GridBagConstraints(); + c.fill = GridBagConstraints.HORIZONTAL; + c.gridx = 0; + c.gridy = 0; + add(Box.createVerticalStrut(3), c); + c.gridx = 1; + c.gridy = 1; + add(Box.createHorizontalStrut(3), c); + c.weightx = 1; + c.gridx = 2; + add(myHardPanel, c); + c.gridx = 3; + c.weightx = 0; + add(Box.createHorizontalStrut(3), c); + c.weightx = 1; + c.gridx = 4; + add(myBoringPanel, c); + c.gridx = 5; + c.weightx = 0; + add(Box.createHorizontalStrut(3), c); + } + + public void setEnabledRecursive(final boolean isEnabled) { + ApplicationManager.getApplication().invokeLater(() -> { + super.setEnabled(isEnabled); + myHardPanel.setEnabledRecursive(isEnabled); + myBoringPanel.setEnabledRecursive(isEnabled); + }); + } + + private void addFileListener() { + final Project project = ProjectUtil.guessCurrentProject(this); + final FileEditorManagerListener editorManagerListener = new FileEditorManagerListener() { + @Override + public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile file) { + final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getTaskFromSelectedEditor(project); + final boolean isEnabled = task != null && task.getStatus() != StudyStatus.Solved; + StepicAdaptiveReactionsPanel.this.setEnabledRecursive(isEnabled); + } + + @Override + public void fileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile file) { + + } + + @Override + public void selectionChanged(@NotNull FileEditorManagerEvent event) { + final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getTaskFromSelectedEditor(project); + final boolean isEnabled = task != null && task.getStatus() != StudyStatus.Solved; + StepicAdaptiveReactionsPanel.this.setEnabledRecursive(isEnabled); + } + }; + project.getMessageBus().connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, editorManagerListener); + } + + private class ReactionButtonPanel extends JPanel { + private final JPanel myButtonPanel; + private final JLabel myLabel; + + public ReactionButtonPanel(@NotNull final String text, + @NotNull final String enabledTooltip, + @NotNull final String disabledTooltip, + int reaction) { + final Project project = ProjectUtil.guessCurrentProject(this); + com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getTaskFromSelectedEditor(project); + final boolean isEnabled = task != null && task.getStatus() != StudyStatus.Solved; + + myLabel = new JLabel(text); + + myButtonPanel = new JPanel(); + myButtonPanel.setLayout(new BoxLayout(myButtonPanel, BoxLayout.PAGE_AXIS)); + myButtonPanel.setToolTipText(isEnabled ? enabledTooltip : disabledTooltip); + myButtonPanel.add(Box.createVerticalStrut(5)); + myButtonPanel.add(myLabel); + myButtonPanel.add(Box.createVerticalStrut(5)); + + setEnabledRecursive(isEnabled); + + setLayout(new GridBagLayout()); + setBorder(BorderFactory.createEtchedBorder()); + add(myButtonPanel); + addMouseListener(() -> EduAdaptiveStepicConnector.addNextRecommendedTask(project, reaction)); + } + + private void addMouseListener(@NotNull Runnable onClickAction) { + final ReactionMouseAdapter mouseAdapter = new ReactionMouseAdapter(onClickAction, this); + this.addMouseListener(mouseAdapter); + myButtonPanel.addMouseListener(mouseAdapter); + myLabel.addMouseListener(mouseAdapter); + } + + + public void setEnabledRecursive(final boolean isEnabled) { + ApplicationManager.getApplication().invokeLater(() -> { + super.setEnabled(isEnabled); + myButtonPanel.setEnabled(isEnabled); + myLabel.setEnabled(isEnabled); + }); + } + + private class ReactionMouseAdapter extends MouseAdapter { + private final Runnable myClickAction; + private final ReactionButtonPanel myPanel; + + public ReactionMouseAdapter(@NotNull final Runnable onClickAction, @NotNull final ReactionButtonPanel mainPanel) { + myClickAction = onClickAction; + myPanel = mainPanel; + } + + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 1) { + final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getCurrentTask(ProjectUtil.guessCurrentProject(myPanel)); + if (task != null && task.getStatus() != StudyStatus.Solved) { + final ProgressIndicatorBase progress = new ProgressIndicatorBase(); + progress.setText("Loading Next Recommendation"); + ProgressManager.getInstance().run(new Task.Backgroundable(ProjectUtil.guessCurrentProject(myPanel), + "Loading Next Recommendation") { + @Override + public void run(@NotNull ProgressIndicator indicator) { + StepicAdaptiveReactionsPanel.this.setEnabledRecursive(false); + myClickAction.run(); + } + + @Override + protected void onFinished() { + StepicAdaptiveReactionsPanel.this.setEnabledRecursive(true); + } + }); + } + } + } + + @Override + public void mouseEntered(MouseEvent e) { + final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getCurrentTask(ProjectUtil.guessCurrentProject(myPanel)); + if (task != null && task.getStatus() != StudyStatus.Solved && myPanel.isEnabled()) { + setBackground(UIUtil.getButtonSelectColor()); + } + } + + @Override + public void mouseExited(MouseEvent e) { + setBackground(UIUtil.getLabelBackground()); + } + + private void setBackground(Color color) { + myPanel.setBackground(color); + myButtonPanel.setBackground(color); + } + } + } +} + diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java index 09cffcee930a..05161fb05fea 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java @@ -18,7 +18,6 @@ package com.jetbrains.edu.learning.ui; import com.intellij.ide.browsers.WebBrowserManager; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.EditorFactory; @@ -26,43 +25,29 @@ import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.FileEditorManagerListener; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.Task; -import com.intellij.openapi.progress.util.ProgressIndicatorBase; import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectUtil; import com.intellij.openapi.ui.SimpleToolWindowPanel; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.JBCardLayout; import com.intellij.ui.OnePixelSplitter; import com.intellij.util.ui.JBUI; -import com.intellij.util.ui.UIUtil; import com.jetbrains.edu.learning.*; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.courseFormat.Course; -import com.jetbrains.edu.learning.courseFormat.StudyStatus; import com.jetbrains.edu.learning.courseFormat.TaskFile; -import com.jetbrains.edu.learning.stepic.EduAdaptiveStepicConnector; +import com.jetbrains.edu.learning.stepic.StepicAdaptiveReactionsPanel; import org.jetbrains.annotations.NotNull; import javax.swing.*; import java.awt.*; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; import java.util.Map; public abstract class StudyToolWindow extends SimpleToolWindowPanel implements DataProvider, Disposable { private static final Logger LOG = Logger.getInstance(StudyToolWindow.class); private static final String TASK_INFO_ID = "taskInfo"; private static final String EMPTY_TASK_TEXT = "Please, open any task to see task description"; - private static final String HARD_REACTION = "Too Hard"; - private static final String BORING_REACTION = "Too Boring"; - private static final String SOLVED_TASK_TOOLTIP = "Task Is Solved"; - private static final String HARD_LABEL_TOOLTIP = "Click To Get An Easier Task"; - private static final String BORING_LABEL_TOOLTIP = "Click To Get A More Challenging Task"; - + private final JBCardLayout myCardLayout; private final JPanel myContentPanel; private final OnePixelSplitter mySplitPane; @@ -80,11 +65,11 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D JPanel toolbarPanel = createToolbarPanel(getActionGroup(project)); setToolbar(toolbarPanel); - + final JPanel panel = new JPanel(new BorderLayout()); final Course course = StudyTaskManager.getInstance(project).getCourse(); if (course != null && course.isAdaptive()) { - panel.add(createReactionPanel(), BorderLayout.NORTH); + panel.add(new StepicAdaptiveReactionsPanel(), BorderLayout.NORTH); } JComponent taskInfoPanel = createTaskInfoPanel(project); panel.add(taskInfoPanel, BorderLayout.CENTER); @@ -101,14 +86,15 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D project.getMessageBus().connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, listener); } - if (StudyTaskManager.getInstance(project).isTurnEditingMode() || StudyTaskManager.getInstance(project).getToolWindowMode() == StudyToolWindowMode.EDITING) { + if (StudyTaskManager.getInstance(project).isTurnEditingMode() || + StudyTaskManager.getInstance(project).getToolWindowMode() == StudyToolWindowMode.EDITING) { TaskFile file = StudyUtils.getSelectedTaskFile(project); if (file != null) { VirtualFile taskDir = file.getTask().getTaskDir(project); setTaskText(taskText, taskDir, project); - } - } else { + } + else { setTaskText(taskText, null, project); } } @@ -122,124 +108,8 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D } } } - - public JPanel createReactionPanel() { - final JPanel mainPanel = new JPanel(new GridBagLayout()); - mainPanel.setBackground(UIUtil.getTextFieldBackground()); - final JPanel hardPanel = createReactionButtonPanel(HARD_REACTION, HARD_LABEL_TOOLTIP, SOLVED_TASK_TOOLTIP, 0); - final JPanel boringPanel = createReactionButtonPanel(BORING_REACTION, BORING_LABEL_TOOLTIP, SOLVED_TASK_TOOLTIP, -1); - - final GridBagConstraints c = new GridBagConstraints(); - c.fill = GridBagConstraints.HORIZONTAL; - c.gridx = 0; - c.gridy = 0; - mainPanel.add(Box.createVerticalStrut(3), c); - - c.gridx = 1; - c.gridy = 1; - mainPanel.add(Box.createHorizontalStrut(3), c); - c.weightx = 1; - c.gridx = 2; - mainPanel.add(hardPanel, c); - c.gridx = 3; - c.weightx = 0; - mainPanel.add(Box.createHorizontalStrut(3), c); - c.weightx = 1; - c.gridx = 4; - mainPanel.add(boringPanel, c); - c.gridx = 5; - c.weightx = 0; - mainPanel.add(Box.createHorizontalStrut(3), c); - - return mainPanel; - } - private JPanel createReactionButtonPanel(@NotNull final String text, - @NotNull final String enabledTooltip, - @NotNull final String disabledTooltip, - int reaction) { - final Project project = ProjectUtil.guessCurrentProject(myContentPanel); - final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getCurrentTask(project); - final boolean isEnabled = task != null && task.getStatus() != StudyStatus.Solved; - - final JLabel label = new JLabel(text); - label.setEnabled(isEnabled); - - final JPanel buttonPanel = new JPanel(); - buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.PAGE_AXIS)); - buttonPanel.setToolTipText(isEnabled ? enabledTooltip : disabledTooltip); - buttonPanel.add(Box.createVerticalStrut(5)); - buttonPanel.add(label); - buttonPanel.add(Box.createVerticalStrut(5)); - - final JPanel mainPanel = new JPanel(new GridBagLayout()); - mainPanel.setBorder(BorderFactory.createEtchedBorder()); - mainPanel.add(buttonPanel); - addMouseListener(mainPanel, buttonPanel, label, () -> EduAdaptiveStepicConnector.addNextRecommendedTask(project, reaction)); - return mainPanel; - } - - private static void addMouseListener(@NotNull final JPanel panel, - @NotNull final JPanel buttonPanel, - @NotNull final JLabel textLabel, - @NotNull Runnable onClickAction) { - final MouseAdapter mouseAdapter = new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - if (e.getClickCount() == 1) { - final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getCurrentTask(ProjectUtil.guessCurrentProject(panel)); - if (task != null && task.getStatus() != StudyStatus.Solved) { - final ProgressIndicatorBase progress = new ProgressIndicatorBase(); - progress.setText("Loading Next Recommendation"); - ProgressManager.getInstance().run(new Task.Backgroundable(ProjectUtil.guessCurrentProject(panel), - "Loading Next Recommendation") { - - @Override - public void run(@NotNull ProgressIndicator indicator) { - setEnabled(false); - onClickAction.run(); - } - - @Override - protected void onFinished() { - setEnabled(true); - } - - private void setEnabled(final boolean enabled) { - ApplicationManager.getApplication().invokeLater(() -> { - panel.setEnabled(enabled); - buttonPanel.setEnabled(enabled); - textLabel.setEnabled(enabled); - }); - } - }); - } - } - } - - @Override - public void mouseEntered(MouseEvent e) { - final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getCurrentTask(ProjectUtil.guessCurrentProject(panel)); - if (task != null && task.getStatus() != StudyStatus.Solved && panel.isEnabled()) { - setBackground(UIUtil.getButtonSelectColor()); - } - } - - @Override - public void mouseExited(MouseEvent e) { - setBackground(UIUtil.getLabelBackground()); - } - - private void setBackground(Color color) { - panel.setBackground(color); - buttonPanel.setBackground(color); - } - }; - panel.addMouseListener(mouseAdapter); - buttonPanel.addMouseListener(mouseAdapter); - } - public void dispose() { } From 846c3974a0b17a534538dcad6941745fc8e3018e Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Fri, 10 Jun 2016 18:47:03 +0300 Subject: [PATCH 41/44] Minor Fix after check balloon background color --- .../src/com/jetbrains/edu/learning/checker/StudyCheckTask.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java index dccc94d3ff77..965347321167 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java @@ -182,7 +182,7 @@ public class StudyCheckTask extends com.intellij.openapi.progress.Task.Backgroun if (course.isAdaptive()) { ApplicationManager.getApplication().invokeLater( () -> { - StudyCheckUtils.showTestResultPopUp("Congratulations!", MessageType.ERROR.getPopupBackground(), myProject); + StudyCheckUtils.showTestResultPopUp("Congratulations!", MessageType.INFO.getPopupBackground(), myProject); StudyCheckUtils.showTestResultsToolWindow(myProject, message, true); }); } From 67ad50b8a2fbf7de719ef74d16941ad5b64d72ff Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Tue, 14 Jun 2016 14:21:23 +0300 Subject: [PATCH 42/44] Initialize client only if client is null in EduStepicConnector --- .../stepic/EduAdaptiveStepicConnector.java | 1 - .../learning/stepic/EduStepicConnector.java | 63 +++++++++++-------- 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java index a1a4a62c813b..0e200d8b5add 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java @@ -160,7 +160,6 @@ public class EduAdaptiveStepicConnector { public static boolean postRecommendationReaction(@NotNull final Project project, @NotNull final String lessonId, @NotNull final String user, int reaction) { - final HttpPost post = new HttpPost(STEPIC_API_URL + RECOMMENDATION_REACTIONS_URL); final String json = new Gson() .toJson(new StepicWrappers.RecommendationReactionWrapper(new StepicWrappers.RecommendationReaction(reaction, user, lessonId))); diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java index bb464476407a..8326b670e12c 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduStepicConnector.java @@ -112,37 +112,46 @@ public class EduStepicConnector { } public static void initializeClient() { - final HttpGet request = new HttpGet(stepicUrl); - request.addHeader(new BasicHeader("referer", "https://stepic.org")); - request.addHeader(new BasicHeader("content-type", "application/json")); + if (ourClient == null) { + final HttpGet request = new HttpGet(stepicUrl); + request.addHeader(new BasicHeader("referer", "https://stepic.org")); + request.addHeader(new BasicHeader("content-type", "application/json")); - HttpClientBuilder builder = HttpClients.custom().setSslcontext(CertificateManager.getInstance().getSslContext()).setMaxConnPerRoute(100000). - setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE); - ourCookieStore = new BasicCookieStore(); + HttpClientBuilder builder = + HttpClients.custom().setSslcontext(CertificateManager.getInstance().getSslContext()).setMaxConnPerRoute(100000). + setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE); + ourCookieStore = new BasicCookieStore(); - try { - // Create a trust manager that does not validate certificate for this connection - TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() { - public X509Certificate[] getAcceptedIssuers() { return null; } - public void checkClientTrusted(X509Certificate[] certs, String authType) {} - public void checkServerTrusted(X509Certificate[] certs, String authType) {} - }}; - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, trustAllCerts, new SecureRandom()); - ourClient = builder.setDefaultCookieStore(ourCookieStore).setSslcontext(sslContext).build(); + try { + // Create a trust manager that does not validate certificate for this connection + TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() { + public X509Certificate[] getAcceptedIssuers() { + return null; + } - ourClient.execute(request); - saveCSRFToken(); - } - catch (IOException e) { - LOG.error(e.getMessage()); - } - catch (NoSuchAlgorithmException e) { - LOG.error(e.getMessage()); - } - catch (KeyManagementException e) { - LOG.error(e.getMessage()); + public void checkClientTrusted(X509Certificate[] certs, String authType) { + } + + public void checkServerTrusted(X509Certificate[] certs, String authType) { + } + }}; + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, trustAllCerts, new SecureRandom()); + ourClient = builder.setDefaultCookieStore(ourCookieStore).setSslcontext(sslContext).build(); + + ourClient.execute(request); + saveCSRFToken(); + } + catch (IOException e) { + LOG.error(e.getMessage()); + } + catch (NoSuchAlgorithmException e) { + LOG.error(e.getMessage()); + } + catch (KeyManagementException e) { + LOG.error(e.getMessage()); + } } } From 0bbd525e12eb9c2d9d5475d029d661eb1f05c5c3 Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Tue, 14 Jun 2016 16:07:10 +0300 Subject: [PATCH 43/44] Minor Pass current project to StepicAdaptiveRreactionPanel --- .../stepic/StepicAdaptiveReactionsPanel.java | 23 +++++++++---------- .../edu/learning/ui/StudyToolWindow.java | 2 +- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicAdaptiveReactionsPanel.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicAdaptiveReactionsPanel.java index 9bb5afdbf63b..fb24f7f51330 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicAdaptiveReactionsPanel.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/StepicAdaptiveReactionsPanel.java @@ -9,7 +9,6 @@ import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; import com.intellij.openapi.progress.util.ProgressIndicatorBase; import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.ui.UIUtil; import com.jetbrains.edu.learning.StudyUtils; @@ -25,6 +24,7 @@ import java.awt.event.MouseEvent; public class StepicAdaptiveReactionsPanel extends JPanel { private final ReactionButtonPanel myHardPanel; private final ReactionButtonPanel myBoringPanel; + private final Project myProject; private static final int TOO_HARD_REACTION = 0; private static final int TOO_BORING_REACTION = -1; private static final String HARD_REACTION = "Too Hard"; @@ -33,7 +33,8 @@ public class StepicAdaptiveReactionsPanel extends JPanel { private static final String HARD_LABEL_TOOLTIP = "Click To Get An Easier Task"; private static final String BORING_LABEL_TOOLTIP = "Click To Get A More Challenging Task"; - public StepicAdaptiveReactionsPanel() { + public StepicAdaptiveReactionsPanel(@NotNull final Project project) { + myProject = project; setLayout(new GridBagLayout()); setBackground(UIUtil.getTextFieldBackground()); @@ -72,11 +73,10 @@ public class StepicAdaptiveReactionsPanel extends JPanel { } private void addFileListener() { - final Project project = ProjectUtil.guessCurrentProject(this); final FileEditorManagerListener editorManagerListener = new FileEditorManagerListener() { @Override public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile file) { - final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getTaskFromSelectedEditor(project); + final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getTaskFromSelectedEditor(myProject); final boolean isEnabled = task != null && task.getStatus() != StudyStatus.Solved; StepicAdaptiveReactionsPanel.this.setEnabledRecursive(isEnabled); } @@ -88,12 +88,12 @@ public class StepicAdaptiveReactionsPanel extends JPanel { @Override public void selectionChanged(@NotNull FileEditorManagerEvent event) { - final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getTaskFromSelectedEditor(project); + final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getTaskFromSelectedEditor(myProject); final boolean isEnabled = task != null && task.getStatus() != StudyStatus.Solved; StepicAdaptiveReactionsPanel.this.setEnabledRecursive(isEnabled); } }; - project.getMessageBus().connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, editorManagerListener); + myProject.getMessageBus().connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, editorManagerListener); } private class ReactionButtonPanel extends JPanel { @@ -104,8 +104,7 @@ public class StepicAdaptiveReactionsPanel extends JPanel { @NotNull final String enabledTooltip, @NotNull final String disabledTooltip, int reaction) { - final Project project = ProjectUtil.guessCurrentProject(this); - com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getTaskFromSelectedEditor(project); + com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getTaskFromSelectedEditor(myProject); final boolean isEnabled = task != null && task.getStatus() != StudyStatus.Solved; myLabel = new JLabel(text); @@ -122,7 +121,7 @@ public class StepicAdaptiveReactionsPanel extends JPanel { setLayout(new GridBagLayout()); setBorder(BorderFactory.createEtchedBorder()); add(myButtonPanel); - addMouseListener(() -> EduAdaptiveStepicConnector.addNextRecommendedTask(project, reaction)); + addMouseListener(() -> EduAdaptiveStepicConnector.addNextRecommendedTask(myProject, reaction)); } private void addMouseListener(@NotNull Runnable onClickAction) { @@ -153,11 +152,11 @@ public class StepicAdaptiveReactionsPanel extends JPanel { @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 1) { - final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getCurrentTask(ProjectUtil.guessCurrentProject(myPanel)); + final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getCurrentTask(myProject); if (task != null && task.getStatus() != StudyStatus.Solved) { final ProgressIndicatorBase progress = new ProgressIndicatorBase(); progress.setText("Loading Next Recommendation"); - ProgressManager.getInstance().run(new Task.Backgroundable(ProjectUtil.guessCurrentProject(myPanel), + ProgressManager.getInstance().run(new Task.Backgroundable(myProject, "Loading Next Recommendation") { @Override public void run(@NotNull ProgressIndicator indicator) { @@ -176,7 +175,7 @@ public class StepicAdaptiveReactionsPanel extends JPanel { @Override public void mouseEntered(MouseEvent e) { - final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getCurrentTask(ProjectUtil.guessCurrentProject(myPanel)); + final com.jetbrains.edu.learning.courseFormat.Task task = StudyUtils.getCurrentTask(myProject); if (task != null && task.getStatus() != StudyStatus.Solved && myPanel.isEnabled()) { setBackground(UIUtil.getButtonSelectColor()); } diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java index 05161fb05fea..1aad834bb9fc 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/ui/StudyToolWindow.java @@ -69,7 +69,7 @@ public abstract class StudyToolWindow extends SimpleToolWindowPanel implements D final JPanel panel = new JPanel(new BorderLayout()); final Course course = StudyTaskManager.getInstance(project).getCourse(); if (course != null && course.isAdaptive()) { - panel.add(new StepicAdaptiveReactionsPanel(), BorderLayout.NORTH); + panel.add(new StepicAdaptiveReactionsPanel(project), BorderLayout.NORTH); } JComponent taskInfoPanel = createTaskInfoPanel(project); panel.add(taskInfoPanel, BorderLayout.CENTER); From 83ae73570a2ae4b54b1fe80a8f6fbb900f2a9957 Mon Sep 17 00:00:00 2001 From: Valentina Kiryushkina Date: Wed, 15 Jun 2016 12:55:02 +0300 Subject: [PATCH 44/44] Minor Remove useless navigate to task call --- .../edu/learning/stepic/EduAdaptiveStepicConnector.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java index 0e200d8b5add..3da3bfc16b78 100644 --- a/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java +++ b/python/educational-core/student/src/com/jetbrains/edu/learning/stepic/EduAdaptiveStepicConnector.java @@ -18,7 +18,6 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import com.jetbrains.edu.learning.StudyTaskManager; import com.jetbrains.edu.learning.StudyUtils; -import com.jetbrains.edu.learning.actions.StudyNextStudyTaskAction; import com.jetbrains.edu.learning.checker.StudyExecutor; import com.jetbrains.edu.learning.core.EduNames; import com.jetbrains.edu.learning.courseFormat.*; @@ -250,7 +249,6 @@ public class EduAdaptiveStepicConnector { StudyProjectGenerator.flushLesson(lessonDirectory, adaptive); StudyProjectGenerator.flushCourseJson(course, new File(course.getCourseDirectory())); adaptive.initLesson(course, true); - ApplicationManager.getApplication().invokeLater(() -> new StudyNextStudyTaskAction().navigateTask(project)); } } ApplicationManager.getApplication().invokeLater(() -> {