From ea39ed6ad9c0ff1b73ee03d153df5d78ad738407 Mon Sep 17 00:00:00 2001 From: Timur Malanin Date: Mon, 20 Oct 2025 16:16:50 +0000 Subject: [PATCH] PY-84846 Log Python "Run with" tool usage. (cherry picked from commit ee13b27a80b759d2c12169a30499ba8dd39a03ea) IJ-MR-178767 GitOrigin-RevId: a1c87d6cb426ef195c4fbf1c60cf01a56547ffad --- .../intellij.python.community.impl.xml | 2 + .../python/run/PythonCommandLineState.java | 3 ++ .../python/run/features/PyRunToolProvider.kt | 9 +++- .../run/features/PyRunToolUsageCollector.kt | 49 +++++++++++++++++++ .../python/sdk/uv/run/UvRunToolProvider.kt | 2 +- 5 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 python/src/com/jetbrains/python/run/features/PyRunToolUsageCollector.kt diff --git a/python/pluginResources/intellij.python.community.impl.xml b/python/pluginResources/intellij.python.community.impl.xml index 960d33d9813f..87b2c43ca45d 100644 --- a/python/pluginResources/intellij.python.community.impl.xml +++ b/python/pluginResources/intellij.python.community.impl.xml @@ -78,6 +78,8 @@ + + diff --git a/python/src/com/jetbrains/python/run/PythonCommandLineState.java b/python/src/com/jetbrains/python/run/PythonCommandLineState.java index c9cf0f2246a4..8dd3893a8e91 100644 --- a/python/src/com/jetbrains/python/run/PythonCommandLineState.java +++ b/python/src/com/jetbrains/python/run/PythonCommandLineState.java @@ -67,8 +67,10 @@ import com.jetbrains.python.library.PythonLibraryType; import com.jetbrains.python.packaging.PyExecutionException; import com.jetbrains.python.remote.PyRemotePathMapper; import com.jetbrains.python.remote.PyRemoteSdkAdditionalData; +import com.jetbrains.python.run.features.PyRunToolIds; import com.jetbrains.python.run.features.PyRunToolParameters; import com.jetbrains.python.run.features.PyRunToolProvider; +import com.jetbrains.python.run.features.PyRunToolUsageCollector; import com.jetbrains.python.run.target.HelpersAwareTargetEnvironmentRequest; import com.jetbrains.python.run.target.PySdkTargetPaths; import com.jetbrains.python.run.target.PythonCommandLineTargetEnvironmentProvider; @@ -354,6 +356,7 @@ public abstract class PythonCommandLineState extends CommandLineState { PyRunToolProvider runToolProvider = PyRunToolProvider.forSdk(sdk); if (runToolProvider != null && useRunTool(myConfig, sdk)) { runToolParameters = runToolProvider.getRunToolParameters(); + PyRunToolUsageCollector.logRun(myConfig.getProject(), PyRunToolIds.idOf(runToolProvider)); } } diff --git a/python/src/com/jetbrains/python/run/features/PyRunToolProvider.kt b/python/src/com/jetbrains/python/run/features/PyRunToolProvider.kt index b990d0c81b37..05af09be6645 100644 --- a/python/src/com/jetbrains/python/run/features/PyRunToolProvider.kt +++ b/python/src/com/jetbrains/python/run/features/PyRunToolProvider.kt @@ -11,18 +11,25 @@ import org.jetbrains.annotations.NonNls @ApiStatus.Internal value class PyRunToolId(@param:NonNls val value: String) +internal object PyRunToolIds { + @JvmStatic + fun idOf(provider: PyRunToolProvider): String = provider.runToolData.id.value +} + /** * Metadata describing a concrete Python "Run with …" tool option. * * - id: a unique identifier of the tool. * - name: user‑visible action/configuration name (localized). * - group: user‑visible group name under which the action is shown in UI (localized). + * - idForStatistics: identifier for FUS statistics (should not change to avoid breaking statistics). */ @ApiStatus.Internal data class PyRunToolData( @param:NonNls val id: PyRunToolId, @param:Nls val name: String, - @param:Nls val group: String + @param:Nls val group: String, + @param:NonNls val idForStatistics: String = id.value ) /** diff --git a/python/src/com/jetbrains/python/run/features/PyRunToolUsageCollector.kt b/python/src/com/jetbrains/python/run/features/PyRunToolUsageCollector.kt new file mode 100644 index 000000000000..db384ad3810d --- /dev/null +++ b/python/src/com/jetbrains/python/run/features/PyRunToolUsageCollector.kt @@ -0,0 +1,49 @@ +// 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.run.features + +import com.intellij.internal.statistic.eventLog.EventLogGroup +import com.intellij.internal.statistic.eventLog.events.EventFields +import com.intellij.internal.statistic.eventLog.events.EventId1 +import com.intellij.internal.statistic.eventLog.validator.ValidationResultType +import com.intellij.internal.statistic.eventLog.validator.rules.EventContext +import com.intellij.internal.statistic.eventLog.validator.rules.impl.CustomValidationRule +import com.intellij.internal.statistic.service.fus.collectors.CounterUsagesCollector +import com.intellij.internal.statistic.utils.getPluginInfo +import com.intellij.openapi.project.Project + +/** + * FUS collector for "Run with …" Python tools exposed via [PyRunToolProvider]. + * Logs how many runs are started via a run tool and the tool id. + */ +internal object PyRunToolUsageCollector : CounterUsagesCollector() { + private val GROUP = EventLogGroup("python.run.tool", 1, "FUS", "Tracks Python run configurations executed with specific run tools (e.g., uv run)") + + private val TOOL_ID = EventFields.StringValidatedByCustomRule("run_tool_id", PyRunToolIdValidator::class.java) + + private val RUN_USED: EventId1 = GROUP.registerEvent("run.used", TOOL_ID, "Execution of Python run configurations with specific run tool") + + @JvmStatic + fun logRun(project: Project, runToolId: String) { + RUN_USED.log(project, runToolId) + } + + override fun getGroup(): EventLogGroup = GROUP +} + +/** + * Custom validator for PyRunTool IDs in FUS statistics. + * Validates that the ID corresponds to a known PyRunToolProvider. + */ +internal class PyRunToolIdValidator : CustomValidationRule() { + override fun getRuleId(): String = "python_run_tool" + + override fun doValidate(data: String, context: EventContext): ValidationResultType { + val validIds = PyRunToolProvider.EP.extensionList + .asSequence() + .filter { getPluginInfo(it.javaClass).isDevelopedByJetBrains() } + .map { it.runToolData.idForStatistics } + .toSet() + + return if (data in validIds) ValidationResultType.ACCEPTED else ValidationResultType.REJECTED + } +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/sdk/uv/run/UvRunToolProvider.kt b/python/src/com/jetbrains/python/sdk/uv/run/UvRunToolProvider.kt index 4d6f8bd8b1db..10b0614206a3 100644 --- a/python/src/com/jetbrains/python/sdk/uv/run/UvRunToolProvider.kt +++ b/python/src/com/jetbrains/python/sdk/uv/run/UvRunToolProvider.kt @@ -15,7 +15,7 @@ import com.jetbrains.python.sdk.uv.isUv private class UvRunToolProvider : PyRunToolProvider { override val runToolData: PyRunToolData = PyRunToolData( - PyRunToolId("py.run.with.uv"), + PyRunToolId("uv.run"), PyBundle.message("uv.run.configuration.type.display.name"), PyBundle.message("python.run.configuration.fragments.python.group"), )