From 1c888f575e988f8d2c5431d625b6b5d9099f13ee Mon Sep 17 00:00:00 2001 From: Oleksandr Liemiahov Date: Mon, 8 Jun 2026 14:10:24 +0300 Subject: [PATCH 1/3] ALT-11025 --- .../HyperskillOpenInIdeRequestHandler.kt | 70 ++++++--------- .../HyperskillProjectOpenTopicProblemsTest.kt | 87 ++++++++++++++++++- 2 files changed, 114 insertions(+), 43 deletions(-) diff --git a/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/stepik/hyperskill/courseGeneration/HyperskillOpenInIdeRequestHandler.kt b/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/stepik/hyperskill/courseGeneration/HyperskillOpenInIdeRequestHandler.kt index 621c855dc..4cb187ce1 100644 --- a/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/stepik/hyperskill/courseGeneration/HyperskillOpenInIdeRequestHandler.kt +++ b/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/stepik/hyperskill/courseGeneration/HyperskillOpenInIdeRequestHandler.kt @@ -11,7 +11,6 @@ import org.hyperskill.academy.learning.* import org.hyperskill.academy.learning.authUtils.requestFocus import org.hyperskill.academy.learning.courseFormat.Course import org.hyperskill.academy.learning.courseFormat.EduFormatNames.HYPERSKILL_PROJECTS_URL -import org.hyperskill.academy.learning.courseFormat.EduFormatNames.KOTLIN import org.hyperskill.academy.learning.courseFormat.FrameworkLesson import org.hyperskill.academy.learning.courseFormat.Lesson import org.hyperskill.academy.learning.courseFormat.Section @@ -213,19 +212,14 @@ object HyperskillOpenInIdeRequestHandler : OpenInIdeRequestHandler findProject { it.matchesById(request.projectId) } is HyperskillOpenStepWithProjectRequest -> { val hyperskillLanguage = request.language - val (languageId, languageVersion) = HyperskillLanguages.getLanguageIdAndVersion(hyperskillLanguage) ?: return null - - findProject { - it.matchesById(request.projectId) && it.languageId == languageId && it.languageVersion == languageVersion && courseFilter(it) - } - ?: findProject { course -> course.isHyperskillProblemsCourse(hyperskillLanguage) } + HyperskillLanguages.getLanguageIdAndVersion(hyperskillLanguage) ?: return null + findProject { course -> course.isHyperskillProblemsCourse(hyperskillLanguage) && courseFilter(course) } } is HyperskillOpenStepRequest -> { val hyperskillLanguage = request.language - val (languageId, languageVersion) = HyperskillLanguages.getLanguageIdAndVersion(hyperskillLanguage) ?: return null - findProject { it is HyperskillCourse && it.languageId == languageId && it.languageVersion == languageVersion && courseFilter(it) } - ?: findProject { course -> course.isHyperskillProblemsCourse(hyperskillLanguage) && courseFilter(course) } + HyperskillLanguages.getLanguageIdAndVersion(hyperskillLanguage) ?: return null + findProject { course -> course.isHyperskillProblemsCourse(hyperskillLanguage) && courseFilter(course) } } } } @@ -241,7 +235,8 @@ object HyperskillOpenInIdeRequestHandler : OpenInIdeRequestHandler { val (languageId, languageVersion) = HyperskillLanguages.getLanguageIdAndVersion(hyperskillLanguage) ?: return Err( @@ -253,41 +248,26 @@ object HyperskillOpenInIdeRequestHandler : OpenInIdeRequestHandler getStepSource(request.stepId, request.isLanguageSelectedByUser) + is HyperskillOpenProjectStageRequest -> null + } + + val hyperskillCourse = createHyperskillCourse(request, hyperskillLanguage, hyperskillProject, stepSource).onError { LOG.warn("Failed to create course: $it") return Err(it) } @@ -361,7 +347,7 @@ object HyperskillOpenInIdeRequestHandler : OpenInIdeRequestHandler() + override fun setUp() { super.setUp() configureMockResponsesForStages() @@ -42,6 +49,71 @@ class HyperskillProjectOpenTopicProblemsTest : HyperskillProjectOpenerTestBase() } } + @Test + fun `test open problem with selected project creates problems project`() { + val selectedProjectCourse = hyperskillCourseWithFiles { + frameworkLesson("lesson1") { + eduTask("task1", stepId = 1) { + taskFile("src/Task.kt", "stage 1") + } + } + } + + val request = HyperskillOpenStepWithProjectRequest(1, step2640.id, FakeGradleBasedLanguage.id) + mockProjectOpener.open(HyperskillOpenInIdeRequestHandler, request) + + val openedCourse = project.course as HyperskillCourse + assertEquals(getProblemsProjectName(FakeGradleBasedLanguage.id), openedCourse.name) + assertNull(openedCourse.hyperskillProject) + assertNull(selectedProjectCourse.getTopicsSection()) + assertProblemLoaded(openedCourse, TOPIC_85_NAME, step2640.title) + } + + @Test + fun `test get course for selected project loads step source once`() { + val request = HyperskillOpenStepWithProjectRequest(1, step2640.id, FakeGradleBasedLanguage.id) + + val course = HyperskillOpenInIdeRequestHandler.getCourse(request, EmptyProgressIndicator()) + .onError { error("Course should be created: $it") } as HyperskillCourse + + assertProblemLoaded(course, TOPIC_85_NAME, step2640.title) + assertStepSourceRequestedOnce(step2640) + } + + @Test + fun `test open problem with selected project reuses problems project`() { + val problemsCourse = hyperskillCourseWithFiles( + projectId = null, + name = getProblemsProjectName(FakeGradleBasedLanguage.id) + ) {} + + val request = HyperskillOpenStepWithProjectRequest(1, step2640.id, FakeGradleBasedLanguage.id) + mockProjectOpener.open(HyperskillOpenInIdeRequestHandler, request) + + assertSame(problemsCourse, project.course) + assertProblemLoaded(problemsCourse, TOPIC_85_NAME, step2640.title) + } + + @Test + fun `test open problem without selected project does not reuse regular hyperskill project`() { + val regularCourse = hyperskillCourseWithFiles { + frameworkLesson("lesson1") { + eduTask("task1", stepId = 1) { + taskFile("src/Task.kt", "stage 1") + } + } + } + + val request = HyperskillOpenStepRequest(step2640.id, FakeGradleBasedLanguage.id) + mockProjectOpener.open(HyperskillOpenInIdeRequestHandler, request) + + val openedCourse = project.course as HyperskillCourse + assertEquals(getProblemsProjectName(FakeGradleBasedLanguage.id), openedCourse.name) + assertNull(openedCourse.hyperskillProject) + assertNull(regularCourse.getTopicsSection()) + assertProblemLoaded(openedCourse, TOPIC_85_NAME, step2640.title) + } + @Test fun `test unknown language`() { val unknownLanguage = "Unknown language" @@ -88,10 +160,22 @@ class HyperskillProjectOpenTopicProblemsTest : HyperskillProjectOpenerTestBase() error("Error is expected: project shouldn't open") } + private fun assertProblemLoaded(course: HyperskillCourse, topicName: String, problemName: String) { + val task = course.getTopicsSection()?.getLesson(topicName)?.getTask(problemName) + assertNotNull("Failed to find `$problemName` problem in `$topicName` topic", task) + } + + private fun assertStepSourceRequestedOnce(stepInfo: StepInfo) { + assertEquals("Step source should be loaded once", 1, stepSourceRequestCounts[stepInfo.id] ?: 0) + } + private fun configureMockResponsesForProblems() { requestedInformation.forEach { information -> mockConnector.withResponseHandler(testRootDisposable) { request, _ -> if (request.pathWithoutPrams.endsWith(information.path) && request.hasParams(information.param)) { + if (information is StepInfo) { + stepSourceRequestCounts[information.id] = (stepSourceRequestCounts[information.id] ?: 0) + 1 + } mockResponse(information.file) } else null @@ -101,6 +185,7 @@ class HyperskillProjectOpenTopicProblemsTest : HyperskillProjectOpenerTestBase() companion object { private const val TOPIC_NAME = "topicName" + private const val TOPIC_85_NAME = "Wildcards" private val step2640 = StepInfo(2640, "Packing bakeries") private val step2641 = StepInfo(2641, "List multiplicator") @@ -127,4 +212,4 @@ class HyperskillProjectOpenTopicProblemsTest : HyperskillProjectOpenerTestBase() step12164, topic1034 ) } -} \ No newline at end of file +} From 1f958f80d1e7827839bffd56de8e212fc19b563b Mon Sep 17 00:00:00 2001 From: Oleksandr Liemiahov Date: Fri, 12 Jun 2026 09:19:58 +0300 Subject: [PATCH 2/3] ALT-11025 --- .../stepik/hyperskill/HyperskillLanguages.kt | 2 +- .../hyperskill/HyperskillRestService.kt | 12 +- .../stepik/hyperskill/HyperskillUtils.kt | 4 +- .../HyperskillOpenInIdeRequestHandler.kt | 123 ++++++++---------- .../courseGeneration/HyperskillOpenRequest.kt | 43 ++---- .../academy/learning/MockProjectOpener.kt | 14 ++ .../hyperskill/HyperskillNextActivityTest.kt | 5 +- .../HyperskillProblemLoadingTest.kt | 4 +- .../HyperskillProjectOpenTopicProblemsTest.kt | 47 +++---- .../ai-debugger-core/build.gradle.kts | 4 + 10 files changed, 107 insertions(+), 151 deletions(-) diff --git a/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/stepik/hyperskill/HyperskillLanguages.kt b/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/stepik/hyperskill/HyperskillLanguages.kt index 634ffc742..d1a2f20e3 100644 --- a/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/stepik/hyperskill/HyperskillLanguages.kt +++ b/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/stepik/hyperskill/HyperskillLanguages.kt @@ -36,7 +36,7 @@ enum class HyperskillLanguages(private val id: String, private val languageName: /** * Request language is language plugin received from JBA in requests - * @see [org.hyperskill.academy.learning.stepik.hyperskill.courseGeneration.HyperskillOpenStepWithProjectRequest] + * @see [org.hyperskill.academy.learning.stepik.hyperskill.courseGeneration.HyperskillOpenStepRequest] */ open val requestLanguage: String = languageName diff --git a/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/stepik/hyperskill/HyperskillRestService.kt b/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/stepik/hyperskill/HyperskillRestService.kt index e58fd1d9d..3ec268b58 100644 --- a/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/stepik/hyperskill/HyperskillRestService.kt +++ b/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/stepik/hyperskill/HyperskillRestService.kt @@ -154,14 +154,10 @@ open class HyperskillRestService : OAuthRestService(HYPERSKILL) { if (!HyperskillConnector.getInstance().isLoggedIn()) { error("Attempt to open step for unauthorized user") } - val projectId = getSelectedProjectIdUnderProgress() ?: return openInIDE( - HyperskillOpenStepRequest( - stepId, - language, - isLanguageSelectedByUser - ), request, context - ) - return openInIDE(HyperskillOpenStepWithProjectRequest(projectId, stepId, language, isLanguageSelectedByUser), request, context) + // Problems are always opened in a separate problems project, so the project selected + // on Hyperskill is irrelevant here and must not be requested: an extra API call slows + // opening down and its failure would block opening the problem + return openInIDE(HyperskillOpenStepRequest(stepId, language, isLanguageSelectedByUser), request, context) } private fun getLanguageSelectedByUser(): Result { diff --git a/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/stepik/hyperskill/HyperskillUtils.kt b/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/stepik/hyperskill/HyperskillUtils.kt index d0bdc73c8..58f71f2b6 100644 --- a/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/stepik/hyperskill/HyperskillUtils.kt +++ b/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/stepik/hyperskill/HyperskillUtils.kt @@ -28,7 +28,7 @@ import org.hyperskill.academy.learning.stepik.hyperskill.api.HyperskillConnector import org.hyperskill.academy.learning.stepik.hyperskill.api.HyperskillStepSource import org.hyperskill.academy.learning.stepik.hyperskill.api.WithPaginationMetaData import org.hyperskill.academy.learning.stepik.hyperskill.courseGeneration.HyperskillOpenInIdeRequestHandler -import org.hyperskill.academy.learning.stepik.hyperskill.courseGeneration.HyperskillOpenStepWithProjectRequest +import org.hyperskill.academy.learning.stepik.hyperskill.courseGeneration.HyperskillOpenStepRequest import org.hyperskill.academy.learning.stepik.hyperskill.settings.HyperskillSettings import org.hyperskill.academy.learning.yaml.YamlFormatSynchronizer import javax.swing.event.HyperlinkEvent @@ -220,7 +220,7 @@ private fun openStep(project: Project, task: Task?, nextActivityInfo: NextActivi val language = HyperskillLanguages.getRequestLanguage(course.languageId) ?: return ProjectOpener.getInstance().open( HyperskillOpenInIdeRequestHandler, - HyperskillOpenStepWithProjectRequest(course.id, nextStep.id, language) + HyperskillOpenStepRequest(nextStep.id, language) ).onError { logger().warn("Opening the next activity resulted in an error: ${it.message}. The error was ignored and not displayed for the user.") } diff --git a/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/stepik/hyperskill/courseGeneration/HyperskillOpenInIdeRequestHandler.kt b/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/stepik/hyperskill/courseGeneration/HyperskillOpenInIdeRequestHandler.kt index 4cb187ce1..ac8703e91 100644 --- a/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/stepik/hyperskill/courseGeneration/HyperskillOpenInIdeRequestHandler.kt +++ b/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/stepik/hyperskill/courseGeneration/HyperskillOpenInIdeRequestHandler.kt @@ -115,7 +115,7 @@ object HyperskillOpenInIdeRequestHandler : OpenInIdeRequestHandler Boolean) -> Pair? ): Project? { when (request) { - is HyperskillOpenStepRequestBase -> { + is HyperskillOpenStepRequest -> { val stepId = request.stepId val stepSource = getStepSource(stepId, request.isLanguageSelectedByUser) val isAndroidEnvRequired = stepSource.framework == EduNames.ANDROID @@ -210,12 +210,6 @@ object HyperskillOpenInIdeRequestHandler : OpenInIdeRequestHandler? { return when (request) { is HyperskillOpenProjectStageRequest -> findProject { it.matchesById(request.projectId) } - is HyperskillOpenStepWithProjectRequest -> { - val hyperskillLanguage = request.language - HyperskillLanguages.getLanguageIdAndVersion(hyperskillLanguage) ?: return null - findProject { course -> course.isHyperskillProblemsCourse(hyperskillLanguage) && courseFilter(course) } - } - is HyperskillOpenStepRequest -> { val hyperskillLanguage = request.language HyperskillLanguages.getLanguageIdAndVersion(hyperskillLanguage) ?: return null @@ -248,7 +242,7 @@ object HyperskillOpenInIdeRequestHandler : OpenInIdeRequestHandler { + LOG.info("Processing HyperskillOpenStepRequest: stepId=${request.stepId}, language=${request.language}") + indicator.text2 = EduCoreBundle.message("hyperskill.loading.step.info") + indicator.fraction = 0.2 + + val newProject = HyperskillProject() + val hyperskillLanguage = request.language + val stepSource = getStepSource(request.stepId, request.isLanguageSelectedByUser) + val hyperskillCourse = createHyperskillCourse(request, hyperskillLanguage, newProject, stepSource).onError { return Err(it) } - request as HyperskillOpenWithProjectRequestBase - LOG.info("Fetching project info for projectId=${request.projectId}") - indicator.text = EduCoreBundle.message("hyperskill.loading.project.info") - indicator.text2 = EduCoreBundle.message("hyperskill.loading.project.info.details") - indicator.fraction = 0.1 + hyperskillCourse.validateLanguage(hyperskillLanguage).onError { + LOG.warn("Language validation failed: $it") + return Err(it) + } - val projectStartTime = System.currentTimeMillis() - val hyperskillProject = HyperskillConnector.getInstance().getProject(request.projectId).onError { - LOG.warn("Failed to fetch project ${request.projectId}: $it") - return Err(ValidationErrorMessage(it)) - } - LOG.info("Project info fetched in ${System.currentTimeMillis() - projectStartTime}ms") + indicator.fraction = 0.5 + indicator.text2 = EduCoreBundle.message("hyperskill.loading.problems") + hyperskillCourse.addProblemsWithTopicWithFiles(null, stepSource) + hyperskillCourse.selectedProblem = request.stepId - indicator.fraction = 0.2 - indicator.text = EduCoreBundle.message("hyperskill.creating.course") - indicator.text2 = EduCoreBundle.message("hyperskill.creating.course.details") + indicator.fraction = 1.0 + LOG.info("HyperskillOpenStepRequest processed in ${System.currentTimeMillis() - totalStartTime}ms") + Ok(hyperskillCourse) + } - val hyperskillLanguage = if (request is HyperskillOpenStepWithProjectRequest) request.language else hyperskillProject.language - LOG.info("Creating course for language: $hyperskillLanguage") + is HyperskillOpenProjectStageRequest -> { + LOG.info("Fetching project info for projectId=${request.projectId}") + indicator.text = EduCoreBundle.message("hyperskill.loading.project.info") + indicator.text2 = EduCoreBundle.message("hyperskill.loading.project.info.details") + indicator.fraction = 0.1 + + val projectStartTime = System.currentTimeMillis() + val hyperskillProject = HyperskillConnector.getInstance().getProject(request.projectId).onError { + LOG.warn("Failed to fetch project ${request.projectId}: $it") + return Err(ValidationErrorMessage(it)) + } + LOG.info("Project info fetched in ${System.currentTimeMillis() - projectStartTime}ms") - val stepSource = when (request) { - is HyperskillOpenStepWithProjectRequest -> getStepSource(request.stepId, request.isLanguageSelectedByUser) - is HyperskillOpenProjectStageRequest -> null - } + indicator.fraction = 0.2 + indicator.text = EduCoreBundle.message("hyperskill.creating.course") + indicator.text2 = EduCoreBundle.message("hyperskill.creating.course.details") - val hyperskillCourse = createHyperskillCourse(request, hyperskillLanguage, hyperskillProject, stepSource).onError { - LOG.warn("Failed to create course: $it") - return Err(it) - } + val hyperskillLanguage = hyperskillProject.language + LOG.info("Creating course for language: $hyperskillLanguage") - hyperskillCourse.validateLanguage(hyperskillLanguage).onError { - LOG.warn("Language validation failed: $it") - return Err(it) - } + val hyperskillCourse = createHyperskillCourse(request, hyperskillLanguage, hyperskillProject).onError { + LOG.warn("Failed to create course: $it") + return Err(it) + } - indicator.fraction = 0.4 + hyperskillCourse.validateLanguage(hyperskillLanguage).onError { + LOG.warn("Language validation failed: $it") + return Err(it) + } - when (request) { - is HyperskillOpenStepWithProjectRequest -> { - LOG.info("Loading problems for stepId=${request.stepId}") - indicator.text = EduCoreBundle.message("hyperskill.loading.problems") - indicator.text2 = EduCoreBundle.message("hyperskill.loading.problems.details") - hyperskillCourse.addProblemsWithTopicWithFiles(null, stepSource ?: error("Step source must be loaded for step request")) - hyperskillCourse.selectedProblem = request.stepId - } + indicator.fraction = 0.4 - is HyperskillOpenProjectStageRequest -> { LOG.info("Loading stages for new project, stageId=${request.stageId}") indicator.text = EduCoreBundle.message("hyperskill.loading.stages") indicator.text2 = EduCoreBundle.message("hyperskill.loading.stages.details") @@ -362,12 +347,12 @@ object HyperskillOpenInIdeRequestHandler : OpenInIdeRequestHandler Date: Thu, 18 Jun 2026 10:53:34 +0300 Subject: [PATCH 3/3] ALT-11025 --- .../HyperskillOpenInIdeRequestHandler.kt | 26 +++++++++++----- .../academy/learning/yaml/YamlLoader.kt | 31 +++++++++++++++++++ 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/stepik/hyperskill/courseGeneration/HyperskillOpenInIdeRequestHandler.kt b/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/stepik/hyperskill/courseGeneration/HyperskillOpenInIdeRequestHandler.kt index ac8703e91..11934d5f4 100644 --- a/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/stepik/hyperskill/courseGeneration/HyperskillOpenInIdeRequestHandler.kt +++ b/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/stepik/hyperskill/courseGeneration/HyperskillOpenInIdeRequestHandler.kt @@ -117,13 +117,25 @@ object HyperskillOpenInIdeRequestHandler : OpenInIdeRequestHandler { val stepId = request.stepId - val stepSource = getStepSource(stepId, request.isLanguageSelectedByUser) - val isAndroidEnvRequired = stepSource.framework == EduNames.ANDROID - val courseFilter: (Course) -> Boolean = if (isAndroidEnvRequired) ::hasAndroidEnvironment else { _ -> true } - val (project, course) = findExistingProject(findProject, request, courseFilter) ?: return null - val hyperskillCourse = course as HyperskillCourse - hyperskillCourse.addProblemsWithTopicWithFiles(project, stepSource) - hyperskillCourse.selectedProblem = stepId + // Opening a problem must run in the background, not on the EDT. The post-solve "next activity" flow invokes + // this path directly on the UI thread, where two things go wrong: + // 1. `getStepSource` performs a blocking network request, tripping the "Network requests from EDT are not + // allowed" assertion and freezing the IDE; + // 2. `addProblemsWithTopicWithFiles` shows a modal progress that pumps the event queue, so a YAML reload + // queued by the produced file changes can run against a not-yet-complete course model and report + // "parent for '' was not found" when the user checks the problem. + // Running the whole step-opening off the EDT keeps the course model consistent before any reload. + val (project, hyperskillCourse) = computeUnderProgress(title = EduCoreBundle.message("hyperskill.loading.problems")) { + val stepSource = getStepSource(stepId, request.isLanguageSelectedByUser) + val isAndroidEnvRequired = stepSource.framework == EduNames.ANDROID + val courseFilter: (Course) -> Boolean = if (isAndroidEnvRequired) ::hasAndroidEnvironment else { _ -> true } + val (project, course) = findExistingProject(findProject, request, courseFilter) ?: return@computeUnderProgress null + val hyperskillCourse = course as HyperskillCourse + hyperskillCourse.addProblemsWithTopicWithFiles(project, stepSource) + hyperskillCourse.selectedProblem = stepId + project to hyperskillCourse + } ?: return null + runInEdt { requestFocus() navigateToStep(project, hyperskillCourse, stepId) diff --git a/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/yaml/YamlLoader.kt b/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/yaml/YamlLoader.kt index 14f41382c..17c2c17d1 100644 --- a/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/yaml/YamlLoader.kt +++ b/intellij-plugin/hs-core/src/org/hyperskill/academy/learning/yaml/YamlLoader.kt @@ -205,9 +205,40 @@ object YamlLoader { EduCoreBundle.message("yaml.editor.invalid.unexpected.item.type", itemType) ) } + if (itemContainer == null) { + // TODO(ALT-11025): temporary diagnostics for the "parent for '' was not found" error reported on Check. + // Captures which item/dir failed to resolve, the live course structure, and the stack trace of whatever + // triggered this reload. Remove once the root cause is confirmed. + logParentNotFoundDiagnostics(project, parentDir, course, customContentPath) + } return itemContainer ?: loadingError(EduCoreBundle.message("yaml.editor.invalid.format.parent.not.found", name)) } + private fun StudyItem.logParentNotFoundDiagnostics( + project: Project, + parentDir: VirtualFile, + course: Course, + customContentPath: String + ) { + val sectionDir = parentDir.parent + val structure = course.sections.joinToString("; ") { section -> + "section '${section.name}'(presentable='${section.presentableName}') -> [${section.lessons.joinToString(", ") { it.name }}]" + } + LOG.warn( + "ALT-11025 parent not found while reloading YAML:" + + " item='$name' type=${this::class.simpleName}" + + " parentDir='${parentDir.path}' (name='${parentDir.name}')" + + " sectionDir='${sectionDir?.path}' (name='${sectionDir?.name}')" + + " course=${course::class.simpleName} name='${course.name}' customContentPath='$customContentPath'" + + " courseDirByCustomPath='${project.courseDir.findFileByRelativePath(customContentPath)?.path}'" + + " parentDir.getLesson='${parentDir.getLesson(project)?.name}'" + + " sectionDir.getSection='${sectionDir?.getSection(project)?.name}'" + + " course.topLevelLessons=[${course.lessons.joinToString(", ") { it.name }}]" + + " course.sectionsWithLessons=[$structure]", + Throwable("loadItem trigger stack (ALT-11025 diagnostics)") + ) + } + private fun T.applyChanges(project: Project, deserializedItem: T) { getChangeApplierForItem(project, deserializedItem).applyChanges(this, deserializedItem) }