diff --git a/platform/whatsNew/intellij.platform.whatsNew.iml b/platform/whatsNew/intellij.platform.whatsNew.iml index 15ce108ed47b..0743acaf1813 100644 --- a/platform/whatsNew/intellij.platform.whatsNew.iml +++ b/platform/whatsNew/intellij.platform.whatsNew.iml @@ -40,7 +40,8 @@ - + + \ No newline at end of file diff --git a/platform/whatsNew/src/com/intellij/platform/whatsNew/WhatsNewContent.kt b/platform/whatsNew/src/com/intellij/platform/whatsNew/WhatsNewContent.kt index 53dda6c7d2b4..0cdf8008a42d 100644 --- a/platform/whatsNew/src/com/intellij/platform/whatsNew/WhatsNewContent.kt +++ b/platform/whatsNew/src/com/intellij/platform/whatsNew/WhatsNewContent.kt @@ -27,7 +27,7 @@ import java.net.HttpURLConnection import java.net.URL import java.nio.charset.StandardCharsets -internal sealed class WhatsNewContent { +internal abstract class WhatsNewContent { companion object { private val DataContext.project: Project? get() = CommonDataKeys.PROJECT.getData(this) diff --git a/platform/whatsNew/src/com/intellij/platform/whatsNew/WhatsNewShowOnStartCheckService.kt b/platform/whatsNew/src/com/intellij/platform/whatsNew/WhatsNewShowOnStartCheckService.kt index f7e6aabc34a0..e0065ee46c6c 100644 --- a/platform/whatsNew/src/com/intellij/platform/whatsNew/WhatsNewShowOnStartCheckService.kt +++ b/platform/whatsNew/src/com/intellij/platform/whatsNew/WhatsNewShowOnStartCheckService.kt @@ -8,7 +8,6 @@ import com.intellij.internal.performanceTests.ProjectInitializationDiagnosticSer import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.client.ClientKind import com.intellij.openapi.client.ClientSessionsManager -import com.intellij.openapi.components.service import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.intellij.openapi.startup.ProjectActivity @@ -18,32 +17,55 @@ import com.intellij.util.application import kotlinx.coroutines.withContext import java.util.concurrent.atomic.AtomicBoolean -internal class WhatsNewShowOnStartCheckService : ProjectActivity { - private val ourStarted = AtomicBoolean(false) +internal interface WhatsNewEnvironmentAccessor { + val isForceDisabled: Boolean + suspend fun getWhatsNewContent(): WhatsNewContent? + fun findAction(): WhatsNewAction? + suspend fun showWhatsNew(project: Project, action: WhatsNewAction) +} + +private class WhatsNewEnvironmentAccessorImpl : WhatsNewEnvironmentAccessor { + private val isPlaybackMode = SystemProperties.getBooleanProperty("idea.is.playback", false) + override val isForceDisabled: Boolean + get() = application.isHeadlessEnvironment + || application.isUnitTestMode + || isPlaybackMode + // TODO: disable for remdev since lux is not ready to open what's new + || AppMode.isRemoteDevHost() + // TODO: disable for UI tests since UI tests are not ready for What's new + || Registry.`is`("expose.ui.hierarchy.url", false) + + override suspend fun getWhatsNewContent() = WhatsNewContent.getWhatsNewContent() + override fun findAction() = ActionManager.getInstance().getAction("WhatsNewAction") as? WhatsNewAction + override suspend fun showWhatsNew(project: Project, action: WhatsNewAction) { + action.openWhatsNew(project) + } +} + +internal class WhatsNewShowOnStartCheckService(private val environment: WhatsNewEnvironmentAccessor) : ProjectActivity { + @Suppress("unused") // used by the component container + constructor() : this(WhatsNewEnvironmentAccessorImpl()) + + private val wasStarted = AtomicBoolean(false) + override suspend fun execute(project: Project) { - if (ourStarted.getAndSet(true)) return - if (application.isHeadlessEnvironment - || application.isUnitTestMode - || isPlaybackMode - // TODO: disable for remdev since lux is not ready to open what's new - || AppMode.isRemoteDevHost() - // TODO: disable for UI tests since UI tests are not ready for What's new - || Registry.`is`("expose.ui.hierarchy.url", false)) return + if (wasStarted.getAndSet(true)) return + if (environment.isForceDisabled) return logger.info("Checking whether to show the What's New page on startup.") // a bit hacky workaround but now we don't have any tools to forward local startup activities to a controller val clientId = ClientSessionsManager.getAppSessions(ClientKind.CONTROLLER).firstOrNull()?.clientId ?: ClientId.localId withContext(clientId.asContextElement()) { - val content = WhatsNewContent.getWhatsNewContent() + val content = environment.getWhatsNewContent() logger.info("Got What's New content: $content") if (content != null) { if (WhatsNewContentVersionChecker.isNeedToShowContent(content).also { logger.info("Should show What's New: $it") }) { - val whatsNewAction = service().getAction("WhatsNewAction") as? WhatsNewAction + val whatsNewAction = environment.findAction() if (whatsNewAction != null) { - val activityTracker = ProjectInitializationDiagnosticService.registerTracker(project, "OpenWhatsNewOnStart"); - whatsNewAction.openWhatsNew(project) + val activityTracker = ProjectInitializationDiagnosticService.registerTracker(project, "OpenWhatsNewOnStart") + environment.showWhatsNew(project, whatsNewAction) activityTracker.activityFinished() } } @@ -52,4 +74,4 @@ internal class WhatsNewShowOnStartCheckService : ProjectActivity { } } -private val logger = logger() \ No newline at end of file +private val logger = logger() diff --git a/platform/whatsNew/testSrc/com/intellij/platform/whatsNew/OnStartCheckServiceTest.kt b/platform/whatsNew/testSrc/com/intellij/platform/whatsNew/OnStartCheckServiceTest.kt new file mode 100644 index 000000000000..be822250ab2a --- /dev/null +++ b/platform/whatsNew/testSrc/com/intellij/platform/whatsNew/OnStartCheckServiceTest.kt @@ -0,0 +1,59 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.platform.whatsNew + +import com.intellij.openapi.actionSystem.DataContext +import com.intellij.openapi.project.Project +import com.intellij.testFramework.junit5.TestApplication +import com.intellij.testFramework.junit5.fixture.projectFixture +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import java.util.concurrent.atomic.AtomicBoolean + +@TestApplication +class OnStartCheckServiceTest { + + private val project = projectFixture() + + private val mockContent = object : WhatsNewContent() { + override fun getRequest(dataContext: DataContext?) = error("Mock object, do not call") + override fun getActionWhiteList() = error("Mock object, do not call") + override fun getVersion() = error("Mock object, do not call") + override suspend fun isAvailable() = error("Mock object, do not call") + } + + @Test + fun `WhatsNew should be disabled`() { + val (environment, isCalled) = mockAccessor(isDisabled = true) + val service = WhatsNewShowOnStartCheckService(environment) + runBlocking { + service.execute(project.get()) + assertFalse(isCalled.get(), "What's New page should not be shown") + } + } + + @Test + fun `WhatsNew should be shown`() { + val (environment, isCalled) = mockAccessor(isDisabled = false) + val service = WhatsNewShowOnStartCheckService(environment) + runBlocking { + service.execute(project.get()) + assertTrue(isCalled.get(), "What's New page should be shown") + } + } + + private fun mockAccessor(isDisabled: Boolean): Pair { + val isCalled = AtomicBoolean(false) + val environment = object : WhatsNewEnvironmentAccessor { + override val isForceDisabled = isDisabled + override suspend fun getWhatsNewContent() = mockContent + override fun findAction() = WhatsNewAction() + override suspend fun showWhatsNew(project: Project, action: WhatsNewAction) { + isCalled.set(true) + } + } + + return environment to isCalled + } +}