Fix recommendation getting if non-code recommendation was received: we shouldn't...
[idea/community.git] / python / educational-core / student / src / com / jetbrains / edu / learning / stepic / EduAdaptiveStepicConnector.java
1 package com.jetbrains.edu.learning.stepic;
2
3 import com.google.gson.FieldNamingPolicy;
4 import com.google.gson.Gson;
5 import com.google.gson.GsonBuilder;
6 import com.intellij.ide.projectView.ProjectView;
7 import com.intellij.lang.Language;
8 import com.intellij.openapi.application.ApplicationManager;
9 import com.intellij.openapi.diagnostic.Logger;
10 import com.intellij.openapi.editor.Document;
11 import com.intellij.openapi.editor.Editor;
12 import com.intellij.openapi.progress.ProgressIndicator;
13 import com.intellij.openapi.project.Project;
14 import com.intellij.openapi.projectRoots.Sdk;
15 import com.intellij.openapi.ui.MessageType;
16 import com.intellij.openapi.ui.popup.Balloon;
17 import com.intellij.openapi.ui.popup.JBPopupFactory;
18 import com.intellij.openapi.util.Pair;
19 import com.intellij.openapi.util.io.FileUtil;
20 import com.intellij.openapi.util.text.StringUtil;
21 import com.intellij.openapi.vfs.VfsUtil;
22 import com.intellij.openapi.vfs.VirtualFile;
23 import com.intellij.openapi.vfs.VirtualFileManager;
24 import com.jetbrains.edu.learning.StudyTaskManager;
25 import com.jetbrains.edu.learning.StudyUtils;
26 import com.jetbrains.edu.learning.checker.StudyExecutor;
27 import com.jetbrains.edu.learning.core.EduNames;
28 import com.jetbrains.edu.learning.courseFormat.*;
29 import com.jetbrains.edu.learning.courseGeneration.StudyGenerator;
30 import com.jetbrains.edu.learning.courseGeneration.StudyProjectGenerator;
31 import com.jetbrains.edu.learning.editor.StudyEditor;
32 import com.jetbrains.edu.learning.navigation.StudyNavigator;
33 import com.jetbrains.edu.learning.ui.StudyToolWindow;
34 import org.apache.http.HttpEntity;
35 import org.apache.http.HttpStatus;
36 import org.apache.http.client.config.RequestConfig;
37 import org.apache.http.client.methods.CloseableHttpResponse;
38 import org.apache.http.client.methods.HttpGet;
39 import org.apache.http.client.methods.HttpPost;
40 import org.apache.http.client.utils.URIBuilder;
41 import org.apache.http.entity.ContentType;
42 import org.apache.http.entity.StringEntity;
43 import org.apache.http.impl.client.CloseableHttpClient;
44 import org.apache.http.util.EntityUtils;
45 import org.jetbrains.annotations.NotNull;
46 import org.jetbrains.annotations.Nullable;
47
48 import java.io.File;
49 import java.io.IOException;
50 import java.io.UnsupportedEncodingException;
51 import java.net.URI;
52 import java.net.URISyntaxException;
53 import java.util.HashMap;
54 import java.util.List;
55 import java.util.Map;
56 import java.util.concurrent.TimeUnit;
57
58 import static com.jetbrains.edu.learning.stepic.EduStepicConnector.*;
59
60 public class EduAdaptiveStepicConnector {
61   public static final String PYTHON2 = "python2";
62   public static final String PYTHON3 = "python3";
63   public static final String PYCHARM_COMMENT = "# Posted from PyCharm Edu\n";
64   private static final Logger LOG = Logger.getInstance(EduAdaptiveStepicConnector.class);
65   private static final int CONNECTION_TIMEOUT = 60 * 1000;
66
67   @Nullable
68   public static Task getNextRecommendation(@NotNull final Project project, @NotNull Course course) {
69     try {
70       final CloseableHttpClient client = getHttpClient(project);
71       final URI uri = new URIBuilder(EduStepicNames.STEPIC_API_URL + EduStepicNames.RECOMMENDATIONS_URL)
72         .addParameter(EduNames.COURSE, String.valueOf(course.getId()))
73         .build();
74       final HttpGet request = new HttpGet(uri);
75       setHeaders(request, EduStepicNames.CONTENT_TYPE_APPL_JSON);
76       setTimeout(request);
77
78       final CloseableHttpResponse response = client.execute(request);
79       final HttpEntity responseEntity = response.getEntity();
80       final String responseString = responseEntity != null ? EntityUtils.toString(responseEntity) : "";
81
82       final int statusCode = response.getStatusLine().getStatusCode();
83       EntityUtils.consume(responseEntity);
84       if (statusCode == HttpStatus.SC_OK) {
85         final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
86         final StepicWrappers.RecommendationWrapper recomWrapper = gson.fromJson(responseString, StepicWrappers.RecommendationWrapper.class);
87
88         if (recomWrapper.recommendations.length != 0) {
89           final StepicWrappers.Recommendation recommendation = recomWrapper.recommendations[0];
90           final String lessonId = recommendation.lesson;
91           final StepicWrappers.LessonContainer
92             lessonContainer = getFromStepic(EduStepicNames.LESSONS + lessonId, StepicWrappers.LessonContainer.class);
93           if (lessonContainer.lessons.size() == 1) {
94             final Lesson realLesson = lessonContainer.lessons.get(0);
95             course.getLessons().get(0).setId(Integer.parseInt(lessonId));
96
97             viewAllSteps(client, realLesson.getId());
98
99             for (int stepId : realLesson.steps) {
100               final StepicWrappers.StepSource step = getStep(stepId);
101               if (step.block.name.equals("code")) {
102                 return getTaskFromStep(project, stepId, step.block, realLesson.getName());
103               }
104             }
105
106             final StepicUser user = StudyTaskManager.getInstance(project).getUser();
107             postRecommendationReaction(project, lessonId,
108                                        String.valueOf(user.getId()), -1);
109             return getNextRecommendation(project, course);
110           }
111           else {
112             LOG.warn("Got unexpected number of lessons: " + lessonContainer.lessons.size());
113           }
114         }
115       }
116       else {
117         if ((statusCode == HttpStatus.SC_BAD_REQUEST || statusCode == HttpStatus.SC_UNAUTHORIZED) && login(project)) {
118           return getNextRecommendation(project, course);
119         }
120         throw new IOException("Stepic returned non 200 status code: " + responseString);
121       }
122     }
123     catch (IOException e) {
124       LOG.warn(e.getMessage());
125
126       final String connectionMessages = "Connection problems, Please, try again";
127       final Balloon balloon =
128         JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(connectionMessages, MessageType.ERROR, null)
129           .createBalloon();
130       ApplicationManager.getApplication().invokeLater(() -> {
131         if (StudyUtils.getSelectedEditor(project) != null) {
132           StudyUtils.showCheckPopUp(project, balloon);
133         }
134       });
135     }
136     catch (URISyntaxException e) {
137       LOG.warn(e.getMessage());
138     }
139     return null;
140   }
141
142   private static void setTimeout(HttpGet request) {
143     final RequestConfig requestConfig = RequestConfig.custom()
144       .setConnectionRequestTimeout(CONNECTION_TIMEOUT)
145       .setConnectTimeout(CONNECTION_TIMEOUT)
146       .setSocketTimeout(CONNECTION_TIMEOUT)
147       .build();
148     request.setConfig(requestConfig);
149   }
150
151   private static void setTimeout(HttpPost request) {
152     final RequestConfig requestConfig = RequestConfig.custom()
153       .setConnectionRequestTimeout(CONNECTION_TIMEOUT)
154       .setConnectTimeout(CONNECTION_TIMEOUT)
155       .setSocketTimeout(CONNECTION_TIMEOUT)
156       .build();
157     request.setConfig(requestConfig);
158   }
159
160   private static void viewAllSteps(CloseableHttpClient client, int lessonId) throws URISyntaxException, IOException {
161     final URI unitsUrl = new URIBuilder(EduStepicNames.UNITS).addParameter(EduNames.LESSON, String.valueOf(lessonId)).build();
162     final StepicWrappers.UnitContainer unitContainer = getFromStepic(unitsUrl.toString(), StepicWrappers.UnitContainer.class);
163     if (unitContainer.units.size() != 1) {
164       LOG.warn("Got unexpected numbers of units: " + unitContainer.units.size());
165       return;
166     }
167
168     final URIBuilder builder = new URIBuilder(EduStepicNames.ASSIGNMENT);
169     for (Integer step : unitContainer.units.get(0).assignments) {
170       builder.addParameter("ids[]", String.valueOf(step));
171     }
172     final URI assignmentUrl = builder.build();
173     final StepicWrappers.AssignmentsWrapper assignments = getFromStepic(assignmentUrl.toString(), StepicWrappers.AssignmentsWrapper.class);
174     if (assignments.assignments.size() > 0) {
175       for (StepicWrappers.Assignment assignment : assignments.assignments) {
176         final HttpPost post = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.VIEWS_URL);
177         final StepicWrappers.ViewsWrapper viewsWrapper = new StepicWrappers.ViewsWrapper(assignment.id, assignment.step);
178         post.setEntity(new StringEntity(new Gson().toJson(viewsWrapper)));
179         setHeaders(post, EduStepicNames.CONTENT_TYPE_APPL_JSON);
180         setTimeout(post);
181         final CloseableHttpResponse viewPostResult = client.execute(post);
182         if (viewPostResult.getStatusLine().getStatusCode() != HttpStatus.SC_CREATED) {
183           LOG.warn("Error while Views post, code: " + viewPostResult.getStatusLine().getStatusCode());
184         }
185       }
186     }
187     else {
188       LOG.warn("Got assignments of incorrect length: " + assignments.assignments.size());
189     }
190   }
191
192   public static boolean postRecommendationReaction(@NotNull final Project project, @NotNull final String lessonId,
193                                                    @NotNull final String user, int reaction) {
194     final HttpPost post = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.RECOMMENDATION_REACTIONS_URL);
195     final String json = new Gson()
196       .toJson(new StepicWrappers.RecommendationReactionWrapper(new StepicWrappers.RecommendationReaction(reaction, user, lessonId)));
197     post.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON));
198     final CloseableHttpClient client = getHttpClient(project);
199     setHeaders(post, EduStepicNames.CONTENT_TYPE_APPL_JSON);
200     setTimeout(post);
201     try {
202       final CloseableHttpResponse execute = client.execute(post);
203       final int statusCode = execute.getStatusLine().getStatusCode();
204       if (statusCode == HttpStatus.SC_CREATED) {
205         return true;
206       }
207       else {
208         if ((statusCode == HttpStatus.SC_BAD_REQUEST || statusCode == HttpStatus.SC_UNAUTHORIZED) && login(project)) {
209           return postRecommendationReaction(project, lessonId, user, reaction);
210         }
211         final HttpEntity entity = execute.getEntity();
212         final String entityString = EntityUtils.toString(entity);
213         EntityUtils.consume(entity);
214         LOG.warn("Stepic returned non-201 status code: " + statusCode + " " + entityString);
215         return false;
216       }
217     }
218     catch (IOException e) {
219       LOG.warn(e.getMessage());
220     }
221     return false;
222   }
223
224   public static void addNextRecommendedTask(@NotNull final Project project, int reaction, ProgressIndicator indicator) {
225     final StudyEditor editor = StudyUtils.getSelectedStudyEditor(project);
226     final Course course = StudyTaskManager.getInstance(project).getCourse();
227     if (course != null && editor != null && editor.getTaskFile() != null) {
228       indicator.checkCanceled();
229       final StepicUser user = StudyTaskManager.getInstance(project).getUser();
230
231       final boolean recommendationReaction =
232         postRecommendationReaction(project, String.valueOf(editor.getTaskFile().getTask().getLesson().getId()),
233                                    String.valueOf(user.getId()), reaction);
234       if (recommendationReaction) {
235         indicator.checkCanceled();
236         final Task task = getNextRecommendation(project, course);
237
238         if (task != null) {
239           final Lesson adaptive = course.getLessons().get(0);
240           final Task unsolvedTask = adaptive.getTaskList().get(adaptive.getTaskList().size() - 1);
241           final String lessonName = EduNames.LESSON + String.valueOf(adaptive.getIndex());
242           if (reaction == 0 || reaction == -1) {
243             unsolvedTask.setName(task.getName());
244             unsolvedTask.setStepicId(task.getStepicId());
245             unsolvedTask.setText(task.getText());
246             unsolvedTask.getTestsText().clear();
247             unsolvedTask.setStatus(StudyStatus.Unchecked);
248             final Map<String, String> testsText = task.getTestsText();
249             for (String testName : testsText.keySet()) {
250               unsolvedTask.addTestsTexts(testName, testsText.get(testName));
251             }
252             final Map<String, TaskFile> taskFiles = task.getTaskFiles();
253             if (taskFiles.size() == 1) {
254               final TaskFile taskFile = editor.getTaskFile();
255               taskFile.text = ((TaskFile)taskFiles.values().toArray()[0]).text;
256
257               ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> {
258                 final Document document = editor.getEditor().getDocument();
259                 final String taskFileText = taskFiles.get(EduStepicNames.DEFAULT_TASKFILE_NAME).text;
260                 document.setText(taskFileText);
261               }));
262             }
263             else {
264               LOG.warn("Got task without unexpected number of task files: " + taskFiles.size());
265             }
266
267             final File lessonDirectory = new File(course.getCourseDirectory(), lessonName);
268             final String taskName = EduNames.TASK + String.valueOf(adaptive.getTaskList().size());
269             final File taskDirectory = new File(lessonDirectory, taskName);
270             StudyProjectGenerator.flushTask(task, taskDirectory);
271             StudyProjectGenerator.flushCourseJson(course, new File(course.getCourseDirectory()));
272             final VirtualFile lessonDir = project.getBaseDir().findChild(lessonName);
273
274             if (lessonDir != null) {
275               createTestFiles(course, task, unsolvedTask, lessonDir);
276             }
277             final StudyToolWindow window = StudyUtils.getStudyToolWindow(project);
278             if (window != null) {
279               window.setTaskText(StudyUtils.wrapTextToDisplayLatex(unsolvedTask.getText()), unsolvedTask.getTaskDir(project), project);
280             }
281             StudyNavigator.navigateToTask(project, lessonName, taskName);
282           }
283           else {
284             adaptive.addTask(task);
285             task.setIndex(adaptive.getTaskList().size());
286             final VirtualFile lessonDir = project.getBaseDir().findChild(lessonName);
287
288             if (lessonDir != null) {
289               ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> {
290                 try {
291                   final File lessonDirectory = new File(course.getCourseDirectory(), lessonName);
292                   final String taskName = EduNames.TASK + String.valueOf(task.getIndex());
293                   final File taskDir = new File(lessonDirectory, taskName);
294                   StudyProjectGenerator.flushTask(task, taskDir);
295                   StudyProjectGenerator.flushCourseJson(course, new File(course.getCourseDirectory()));
296                   StudyGenerator.createTask(task, lessonDir, new File(course.getCourseDirectory(), lessonDir.getName()), project);
297                   adaptive.initLesson(course, true);
298                   StudyNavigator.navigateToTask(project, lessonName, taskName);
299                 }
300                 catch (IOException e) {
301                   LOG.warn(e.getMessage());
302                 }
303               }));
304             }
305           }
306         }
307         ApplicationManager.getApplication().invokeLater(() -> {
308           VirtualFileManager.getInstance().refreshWithoutFileWatcher(false);
309           ProjectView.getInstance(project).refresh();
310         });
311       }
312       else {
313         LOG.warn("Recommendation reactions weren't posted");
314         ApplicationManager.getApplication().invokeLater(() -> {
315           StudyUtils.showErrorPopupOnToolbar(project);
316         });
317       }
318     }
319   }
320
321   private static void createTestFiles(Course course, Task task, Task unsolvedTask, VirtualFile lessonDir) {
322     ApplicationManager.getApplication().invokeLater(() -> ApplicationManager.getApplication().runWriteAction(() -> {
323       try {
324         final VirtualFile taskDir = VfsUtil
325           .findFileByIoFile(new File(lessonDir.getCanonicalPath(), EduNames.TASK + unsolvedTask.getIndex()), true);
326         final File resourceRoot = new File(course.getCourseDirectory(), lessonDir.getName());
327         File newResourceRoot = null;
328         if (taskDir != null) {
329           newResourceRoot = new File(resourceRoot, taskDir.getName());
330           File[] filesInTask = newResourceRoot.listFiles();
331           if (filesInTask != null) {
332             for (File file : filesInTask) {
333               String fileName = file.getName();
334               if (!task.isTaskFile(fileName)) {
335                 File resourceFile = new File(newResourceRoot, fileName);
336                 File fileInProject = new File(taskDir.getCanonicalPath(), fileName);
337                 FileUtil.copy(resourceFile, fileInProject);
338               }
339             }
340           }
341         }
342         else {
343           LOG.warn("Task directory is null");
344         }
345       }
346       catch (IOException e) {
347         LOG.warn(e.getMessage());
348       }
349     }));
350   }
351
352   @NotNull
353   private static Task getTaskFromStep(Project project, int lessonID, @NotNull final StepicWrappers.Step step, @NotNull String name) {
354     final Task task = new Task();
355     task.setName(name);
356     task.setStepicId(lessonID);
357     task.setText(step.text);
358     task.setStatus(StudyStatus.Unchecked);
359     if (step.options.samples != null) {
360       final StringBuilder builder = new StringBuilder();
361       for (List<String> sample : step.options.samples) {
362         if (sample.size() == 2) {
363           builder.append("<b>Sample Input:</b><br>");
364           builder.append(StringUtil.replace(sample.get(0), "\n", "<br>"));
365           builder.append("<br>");
366           builder.append("<b>Sample Output:</b><br>");
367           builder.append(StringUtil.replace(sample.get(1), "\n", "<br>"));
368           builder.append("<br><br>");
369         }
370       }
371       task.setText(task.getText() + "<br>" + builder.toString());
372     }
373
374     if (step.options.executionMemoryLimit != null && step.options.executionTimeLimit != null) {
375       String builder = "<b>Memory limit</b>: " +
376                        step.options.executionMemoryLimit + " Mb" +
377                        "<br>" +
378                        "<b>Time limit</b>: " +
379                        step.options.executionTimeLimit + "s" +
380                        "<br><br>";
381       task.setText(task.getText() + builder);
382     }
383
384     if (step.options.test != null) {
385       for (StepicWrappers.TestFileWrapper wrapper : step.options.test) {
386         task.addTestsTexts(wrapper.name, wrapper.text);
387       }
388     }
389     else {
390       if (step.options.samples != null) {
391         createTestFileFromSamples(task, step.options.samples);
392       }
393     }
394
395     task.taskFiles = new HashMap<String, TaskFile>();      // TODO: it looks like we don't need taskFiles as map anymore
396     if (step.options.files != null) {
397       for (TaskFile taskFile : step.options.files) {
398         task.taskFiles.put(taskFile.name, taskFile);
399       }
400     }
401     else {
402       final TaskFile taskFile = new TaskFile();
403       taskFile.name = "code";
404       final String templateForTask = getCodeTemplateForTask(step.options.codeTemplates, task, project);
405       taskFile.text = templateForTask == null ? "# write your answer here \n" : templateForTask;
406       task.taskFiles.put("code.py", taskFile);
407     }
408     return task;
409   }
410
411   private static String getCodeTemplateForTask(@Nullable StepicWrappers.CodeTemplatesWrapper codeTemplates,
412                                                @NotNull final Task task, @NotNull final Project project) {
413     if (codeTemplates != null) {
414       final String languageString = getLanguageString(task, project);
415       if (languageString != null) {
416         return codeTemplates.getTemplateForLanguage(languageString);
417       }
418     }
419
420     return null;
421   }
422
423   @Nullable
424   public static Pair<Boolean, String> checkTask(@NotNull final Project project, @NotNull final Task task) {
425     int attemptId = -1;
426     try {
427       attemptId = getAttemptId(project, task);
428     }
429     catch (IOException e) {
430       LOG.warn(e.getMessage());
431     }
432     if (attemptId != -1) {
433       final Editor editor = StudyUtils.getSelectedEditor(project);
434       String language = getLanguageString(task, project);
435       if (editor != null && language != null) {
436         final CloseableHttpClient client = getHttpClient(project);
437         StepicWrappers.ResultSubmissionWrapper wrapper = postResultsForCheck(client, attemptId, language, editor.getDocument().getText());
438
439         final StepicUser user = StudyTaskManager.getInstance(project).getUser();
440         final int id = user.getId();
441         wrapper = getCheckResults(attemptId, id, client, wrapper);
442         if (wrapper.submissions.length == 1) {
443           final boolean isSolved = !wrapper.submissions[0].status.equals("wrong");
444           return Pair.create(isSolved, wrapper.submissions[0].hint);
445         }
446         else {
447           LOG.warn("Got a submission wrapper with incorrect submissions number: " + wrapper.submissions.length);
448         }
449       }
450     }
451     else {
452       LOG.warn("Got an incorrect attempt id: " + attemptId);
453     }
454     return Pair.create(false, "");
455   }
456
457   @Nullable
458   private static StepicWrappers.ResultSubmissionWrapper postResultsForCheck(@NotNull final CloseableHttpClient client,
459                                                                             final int attemptId,
460                                                                             @NotNull final String language,
461                                                                             @NotNull final String text) {
462     final CloseableHttpResponse response;
463     try {
464       final StepicWrappers.SubmissionToPostWrapper submissionToPostWrapper =
465         new StepicWrappers.SubmissionToPostWrapper(String.valueOf(attemptId), language, PYCHARM_COMMENT + text);
466       final HttpPost httpPost = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.SUBMISSIONS);
467       setHeaders(httpPost, EduStepicNames.CONTENT_TYPE_APPL_JSON);
468       setTimeout(httpPost);
469       try {
470         httpPost.setEntity(new StringEntity(new Gson().toJson(submissionToPostWrapper)));
471       }
472       catch (UnsupportedEncodingException e) {
473         LOG.warn(e.getMessage());
474       }
475       response = client.execute(httpPost);
476       final HttpEntity entity = response.getEntity();
477       final String entityString = EntityUtils.toString(entity);
478       EntityUtils.consume(entity);
479       return new Gson().fromJson(entityString, StepicWrappers.ResultSubmissionWrapper.class);
480     }
481     catch (IOException e) {
482       LOG.warn(e.getMessage());
483     }
484     return null;
485   }
486
487   @NotNull
488   private static StepicWrappers.ResultSubmissionWrapper getCheckResults(int attemptId,
489                                                                         int id,
490                                                                         CloseableHttpClient client,
491                                                                         StepicWrappers.ResultSubmissionWrapper wrapper) {
492     try {
493       while (wrapper.submissions.length == 1 && wrapper.submissions[0].status.equals("evaluation")) {
494         TimeUnit.MILLISECONDS.sleep(500);
495         final URI submissionURI = new URIBuilder(EduStepicNames.STEPIC_API_URL + EduStepicNames.SUBMISSIONS)
496           .addParameter("attempt", String.valueOf(attemptId))
497           .addParameter("order", "desc")
498           .addParameter("user", String.valueOf(id))
499           .build();
500         final HttpGet httpGet = new HttpGet(submissionURI);
501         setHeaders(httpGet, EduStepicNames.CONTENT_TYPE_APPL_JSON);
502         setTimeout(httpGet);
503         final CloseableHttpResponse httpResponse = client.execute(httpGet);
504         final HttpEntity entity = httpResponse.getEntity();
505         final String entityString = EntityUtils.toString(entity);
506         EntityUtils.consume(entity);
507         wrapper = new Gson().fromJson(entityString, StepicWrappers.ResultSubmissionWrapper.class);
508       }
509     }
510     catch (InterruptedException e) {
511       LOG.warn(e.getMessage());
512     }
513     catch (IOException e) {
514       LOG.warn(e.getMessage());
515     }
516     catch (URISyntaxException e) {
517       LOG.warn(e.getMessage());
518     }
519     return wrapper;
520   }
521
522   @Nullable
523   private static String getLanguageString(@NotNull Task task, @NotNull Project project) {
524     final Language pythonLanguage = Language.findLanguageByID("Python");
525     if (pythonLanguage != null) {
526       final Sdk language = StudyExecutor.INSTANCE.forLanguage(pythonLanguage).findSdk(project);
527       if (language != null) {
528         final String versionString = language.getVersionString();
529         if (versionString != null) {
530           final List<String> versionStringParts = StringUtil.split(versionString, " ");
531           if (versionStringParts.size() == 2) {
532             return versionStringParts.get(1).startsWith("2") ? PYTHON2 : PYTHON3;
533           }
534         }
535       }
536       else {
537         StudyUtils.showNoSdkNotification(task, project);
538       }
539     }
540     return null;
541   }
542
543   private static int getAttemptId(@NotNull final Project project, @NotNull Task task) throws IOException {
544     final StepicWrappers.AttemptToPostWrapper attemptWrapper = new StepicWrappers.AttemptToPostWrapper(task.getStepicId());
545
546     final HttpPost post = new HttpPost(EduStepicNames.STEPIC_API_URL + EduStepicNames.ATTEMPTS);
547     post.setEntity(new StringEntity(new Gson().toJson(attemptWrapper)));
548
549     final CloseableHttpClient client = getHttpClient(project);
550     setHeaders(post, EduStepicNames.CONTENT_TYPE_APPL_JSON);
551     setTimeout(post);
552     final CloseableHttpResponse httpResponse = client.execute(post);
553     final int statusCode = httpResponse.getStatusLine().getStatusCode();
554     if (statusCode == HttpStatus.SC_CREATED) {
555       final HttpEntity entity = httpResponse.getEntity();
556       final String entityString = EntityUtils.toString(entity);
557       EntityUtils.consume(entity);
558       final StepicWrappers.AttemptContainer container =
559         new Gson().fromJson(entityString, StepicWrappers.AttemptContainer.class);
560       return (container.attempts != null && !container.attempts.isEmpty()) ? container.attempts.get(0).id : -1;
561     }
562     else {
563       if ((statusCode == HttpStatus.SC_BAD_REQUEST || statusCode == HttpStatus.SC_UNAUTHORIZED) && login(project)) {
564         return getAttemptId(project, task);
565       }
566     }
567     return -1;
568   }
569
570   private static void createTestFileFromSamples(@NotNull final Task task,
571                                                 @NotNull final List<List<String>> samples) {
572
573     String testText = "from test_helper import check_samples\n\n" +
574                       "if __name__ == '__main__':\n" +
575                       "    check_samples(samples=" + new GsonBuilder().create().toJson(samples) + ")";
576     task.addTestsTexts("tests.py", testText);
577   }
578 }