From cc03cdfb94503874a336cd6bda95e131ef402007 Mon Sep 17 00:00:00 2001 From: Vitaly Legchilkin Date: Tue, 5 Aug 2025 08:56:42 +0000 Subject: [PATCH] [python] add display ids for notifications (PY-83036) Merge-request: IJ-MR-171306 Merged-by: Vitaly Legchilkin GitOrigin-RevId: 7a4b12f4bee6fae93ce5351bda0e19289b8f7404 --- .../intellij.python.community.impl.xml | 53 +++++-- .../black/BlackFormatterAdvertiserService.kt | 3 +- .../debugger/PyCythonExtensionWarning.java | 4 +- .../python/debugger/PyDebugProcess.java | 2 + .../PyCompatibilityInspectionAdvertiser.java | 15 +- .../quickfix/IgnoreRequirementFix.kt | 5 +- .../python/packaging/PyRequirementsTxtUtil.kt | 44 ++++-- .../PyPackagingToolWindowService.kt | 24 ++- .../jetbrains/python/sdk/PythonSdkType.java | 3 +- .../python/sdk/PythonSdkUpdater.java | 4 + .../com/jetbrains/python/sdk/add/v2/common.kt | 2 + .../PyProjectSdkConfiguration.kt | 24 +-- .../python/sdk/pipenv/PipEnvPipFileWatcher.kt | 7 +- .../statistics/notificationIdHolders.kt | 138 ++++++++++++++++++ 14 files changed, 279 insertions(+), 49 deletions(-) create mode 100644 python/src/com/jetbrains/python/statistics/notificationIdHolders.kt diff --git a/python/pluginResources/intellij.python.community.impl.xml b/python/pluginResources/intellij.python.community.impl.xml index 7f0195448b8a..d0aa4c0ff7ad 100644 --- a/python/pluginResources/intellij.python.community.impl.xml +++ b/python/pluginResources/intellij.python.community.impl.xml @@ -249,9 +249,11 @@ - + implementationClass="com.jetbrains.python.inspections.PyPandasTruthValueIsAmbiguousInspection" + groupKey="INSP.GROUP.pandas"/> @@ -517,9 +519,6 @@ - - @@ -557,27 +556,44 @@ description="Use project location as a working directory for the packaging tool execution."/> + + + + + + + + + + + + + + + - + + - + + + + + @@ -879,23 +897,28 @@ - - - + + - - + + - + - + diff --git a/python/src/com/jetbrains/python/black/BlackFormatterAdvertiserService.kt b/python/src/com/jetbrains/python/black/BlackFormatterAdvertiserService.kt index e2f37a1e4ae6..9c31fee54e44 100644 --- a/python/src/com/jetbrains/python/black/BlackFormatterAdvertiserService.kt +++ b/python/src/com/jetbrains/python/black/BlackFormatterAdvertiserService.kt @@ -16,6 +16,7 @@ import com.jetbrains.python.PyBundle import com.jetbrains.python.black.configuration.BlackFormatterConfigurable import com.jetbrains.python.black.configuration.BlackFormatterConfiguration import com.jetbrains.python.sdk.pythonSdk +import com.jetbrains.python.statistics.BlackFormatterIntegrationIdsHolder.Companion.BLACK_FORMATTER_SUPPORT import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.Nls import org.jetbrains.annotations.NonNls @@ -69,7 +70,7 @@ class BlackFormatterAdvertiserService private constructor() { val notification = NotificationGroupManager.getInstance().getNotificationGroup(BlackFormattingService.NOTIFICATION_GROUP_ID) .createNotification(PyBundle.message("black.advertising.service.notification.title"), message, NotificationType.INFORMATION) - .setDisplayId("black.formatter") + .setDisplayId(BLACK_FORMATTER_SUPPORT) .setSuggestionType(true) .setImportantSuggestion(true) .addAction(NotificationAction diff --git a/python/src/com/jetbrains/python/debugger/PyCythonExtensionWarning.java b/python/src/com/jetbrains/python/debugger/PyCythonExtensionWarning.java index 12c5f95e4e75..ae643143c8db 100644 --- a/python/src/com/jetbrains/python/debugger/PyCythonExtensionWarning.java +++ b/python/src/com/jetbrains/python/debugger/PyCythonExtensionWarning.java @@ -42,6 +42,8 @@ import java.io.File; import java.io.IOException; import java.util.*; +import static com.jetbrains.python.statistics.CythonWarningIdsHolder.CYTHON_EXTENSION_SPEEDS_UP_PYTHON_DEBUGGING; + public final class PyCythonExtensionWarning { private static final Logger LOG = Logger.getInstance(PyCythonExtensionWarning.class); @@ -56,7 +58,7 @@ public final class PyCythonExtensionWarning { Notification notification = new Notification(CYTHON_WARNING_GROUP_ID, PyBundle.message("compile.cython.extensions.notification"), PyBundle.message("debugger.cython.extension.speeds.up.python.debugging"), - NotificationType.INFORMATION); + NotificationType.INFORMATION).setDisplayId(CYTHON_EXTENSION_SPEEDS_UP_PYTHON_DEBUGGING); notification.setSuggestionType(true); notification.setListener(NotificationListener.URL_OPENING_LISTENER); diff --git a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java index c28f8041fd1b..3588d604c5b0 100644 --- a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java +++ b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java @@ -86,6 +86,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import static com.jetbrains.python.debugger.variablesview.usertyperenderers.ConfigureTypeRenderersActionKt.getTypeRenderer; import static com.jetbrains.python.debugger.variablesview.usertyperenderers.ConfigureTypeRenderersActionKt.loadTypeRendererChildren; +import static com.jetbrains.python.statistics.PythonDebuggerIdsHolder.CONNECTION_FAILED; public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, ProcessListener { @@ -366,6 +367,7 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr if (shouldLogConnectionException(e)) { getNotificationGroup() .createNotification(PyBundle.message("debug.notification.title.connection.failed"), e.getMessage(), NotificationType.ERROR) + .setDisplayId(CONNECTION_FAILED) .notify(myProject); } } diff --git a/python/src/com/jetbrains/python/inspections/PyCompatibilityInspectionAdvertiser.java b/python/src/com/jetbrains/python/inspections/PyCompatibilityInspectionAdvertiser.java index c9cec907aff9..beef704ba049 100644 --- a/python/src/com/jetbrains/python/inspections/PyCompatibilityInspectionAdvertiser.java +++ b/python/src/com/jetbrains/python/inspections/PyCompatibilityInspectionAdvertiser.java @@ -39,6 +39,8 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import static com.jetbrains.python.statistics.PythonCompatibilityInspectionAdvertiserIdsHolder.*; + /** * @author Mikhail Golubev */ @@ -79,11 +81,11 @@ public final class PyCompatibilityInspectionAdvertiser implements Annotator { } else if (containsFutureImports(pyFile)) { showSingletonNotification( - project, PyBundle.message("python.compatibility.inspection.advertiser.using.future.imports.warning.message")); + project, PyBundle.message("python.compatibility.inspection.advertiser.using.future.imports.warning.message"), USING_FUTURE_IMPORTS); } else if (PyPsiUtils.containsImport(pyFile, "six")) { showSingletonNotification( - project, PyBundle.message("python.compatibility.inspection.advertiser.using.six.warning.message")); + project, PyBundle.message("python.compatibility.inspection.advertiser.using.six.warning.message"), USING_SIX_PACKAGE); } } } @@ -115,6 +117,7 @@ public final class PyCompatibilityInspectionAdvertiser implements Annotator { project, PyBundle.message("python.compatibility.inspection.advertiser.notifications.title"), message, + STALE_PYTHON_VERSION, (notification, event) -> { final boolean enabled = "#yes".equals(event.getDescription()); if (enabled) { @@ -153,11 +156,12 @@ public final class PyCompatibilityInspectionAdvertiser implements Annotator { } } - private static void showSingletonNotification(@NotNull Project project, @NotificationContent String msg) { + private static void showSingletonNotification(@NotNull Project project, @NotificationContent String msg, @NotNull String displayId) { showSingletonNotification( project, PyBundle.message("python.compatibility.inspection.advertiser.notifications.title"), msg, + displayId, (notification, event) -> { final boolean enabled = "#yes".equals(event.getDescription()); if (enabled) { @@ -172,10 +176,12 @@ public final class PyCompatibilityInspectionAdvertiser implements Annotator { private static void showSingletonNotification(@NotNull Project project, @NotNull @NotificationTitle String title, @NotNull @NotificationContent String htmlContent, + @NotNull String displayId, @NotNull NotificationListener listener) { project.putUserData(DONT_SHOW_BALLOON, true); NotificationGroupManager.getInstance().getNotificationGroup("Python Compatibility Inspection Advertiser") .createNotification(title, htmlContent, NotificationType.INFORMATION) + .setDisplayId(displayId) .setSuggestionType(true) .setListener((notification, event) -> { try { @@ -214,7 +220,8 @@ public final class PyCompatibilityInspectionAdvertiser implements Annotator { private static @Nullable LanguageLevel getLatestConfiguredCompatiblePythonVersion(@NotNull PsiElement anchor) { final InspectionProfile profile = InspectionProfileManager.getInstance(anchor.getProject()).getCurrentProfile(); - final PyCompatibilityInspection inspection = (PyCompatibilityInspection)profile.getUnwrappedTool(getCompatibilityInspectionShortName(), anchor); + final PyCompatibilityInspection inspection = + (PyCompatibilityInspection)profile.getUnwrappedTool(getCompatibilityInspectionShortName(), anchor); assert inspection != null; final JDOMExternalizableStringList versions = inspection.ourVersions; if (versions.isEmpty()) { diff --git a/python/src/com/jetbrains/python/inspections/quickfix/IgnoreRequirementFix.kt b/python/src/com/jetbrains/python/inspections/quickfix/IgnoreRequirementFix.kt index 96d2f6088a75..db05925a3f77 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/IgnoreRequirementFix.kt +++ b/python/src/com/jetbrains/python/inspections/quickfix/IgnoreRequirementFix.kt @@ -16,6 +16,7 @@ import com.intellij.profile.codeInspection.ProjectInspectionProfileManager import com.jetbrains.python.PyBundle import com.jetbrains.python.PyPsiBundle import com.jetbrains.python.inspections.PyPackageRequirementsInspection +import com.jetbrains.python.statistics.PackageRequirementsIdsHolder.Companion.REQUIREMENTS_HAVE_BEEN_IGNORED import org.jetbrains.annotations.Nls internal class IgnoreRequirementFix(private val packagesToIgnore: Set) : LocalQuickFix { @@ -38,7 +39,9 @@ internal class IgnoreRequirementFix(private val packagesToIgnore: Set) : else -> PyPsiBundle.message("INSP.package.requirements.requirements.have.been.ignored") } - val notification = BALLOON_NOTIFICATIONS.createNotification(notificationMessage, NotificationType.INFORMATION) + val notification = BALLOON_NOTIFICATIONS + .createNotification(notificationMessage, NotificationType.INFORMATION) + .setDisplayId(REQUIREMENTS_HAVE_BEEN_IGNORED) notification.addAction(createUndoAction(inspection, packagesToIgnore, profileManager)) notification.addAction(createEditSettingsAction(project)) notification.notify(project) diff --git a/python/src/com/jetbrains/python/packaging/PyRequirementsTxtUtil.kt b/python/src/com/jetbrains/python/packaging/PyRequirementsTxtUtil.kt index 151e718d6cde..d923e5ba5d26 100644 --- a/python/src/com/jetbrains/python/packaging/PyRequirementsTxtUtil.kt +++ b/python/src/com/jetbrains/python/packaging/PyRequirementsTxtUtil.kt @@ -36,6 +36,10 @@ import com.jetbrains.python.psi.PyFile import com.jetbrains.python.sdk.PySdkPopupFactory import com.jetbrains.python.sdk.PythonSdkAdditionalData import com.jetbrains.python.sdk.PythonSdkUtil +import com.jetbrains.python.statistics.SyncPythonRequirementsIdsHolder.Companion.ANALYZE_ENTRIES_IN_REQUIREMENTS_FILE_FAILED +import com.jetbrains.python.statistics.SyncPythonRequirementsIdsHolder.Companion.CREATE_REQUIREMENTS_FILE_FAILED +import com.jetbrains.python.statistics.SyncPythonRequirementsIdsHolder.Companion.NO_INTERPRETER_CONFIGURED +import com.jetbrains.python.statistics.SyncPythonRequirementsIdsHolder.Companion.SOME_REQUIREMENTS_FROM_BASE_FILES_WERE_NOT_UPDATED import com.jetbrains.python.util.runWithModalBlockingOrInBackground import org.jetbrains.annotations.ApiStatus import java.util.* @@ -101,11 +105,14 @@ internal fun syncWithImports(module: Module) { val configureSdkAction = NotificationAction.createSimpleExpiring(PySdkBundle.message("python.configure.interpreter.action")) { PySdkPopupFactory.createAndShow(module) } - showNotification(notificationGroup, - NotificationType.ERROR, - PyBundle.message("python.requirements.error.no.interpreter"), - module.project, - configureSdkAction) + showNotification( + notificationGroup = notificationGroup, + type = NotificationType.ERROR, + text = PyBundle.message("python.requirements.error.no.interpreter"), + project = module.project, + action = configureSdkAction, + displayId = NO_INTERPRETER_CONFIGURED + ) return } val settings = PyPackageRequirementsSettings.getInstance(module) @@ -121,7 +128,13 @@ internal fun syncWithImports(module: Module) { if (requirementsFile == null) { val text = PyBundle.message("python.requirements.error.create.requirements.file") - showNotification(notificationGroup, NotificationType.WARNING, text, module.project) + showNotification( + notificationGroup = notificationGroup, + type = NotificationType.WARNING, + text = text, + project = module.project, + displayId = CREATE_REQUIREMENTS_FILE_FAILED + ) return } @@ -139,11 +152,23 @@ internal fun syncWithImports(module: Module) { if (matchResult.unhandledLines.isNotEmpty()) { val text = PyBundle.message("python.requirements.warning.unhandled.lines", matchResult.unhandledLines.joinToString(", ")) - showNotification(notificationGroup, NotificationType.WARNING, text, module.project) + showNotification( + notificationGroup = notificationGroup, + type = NotificationType.WARNING, + text = text, + project = module.project, + displayId = ANALYZE_ENTRIES_IN_REQUIREMENTS_FILE_FAILED + ) } if (matchResult.unchangedInBaseFiles.isNotEmpty()) { val text = PyBundle.message("python.requirements.info.file.ref.dropped", matchResult.unchangedInBaseFiles.joinToString(", ")) - showNotification(notificationGroup, NotificationType.INFORMATION, text, module.project) + showNotification( + notificationGroup = notificationGroup, + type = NotificationType.INFORMATION, + text = text, + project = module.project, + displayId = SOME_REQUIREMENTS_FROM_BASE_FILES_WERE_NOT_UPDATED + ) } } @@ -153,8 +178,9 @@ private fun showNotification( @NlsContexts.NotificationContent text: String, project: Project, action: NotificationAction? = null, + displayId: String, ) { - val notification = notificationGroup.createNotification(PyBundle.message("python.requirements.balloon"), text, type) + val notification = notificationGroup.createNotification(PyBundle.message("python.requirements.balloon"), text, type).setDisplayId(displayId) if (action != null) notification.addAction(action) notification.notify(project) } diff --git a/python/src/com/jetbrains/python/packaging/toolwindow/PyPackagingToolWindowService.kt b/python/src/com/jetbrains/python/packaging/toolwindow/PyPackagingToolWindowService.kt index 07029ec3854d..7efeba5c5833 100644 --- a/python/src/com/jetbrains/python/packaging/toolwindow/PyPackagingToolWindowService.kt +++ b/python/src/com/jetbrains/python/packaging/toolwindow/PyPackagingToolWindowService.kt @@ -37,6 +37,8 @@ import com.jetbrains.python.packaging.statistics.PythonPackagesToolwindowStatist import com.jetbrains.python.packaging.toolwindow.model.* import com.jetbrains.python.sdk.PythonSdkUtil import com.jetbrains.python.sdk.pythonSdk +import com.jetbrains.python.statistics.PythonPackagesIdsHolder.Companion.PYTHON_PACKAGE_DELETED +import com.jetbrains.python.statistics.PythonPackagesIdsHolder.Companion.PYTHON_PACKAGE_INSTALLED import kotlinx.coroutines.* import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.Nls @@ -192,7 +194,10 @@ class PyPackagingToolWindowService(val project: Project, val serviceScope: Corou suspend fun installPackage(installRequest: PythonPackageInstallRequest, options: List = emptyList()) { PythonPackagesToolwindowStatisticsCollector.installPackageEvent.log(project) managerUI.installPackagesRequestBackground(installRequest, options)?.let { - handleActionCompleted(message("python.packaging.notification.installed", installRequest.title)) + handleActionCompleted( + text = message("python.packaging.notification.installed", installRequest.title), + displayId = PYTHON_PACKAGE_INSTALLED + ) } } @@ -200,14 +205,20 @@ class PyPackagingToolWindowService(val project: Project, val serviceScope: Corou val installRequest = manager?.findPackageSpecification(pkg.name, pkg.version)?.toInstallRequest() ?: return PythonPackagesToolwindowStatisticsCollector.installPackageEvent.log(project) managerUI.installPackagesRequestBackground(installRequest, options)?.let { - handleActionCompleted(message("python.packaging.notification.installed", installRequest.title)) + handleActionCompleted( + text = message("python.packaging.notification.installed", installRequest.title), + displayId = PYTHON_PACKAGE_INSTALLED + ) } } suspend fun deletePackage(vararg selectedPackages: InstalledPackage) { PythonPackagesToolwindowStatisticsCollector.uninstallPackageEvent.log(project) managerUI.uninstallPackagesBackground(selectedPackages.map { it.instance.name }) ?: return - handleActionCompleted(message("python.packaging.notification.deleted", selectedPackages.joinToString(", ") { it.name })) + handleActionCompleted( + text = message("python.packaging.notification.deleted", selectedPackages.joinToString(", ") { it.name }), + displayId = PYTHON_PACKAGE_DELETED + ) } @ApiStatus.Internal @@ -379,15 +390,16 @@ class PyPackagingToolWindowService(val project: Project, val serviceScope: Corou } } - private suspend fun handleActionCompleted(text: @Nls String) { + private suspend fun handleActionCompleted(text: @Nls String, displayId: String) { VirtualFileManager.getInstance().asyncRefresh() - showPackagingNotification(text) + showPackagingNotification(text, displayId) } - private suspend fun showPackagingNotification(text: @Nls String) { + private suspend fun showPackagingNotification(text: @Nls String, displayId: String) { val notification = NotificationGroupManager.getInstance() .getNotificationGroup("PythonPackages") .createNotification(text, NotificationType.INFORMATION) + .setDisplayId(displayId) withContext(Dispatchers.Main) { notification.notify(project) diff --git a/python/src/com/jetbrains/python/sdk/PythonSdkType.java b/python/src/com/jetbrains/python/sdk/PythonSdkType.java index 88148fbd8b75..0df4d9b7885a 100644 --- a/python/src/com/jetbrains/python/sdk/PythonSdkType.java +++ b/python/src/com/jetbrains/python/sdk/PythonSdkType.java @@ -62,6 +62,7 @@ import java.util.List; import java.util.concurrent.atomic.AtomicReference; import static com.intellij.execution.target.TargetBasedSdks.loadTargetConfiguration; +import static com.jetbrains.python.statistics.PythonSDKUpdaterIdsHolder.REFRESH_SKELETONS_FOR_REMOTE_INTERPRETER_FAILED; /** * Class should be final and singleton since some code checks its instance by ref. @@ -384,7 +385,7 @@ public final class PythonSdkType extends SdkType { Notification notification = new Notification("Python SDK Updater", PyBundle.message("sdk.gen.failed.notification.title"), notificationMessage, - NotificationType.WARNING); + NotificationType.WARNING).setDisplayId(REFRESH_SKELETONS_FOR_REMOTE_INTERPRETER_FAILED); if (notificationListener != null) notification.setListener(notificationListener); notification.notify(null); } diff --git a/python/src/com/jetbrains/python/sdk/PythonSdkUpdater.java b/python/src/com/jetbrains/python/sdk/PythonSdkUpdater.java index cefc8942b067..92b69faecd3f 100644 --- a/python/src/com/jetbrains/python/sdk/PythonSdkUpdater.java +++ b/python/src/com/jetbrains/python/sdk/PythonSdkUpdater.java @@ -64,6 +64,9 @@ import java.util.*; import java.util.List; import java.util.function.Function; +import static com.jetbrains.python.statistics.PythonSDKUpdaterIdsHolder.REFRESH_SKELETONS_FOR_REMOTE_INTERPRETER_FAILED; +import static com.jetbrains.python.statistics.PythonSDKUpdaterIdsHolder.REMOTE_INTERPRETER_SUPPORT_IS_NOT_AVAILABLE; + /** * Refreshes all project's Python SDKs. */ @@ -403,6 +406,7 @@ public final class PythonSdkUpdater { .createNotification(PyBundle.message("sdk.gen.failed.notification.title"), PyBundle.message("remote.interpreter.support.is.not.available", sdk.getName()), NotificationType.WARNING) + .setDisplayId(REMOTE_INTERPRETER_SUPPORT_IS_NOT_AVAILABLE) .notify(myProject); } else if (exception instanceof InvalidSdkException) { diff --git a/python/src/com/jetbrains/python/sdk/add/v2/common.kt b/python/src/com/jetbrains/python/sdk/add/v2/common.kt index f1aa28b76378..6e91e7d143ab 100644 --- a/python/src/com/jetbrains/python/sdk/add/v2/common.kt +++ b/python/src/com/jetbrains/python/sdk/add/v2/common.kt @@ -37,6 +37,7 @@ import com.jetbrains.python.sdk.pipenv.PIPENV_ICON import com.jetbrains.python.sdk.poetry.POETRY_ICON import com.jetbrains.python.sdk.uv.UV_ICON import com.jetbrains.python.statistics.InterpreterTarget +import com.jetbrains.python.statistics.PythonInterpreterInstallationIdsHolder.Companion.PYTHON_INSTALLATION_INTERRUPTED import kotlinx.coroutines.CoroutineScope import org.jetbrains.annotations.ApiStatus import javax.swing.Icon @@ -157,6 +158,7 @@ internal fun installBaseSdk(sdk: Sdk, existingSdks: List): Sdk? { val notification = NotificationGroupManager.getInstance() .getNotificationGroup("Python interpreter installation") .createNotification(message("python.sdk.installation.balloon.error.message"), NotificationType.ERROR) + .setDisplayId(PYTHON_INSTALLATION_INTERRUPTED) notification.collapseDirection notification.addAction(NotificationAction.createSimple(message("python.sdk.installation.balloon.error.action")) { diff --git a/python/src/com/jetbrains/python/sdk/configuration/PyProjectSdkConfiguration.kt b/python/src/com/jetbrains/python/sdk/configuration/PyProjectSdkConfiguration.kt index 8fb10a6e8143..d9432f99d44f 100644 --- a/python/src/com/jetbrains/python/sdk/configuration/PyProjectSdkConfiguration.kt +++ b/python/src/com/jetbrains/python/sdk/configuration/PyProjectSdkConfiguration.kt @@ -28,6 +28,7 @@ import com.jetbrains.python.sdk.configuration.suppressors.PyPackageRequirementsI import com.jetbrains.python.sdk.configuration.suppressors.TipOfTheDaySuppressor import com.jetbrains.python.sdk.configurePythonSdk import com.jetbrains.python.sdk.uv.isUv +import com.jetbrains.python.statistics.ConfiguredPythonInterpreterIdsHolder.Companion.SDK_HAS_BEEN_CONFIGURED_AS_THE_PROJECT_INTERPRETER import com.jetbrains.python.util.ShowingMessageErrorSync import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -108,16 +109,19 @@ object PyProjectSdkConfiguration { private fun notifyAboutConfiguredSdk(project: Project, module: Module, sdk: Sdk) { if (isNotificationSilentMode(project)) return - NotificationGroupManager.getInstance().getNotificationGroup("ConfiguredPythonInterpreter").createNotification( - PyBundle.message("sdk.has.been.configured.as.the.project.interpreter", sdk.name), - NotificationType.INFORMATION - ).apply { - val configureSdkAction = NotificationAction.createSimpleExpiring(PySdkBundle.message("python.configure.interpreter.action")) { - PySdkPopupFactory.createAndShow(module) - } + NotificationGroupManager.getInstance().getNotificationGroup("ConfiguredPythonInterpreter") + .createNotification( + content = PyBundle.message("sdk.has.been.configured.as.the.project.interpreter", sdk.name), + type = NotificationType.INFORMATION + ) + .setDisplayId(SDK_HAS_BEEN_CONFIGURED_AS_THE_PROJECT_INTERPRETER) + .apply { + val configureSdkAction = NotificationAction.createSimpleExpiring(PySdkBundle.message("python.configure.interpreter.action")) { + PySdkPopupFactory.createAndShow(module) + } - addAction(configureSdkAction) - notify(project) - } + addAction(configureSdkAction) + notify(project) + } } } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/sdk/pipenv/PipEnvPipFileWatcher.kt b/python/src/com/jetbrains/python/sdk/pipenv/PipEnvPipFileWatcher.kt index 68ce014e1fab..6b16ea607eee 100644 --- a/python/src/com/jetbrains/python/sdk/pipenv/PipEnvPipFileWatcher.kt +++ b/python/src/com/jetbrains/python/sdk/pipenv/PipEnvPipFileWatcher.kt @@ -25,6 +25,7 @@ import com.jetbrains.python.PyBundle import com.jetbrains.python.onFailure import com.jetbrains.python.packaging.utils.PyPackageCoroutine import com.jetbrains.python.sdk.* +import com.jetbrains.python.statistics.PipfileWatcherIdsHolder.Companion.RUN_PIPENV_LOCK_SUGGESTION import com.jetbrains.python.util.ShowingMessageErrorSync import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -77,7 +78,11 @@ internal class PipEnvPipFileWatcher : EditorFactoryListener { } val content = PyBundle.message("python.sdk.pipenv.pip.file.notification.content") val notification = withContext(Dispatchers.EDT) { - LOCK_NOTIFICATION_GROUP.createNotification(title, content, NotificationType.INFORMATION) + LOCK_NOTIFICATION_GROUP.createNotification( + title = title, + content = content, + type = NotificationType.INFORMATION, + ).setDisplayId(RUN_PIPENV_LOCK_SUGGESTION) } .setListener(NotificationListener { notification, event -> notification.expire() diff --git a/python/src/com/jetbrains/python/statistics/notificationIdHolders.kt b/python/src/com/jetbrains/python/statistics/notificationIdHolders.kt new file mode 100644 index 000000000000..fd6a0671202b --- /dev/null +++ b/python/src/com/jetbrains/python/statistics/notificationIdHolders.kt @@ -0,0 +1,138 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.jetbrains.python.statistics + +import com.intellij.notification.impl.NotificationIdsHolder +import org.jetbrains.annotations.ApiStatus + +internal class ConfiguredPythonInterpreterIdsHolder : NotificationIdsHolder { + override fun getNotificationIds(): List = listOf( + SDK_HAS_BEEN_CONFIGURED_AS_THE_PROJECT_INTERPRETER, + ) + + companion object { + const val SDK_HAS_BEEN_CONFIGURED_AS_THE_PROJECT_INTERPRETER = "sdk.has.been.configured.as.the.project.interpreter" + } +} + +internal class PythonPackagesIdsHolder : NotificationIdsHolder { + override fun getNotificationIds(): List = listOf( + PYTHON_PACKAGE_INSTALLED, + PYTHON_PACKAGE_DELETED, + ) + + companion object { + const val PYTHON_PACKAGE_INSTALLED = "python.package.installed" + const val PYTHON_PACKAGE_DELETED = "python.package.deleted" + } +} + +@ApiStatus.Internal +class PythonDebuggerIdsHolder : NotificationIdsHolder { + override fun getNotificationIds(): List = listOf( + CONNECTION_FAILED, + JUPYTER_CELL_EDITED_BETWEEN_SESSIONS, + JUPYTER_CELL_EDITED_DURING_SESSIONS, + JUPYTER_OTHER_CELL_UNDER_DEBUGGER, + ) + + companion object { + const val CONNECTION_FAILED: String = "connection.failed" + const val JUPYTER_CELL_EDITED_BETWEEN_SESSIONS: String = "jupyter.cell.edited.between.sessions" + const val JUPYTER_CELL_EDITED_DURING_SESSIONS: String = "jupyter.cell.edited.during.sessions" + const val JUPYTER_OTHER_CELL_UNDER_DEBUGGER: String = "jupyter.other.cell.under.debugger" + } +} + + +internal class PythonCompatibilityInspectionAdvertiserIdsHolder : NotificationIdsHolder { + override fun getNotificationIds(): List = listOf( + STALE_PYTHON_VERSION, + USING_FUTURE_IMPORTS, + USING_SIX_PACKAGE, + ) + + companion object { + const val STALE_PYTHON_VERSION = "stale.python.version" + const val USING_FUTURE_IMPORTS = "using.future.imports" + const val USING_SIX_PACKAGE = "using.six.package" + } +} + + +internal class CythonWarningIdsHolder : NotificationIdsHolder { + override fun getNotificationIds(): List = listOf( + CYTHON_EXTENSION_SPEEDS_UP_PYTHON_DEBUGGING, + ) + + companion object { + const val CYTHON_EXTENSION_SPEEDS_UP_PYTHON_DEBUGGING = "cython.extension.speeds.up.python.debugging" + } +} + +internal class PackageRequirementsIdsHolder : NotificationIdsHolder { + override fun getNotificationIds(): List = listOf( + REQUIREMENTS_HAVE_BEEN_IGNORED, + ) + + companion object { + const val REQUIREMENTS_HAVE_BEEN_IGNORED = "requirements.have.been.ignored" + } +} + +internal class BlackFormatterIntegrationIdsHolder : NotificationIdsHolder { + override fun getNotificationIds(): List = listOf( + BLACK_FORMATTER_SUPPORT, + ) + + companion object { + const val BLACK_FORMATTER_SUPPORT = "black.formatter.support" + } +} + +internal class PythonSDKUpdaterIdsHolder : NotificationIdsHolder { + override fun getNotificationIds(): List = listOf( + REFRESH_SKELETONS_FOR_REMOTE_INTERPRETER_FAILED, + REMOTE_INTERPRETER_SUPPORT_IS_NOT_AVAILABLE, + ) + + companion object { + const val REFRESH_SKELETONS_FOR_REMOTE_INTERPRETER_FAILED = "refresh.skeletons.for.remote.interpreter.failed" + const val REMOTE_INTERPRETER_SUPPORT_IS_NOT_AVAILABLE = "remote.interpreter.support.is.not.available" + } +} + +internal class SyncPythonRequirementsIdsHolder : NotificationIdsHolder { + override fun getNotificationIds(): List = listOf( + NO_INTERPRETER_CONFIGURED, + CREATE_REQUIREMENTS_FILE_FAILED, + ANALYZE_ENTRIES_IN_REQUIREMENTS_FILE_FAILED, + SOME_REQUIREMENTS_FROM_BASE_FILES_WERE_NOT_UPDATED, + ) + + companion object { + const val NO_INTERPRETER_CONFIGURED = "no.interpreter.configured" + const val CREATE_REQUIREMENTS_FILE_FAILED = "create.requirements.file.failed" + const val ANALYZE_ENTRIES_IN_REQUIREMENTS_FILE_FAILED = "analyze.entries.in.requirements.file.failed" + const val SOME_REQUIREMENTS_FROM_BASE_FILES_WERE_NOT_UPDATED = "some.requirements.from.base.files.were.not.updated" + } +} + +internal class PythonInterpreterInstallationIdsHolder : NotificationIdsHolder { + override fun getNotificationIds(): List = listOf( + PYTHON_INSTALLATION_INTERRUPTED, + ) + + companion object { + const val PYTHON_INSTALLATION_INTERRUPTED = "python.installation.interrupted" + } +} + +internal class PipfileWatcherIdsHolder : NotificationIdsHolder { + override fun getNotificationIds(): List = listOf( + RUN_PIPENV_LOCK_SUGGESTION, + ) + + companion object { + const val RUN_PIPENV_LOCK_SUGGESTION = "run.pipenv.lock.suggestion" + } +}