PY-84846 Log Python "Run with" tool usage.

(cherry picked from commit ee13b27a80b759d2c12169a30499ba8dd39a03ea)

IJ-MR-178767

GitOrigin-RevId: a1c87d6cb426ef195c4fbf1c60cf01a56547ffad
This commit is contained in:
Timur Malanin
2025-10-20 20:19:30 +00:00
committed by intellij-monorepo-bot
parent 211b4ce2cb
commit ea39ed6ad9
5 changed files with 63 additions and 2 deletions
@@ -78,6 +78,8 @@
<postStartupActivity implementation="com.jetbrains.python.poetry.PoetryPyProjectTomlPostStartupActivity"/>
<virtualFileCustomDataProvider implementation="com.jetbrains.python.psi.PyLangLevelVirtualFileCustomDataProvider"/>
<statistics.counterUsagesCollector implementationClass="com.jetbrains.python.run.features.PyRunToolUsageCollector"/>
<statistics.validation.customValidationRule implementation="com.jetbrains.python.run.features.PyRunToolIdValidator"/>
</extensions>
<extensions defaultExtensionNs="com.intellij.python.pyproject.model">
@@ -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));
}
}
@@ -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
)
/**
@@ -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<String?> = 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
}
}
@@ -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"),
)