mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-05 01:50:56 +07:00
ML-based ranking in the plugin manager
(MP-6452) Fix failing project structure and packaging tests (MP-6452) Make sure that machine learning score is calculated correctly for each plugin Previously, there was a cache with mutable list of features. There were two issues with it. First, because of mutability, it could store mlScore for a different query sometimes. Second, there were no guarantees on consequent call of ranking and features logging before the next ranking event happens, so the feature cache could be inconsistent in certain scenarios. (MP-6452) Introduce search index in plugin manager events that corresponds to actual query order I also change the definition of plugin manager session. The session is restarted only in two cases: when a user clicks on the Plugins section in settings or when the search is restarted after a plugin installation. (MP-6452) Add additional features: is result ordered by ML, is user internal, experiment group and version (MP-6452) Fix missing date of latest plugin update, add days since latest updates and textual features (MP-6452) Apply suggestions from the code review (MP-6452) Integrate the ranking plugin with the search and the logging group In feature extractors, during the first run, I calculate the features for a model to predict plugin relevance and on the second run the features to report to the MP collector (with predicted score, for example). I also implement features cache to calculate the most of the features only once (MP-6452) Implement the baseline plugin manager session id definition and log it to FUS and MP recorders I attach the start of a session to enableSearch method of PluginManagerConfigurable that gets triggered on "Plugins" setting group selection in the menu (MP-6452) Add plugin for plugins ranking in the Plugin Manager ML in SE: add embedding search integration test subsystem tag Merge-request: IJ-MR-130364 Merged-by: Evgeny Abramov <Evgeny.Abramov@jetbrains.com> GitOrigin-RevId: 9136d316aec2ede74bec07798dd8db16e7849f54
This commit is contained in:
committed by
intellij-monorepo-bot
parent
d458731be8
commit
cc938f1533
1
.idea/modules.xml
generated
1
.idea/modules.xml
generated
@@ -576,6 +576,7 @@
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/markdown/spellchecker/intellij.markdown.spellchecker.iml" filepath="$PROJECT_DIR$/plugins/markdown/spellchecker/intellij.markdown.spellchecker.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/markdown/test/intellij.markdown.tests.iml" filepath="$PROJECT_DIR$/plugins/markdown/test/intellij.markdown.tests.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/markdown/xml/intellij.markdown.xml.iml" filepath="$PROJECT_DIR$/plugins/markdown/xml/intellij.markdown.xml.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/marketplace-ml/intellij.marketplaceMl.iml" filepath="$PROJECT_DIR$/plugins/marketplace-ml/intellij.marketplaceMl.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/maven/intellij.maven.iml" filepath="$PROJECT_DIR$/plugins/maven/intellij.maven.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/maven/artifact-resolver/common/intellij.maven.artifactResolver.common.iml" filepath="$PROJECT_DIR$/plugins/maven/artifact-resolver/common/intellij.maven.artifactResolver.common.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/plugins/maven/artifact-resolver-m31/intellij.maven.artifactResolver.m31.iml" filepath="$PROJECT_DIR$/plugins/maven/artifact-resolver-m31/intellij.maven.artifactResolver.m31.iml" />
|
||||
|
||||
@@ -178,6 +178,7 @@
|
||||
<orderEntry type="module" module-name="intellij.searchEverywhereMl.semantics.java" scope="RUNTIME" />
|
||||
<orderEntry type="module" module-name="intellij.searchEverywhereMl.semantics.kotlin" scope="RUNTIME" />
|
||||
<orderEntry type="module" module-name="intellij.searchEverywhereMl.semantics.python" scope="RUNTIME" />
|
||||
<orderEntry type="module" module-name="intellij.marketplaceMl" scope="RUNTIME" />
|
||||
<orderEntry type="module" module-name="intellij.toml" scope="RUNTIME" />
|
||||
<orderEntry type="module" module-name="intellij.platform.tracing.ide" scope="RUNTIME" />
|
||||
<orderEntry type="module" module-name="intellij.notebooks.visualization" scope="RUNTIME" />
|
||||
|
||||
@@ -84,6 +84,7 @@ val IDEA_BUNDLED_PLUGINS: PersistentList<String> = DEFAULT_BUNDLED_PLUGINS + per
|
||||
"intellij.grazie",
|
||||
"intellij.featuresTrainer",
|
||||
"intellij.searchEverywhereMl",
|
||||
"intellij.marketplaceMl",
|
||||
"intellij.platform.tracing.ide",
|
||||
"intellij.toml",
|
||||
KotlinPluginBuilder.MAIN_KOTLIN_PLUGIN_MODULE,
|
||||
|
||||
@@ -8172,20 +8172,22 @@ f:com.intellij.ide.plugins.marketplace.statistics.features.PluginManagerMarketpl
|
||||
f:com.intellij.ide.plugins.marketplace.statistics.features.PluginManagerSearchResultFeatureProvider
|
||||
- sf:INSTANCE:com.intellij.ide.plugins.marketplace.statistics.features.PluginManagerSearchResultFeatureProvider
|
||||
- f:getFeaturesDefinition():com.intellij.internal.statistic.eventLog.events.EventField[]
|
||||
- f:getSearchStateFeatures(java.lang.String,com.intellij.ide.plugins.IdeaPluginDescriptor):java.util.List
|
||||
- f:getSearchStateFeatures(java.lang.String,com.intellij.ide.plugins.IdeaPluginDescriptor,java.util.Map):java.util.List
|
||||
- bs:getSearchStateFeatures$default(com.intellij.ide.plugins.marketplace.statistics.features.PluginManagerSearchResultFeatureProvider,java.lang.String,com.intellij.ide.plugins.IdeaPluginDescriptor,java.util.Map,I,java.lang.Object):java.util.List
|
||||
f:com.intellij.ide.plugins.marketplace.statistics.features.PluginManagerSearchResultMarketplaceFeatureProvider
|
||||
- sf:INSTANCE:com.intellij.ide.plugins.marketplace.statistics.features.PluginManagerSearchResultMarketplaceFeatureProvider
|
||||
- f:getFeaturesDefinition():com.intellij.internal.statistic.eventLog.events.EventField[]
|
||||
- f:getSearchStateFeatures(com.intellij.ide.plugins.PluginNode):com.intellij.internal.statistic.eventLog.events.ObjectEventData
|
||||
- f:getSearchStateFeatures(com.intellij.ide.plugins.PluginNode):java.util.List
|
||||
f:com.intellij.ide.plugins.marketplace.statistics.features.PluginManagerSearchResultsFeatureProvider
|
||||
- sf:INSTANCE:com.intellij.ide.plugins.marketplace.statistics.features.PluginManagerSearchResultsFeatureProvider
|
||||
- f:getCommonFeatures(java.lang.String,java.util.List):java.util.ArrayList
|
||||
- f:getFeaturesDefinition():com.intellij.internal.statistic.eventLog.events.EventField[]
|
||||
- f:getSearchStateFeatures(java.lang.String,java.util.List):java.util.ArrayList
|
||||
- f:getSearchStateFeatures(java.lang.String,java.util.List,java.util.Map):java.util.List
|
||||
f:com.intellij.ide.plugins.marketplace.statistics.features.PluginManagerUserQueryFeatureProvider
|
||||
- sf:INSTANCE:com.intellij.ide.plugins.marketplace.statistics.features.PluginManagerUserQueryFeatureProvider
|
||||
- f:getFeaturesDefinition():com.intellij.internal.statistic.eventLog.events.EventField[]
|
||||
- f:getSearchStateFeatures(java.lang.String):java.util.List
|
||||
f:com.intellij.ide.plugins.marketplace.statistics.validators.MarketplaceTagsListValidator
|
||||
f:com.intellij.ide.plugins.marketplace.statistics.validators.MarketplaceTagValidator
|
||||
- com.intellij.internal.statistic.eventLog.validator.rules.FUSRule
|
||||
- com.intellij.internal.statistic.eventLog.validator.rules.PerformanceCareRule
|
||||
- com.intellij.internal.statistic.eventLog.validator.rules.impl.UtilValidationRule
|
||||
|
||||
@@ -34,7 +34,6 @@ com/intellij/internal/statistic/eventLog/events/EventId1
|
||||
com/intellij/internal/statistic/eventLog/events/EventId2
|
||||
com/intellij/internal/statistic/eventLog/events/EventId3
|
||||
com/intellij/internal/statistic/eventLog/events/IntListEventField
|
||||
com/intellij/internal/statistic/eventLog/events/ObjectEventData
|
||||
com/intellij/internal/statistic/eventLog/events/ObjectEventField
|
||||
com/intellij/internal/statistic/eventLog/events/PrimitiveEventField
|
||||
com/intellij/internal/statistic/eventLog/events/StringEventField
|
||||
|
||||
@@ -12,7 +12,9 @@ import com.intellij.ide.plugins.certificates.PluginCertificateManager;
|
||||
import com.intellij.ide.plugins.enums.PluginsGroupType;
|
||||
import com.intellij.ide.plugins.enums.SortBy;
|
||||
import com.intellij.ide.plugins.marketplace.MarketplaceRequests;
|
||||
import com.intellij.ide.plugins.marketplace.ranking.MarketplaceLocalRanker;
|
||||
import com.intellij.ide.plugins.marketplace.statistics.PluginManagerUsageCollector;
|
||||
import com.intellij.ide.plugins.marketplace.statistics.features.PluginManagerSearchResultFeatureProvider;
|
||||
import com.intellij.ide.plugins.newui.*;
|
||||
import com.intellij.ide.util.PropertiesComponent;
|
||||
import com.intellij.openapi.actionSystem.*;
|
||||
@@ -225,6 +227,8 @@ public final class PluginManagerConfigurable
|
||||
createMarketplaceTab();
|
||||
createInstalledTab();
|
||||
|
||||
PluginManagerUsageCollector.sessionStarted();
|
||||
|
||||
myCardPanel = new MultiPanel() {
|
||||
@Override
|
||||
protected JComponent create(Integer key) {
|
||||
@@ -772,9 +776,12 @@ public final class PluginManagerConfigurable
|
||||
new SearchResultPanel(marketplaceController, panel, true, 0, 0) {
|
||||
@Override
|
||||
protected void handleQuery(@NotNull String query, @NotNull PluginsGroup result) {
|
||||
int searchIndex = PluginManagerUsageCollector.updateAndGetSearchIndex();
|
||||
try {
|
||||
SearchQueryParser.Marketplace parser = new SearchQueryParser.Marketplace(query);
|
||||
|
||||
Map<IdeaPluginDescriptor, Double> pluginToScore = null;
|
||||
|
||||
if (parser.internal) {
|
||||
PluginsViewCustomizer.PluginsGroupDescriptor groupDescriptor =
|
||||
getPluginsViewCustomizer().getInternalPluginsGroupDescriptor();
|
||||
@@ -841,6 +848,11 @@ public final class PluginManagerConfigurable
|
||||
|
||||
ContainerUtil.removeDuplicates(result.descriptors);
|
||||
|
||||
final var localRanker = MarketplaceLocalRanker.getInstanceIfEnabled();
|
||||
if (localRanker != null) {
|
||||
pluginToScore = localRanker.rankPlugins(parser, result.descriptors);
|
||||
}
|
||||
|
||||
if (!result.descriptors.isEmpty()) {
|
||||
String title = IdeBundle.message("plugin.manager.action.label.sort.by.1");
|
||||
|
||||
@@ -868,7 +880,8 @@ public final class PluginManagerConfigurable
|
||||
}
|
||||
}
|
||||
|
||||
PluginManagerUsageCollector.performMarketplaceSearch(ProjectUtil.getActiveProject(), parser, result.descriptors);
|
||||
PluginManagerUsageCollector.performMarketplaceSearch(
|
||||
ProjectUtil.getActiveProject(), parser, result.descriptors, searchIndex, pluginToScore);
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.info(e);
|
||||
@@ -1164,6 +1177,7 @@ public final class PluginManagerConfigurable
|
||||
|
||||
@Override
|
||||
protected void handleQuery(@NotNull String query, @NotNull PluginsGroup result) {
|
||||
int searchIndex = PluginManagerUsageCollector.updateAndGetSearchIndex();
|
||||
myPluginModel.setInvalidFixCallback(null);
|
||||
|
||||
SearchQueryParser.Installed parser = new SearchQueryParser.Installed(query);
|
||||
@@ -1224,7 +1238,8 @@ public final class PluginManagerConfigurable
|
||||
}
|
||||
|
||||
result.descriptors.addAll(descriptors);
|
||||
PluginManagerUsageCollector.performInstalledTabSearch(ProjectUtil.getActiveProject(), parser, result.descriptors);
|
||||
PluginManagerUsageCollector.performInstalledTabSearch(
|
||||
ProjectUtil.getActiveProject(), parser, result.descriptors, searchIndex, null);
|
||||
|
||||
if (!result.descriptors.isEmpty()) {
|
||||
if (parser.invalid) {
|
||||
@@ -1988,6 +2003,7 @@ public final class PluginManagerConfigurable
|
||||
return () -> {
|
||||
};
|
||||
}
|
||||
|
||||
if (StringUtil.isEmpty(option) && (myTabHeaderComponent.getSelectionTab() == MARKETPLACE_TAB || myInstalledSearchPanel.isEmpty())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ internal class MarketplaceSearchPluginData(
|
||||
var isPaid: Boolean = false,
|
||||
val rating: Double = 0.0,
|
||||
val name: String = "",
|
||||
private val cdate: Long? = null,
|
||||
val cdate: Long? = null,
|
||||
val organization: String = "",
|
||||
@get:JsonProperty("updateId")
|
||||
val externalUpdateId: String? = null,
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// 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.ide.plugins.marketplace.ranking
|
||||
|
||||
import com.intellij.ide.plugins.IdeaPluginDescriptor
|
||||
import com.intellij.ide.plugins.newui.SearchQueryParser
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
@ApiStatus.Internal
|
||||
interface MarketplaceLocalRanker {
|
||||
companion object {
|
||||
val EP_NAME: ExtensionPointName<MarketplaceLocalRanker> = ExtensionPointName.create("com.intellij.marketplaceLocalRanker")
|
||||
|
||||
@JvmStatic
|
||||
fun getInstanceIfEnabled(): MarketplaceLocalRanker? {
|
||||
return EP_NAME.extensionList.firstOrNull()?.takeIf { it.isEnabled() }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether machine learning in Marketplace is enabled.
|
||||
* This method can return false if ML-ranking is disabled and no experiments are allowed
|
||||
*/
|
||||
fun isEnabled(): Boolean
|
||||
|
||||
/**
|
||||
* Ranks the plugins inplace within a single lookup in the Marketplace tab of Plugin Manager.
|
||||
* Returns the plugin relevance scores assigned by the ranking model.
|
||||
*/
|
||||
fun rankPlugins(queryParser: SearchQueryParser.Marketplace, plugins: MutableList<IdeaPluginDescriptor>): Map<IdeaPluginDescriptor, Double>
|
||||
|
||||
val experimentGroup: Int
|
||||
val experimentVersion: Int
|
||||
}
|
||||
@@ -12,6 +12,8 @@ import com.intellij.ide.plugins.newui.SearchQueryParser
|
||||
import com.intellij.openapi.extensions.PluginId
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
/*
|
||||
Collects plugin manager usage statistics:
|
||||
@@ -26,33 +28,63 @@ internal object PluginManagerUsageCollector {
|
||||
private val fusCollector = PluginManagerFUSCollector()
|
||||
private val mpCollector = PluginManagerMPCollector()
|
||||
|
||||
// Plugin manager search session identifier which is unique within one IDE session
|
||||
private val sessionId = AtomicInteger(-1)
|
||||
// Search index within one plugin manager search session. The order corresponds to the order of query updates
|
||||
private val searchIndex = AtomicInteger(0)
|
||||
|
||||
private val installedPluginInSession = AtomicBoolean(false)
|
||||
|
||||
@JvmStatic
|
||||
fun sessionStarted(): Int {
|
||||
searchIndex.set(0)
|
||||
installedPluginInSession.set(false)
|
||||
return sessionId.getAndIncrement()
|
||||
}
|
||||
|
||||
/**
|
||||
* Event that happens on search restart intention, before receiving the list with plugins.
|
||||
* Unlike with [performMarketplaceSearch] and [performInstalledTabSearch],
|
||||
* the order of these events corresponds to the order of query updates.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun updateAndGetSearchIndex(): Int {
|
||||
// If we perform search after installing a plugin, we consider this as a new search session:
|
||||
if (installedPluginInSession.compareAndSet(true, false)) sessionStarted()
|
||||
return searchIndex.getAndIncrement()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun performMarketplaceSearch(
|
||||
project: Project?,
|
||||
query: SearchQueryParser.Marketplace,
|
||||
results: List<IdeaPluginDescriptor>
|
||||
) = mpCollector.performMarketplaceSearch(project, query, results)
|
||||
results: List<IdeaPluginDescriptor>,
|
||||
searchIndex: Int,
|
||||
pluginToScore: Map<IdeaPluginDescriptor, Double>? = null
|
||||
) = mpCollector.performMarketplaceSearch(project, query, results, searchIndex, sessionId.get(), pluginToScore)
|
||||
|
||||
@JvmStatic
|
||||
fun performInstalledTabSearch(
|
||||
project: Project?,
|
||||
query: SearchQueryParser.Installed,
|
||||
results: List<IdeaPluginDescriptor>
|
||||
) = mpCollector.performInstalledTabSearch(project, query, results)
|
||||
results: List<IdeaPluginDescriptor>,
|
||||
searchIndex: Int,
|
||||
pluginToScore: Map<IdeaPluginDescriptor, Double>? = null
|
||||
) = mpCollector.performInstalledTabSearch(project, query, results, searchIndex, sessionId.get(), pluginToScore)
|
||||
|
||||
@JvmStatic
|
||||
fun searchReset() = mpCollector.searchReset()
|
||||
fun searchReset() = mpCollector.searchReset(sessionId.get())
|
||||
|
||||
@JvmStatic
|
||||
fun pluginCardOpened(descriptor: IdeaPluginDescriptor, group: PluginsGroup?) {
|
||||
fusCollector.pluginCardOpened(descriptor, group)
|
||||
mpCollector.pluginCardOpened(descriptor, group)
|
||||
fusCollector.pluginCardOpened(descriptor, group, sessionId.get())
|
||||
mpCollector.pluginCardOpened(descriptor, group, sessionId.get())
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun thirdPartyAcceptanceCheck(result: DialogAcceptanceResultEnum) {
|
||||
fusCollector.thirdPartyAcceptanceCheck(result)
|
||||
mpCollector.thirdPartyAcceptanceCheck(result)
|
||||
fusCollector.thirdPartyAcceptanceCheck(result, sessionId.get())
|
||||
mpCollector.thirdPartyAcceptanceCheck(result, sessionId.get())
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@@ -61,14 +93,14 @@ internal object PluginManagerUsageCollector {
|
||||
enable: Boolean,
|
||||
project: Project? = null,
|
||||
) {
|
||||
fusCollector.pluginsStateChanged(descriptors, enable, project)
|
||||
mpCollector.pluginsStateChanged(descriptors, enable, project)
|
||||
fusCollector.pluginsStateChanged(descriptors, enable, project, sessionId.get())
|
||||
mpCollector.pluginsStateChanged(descriptors, enable, project, sessionId.get())
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun pluginRemoved(pluginId: PluginId) {
|
||||
fusCollector.pluginRemoved(pluginId)
|
||||
mpCollector.pluginRemoved(pluginId)
|
||||
fusCollector.pluginRemoved(pluginId, sessionId.get())
|
||||
mpCollector.pluginRemoved(pluginId, sessionId.get())
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@@ -77,23 +109,24 @@ internal object PluginManagerUsageCollector {
|
||||
source: InstallationSourceEnum,
|
||||
previousVersion: String? = null
|
||||
) {
|
||||
fusCollector.pluginInstallationStarted(descriptor, source, previousVersion)
|
||||
mpCollector.pluginInstallationStarted(descriptor, source, previousVersion)
|
||||
fusCollector.pluginInstallationStarted(descriptor, source, sessionId.get(), previousVersion)
|
||||
mpCollector.pluginInstallationStarted(descriptor, source, sessionId.get(), previousVersion)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun pluginInstallationFinished(descriptor: IdeaPluginDescriptor) {
|
||||
fusCollector.pluginInstallationFinished(descriptor)
|
||||
mpCollector.pluginInstallationFinished(descriptor)
|
||||
installedPluginInSession.set(true)
|
||||
fusCollector.pluginInstallationFinished(descriptor, sessionId.get())
|
||||
mpCollector.pluginInstallationFinished(descriptor, sessionId.get())
|
||||
}
|
||||
|
||||
fun signatureCheckResult(descriptor: IdeaPluginDescriptor, result: SignatureVerificationResult) {
|
||||
fusCollector.signatureCheckResult(descriptor, result)
|
||||
mpCollector.signatureCheckResult(descriptor, result)
|
||||
fusCollector.signatureCheckResult(descriptor, result, sessionId.get())
|
||||
mpCollector.signatureCheckResult(descriptor, result, sessionId.get())
|
||||
}
|
||||
|
||||
fun signatureWarningShown(descriptor: IdeaPluginDescriptor, result: DialogAcceptanceResultEnum) {
|
||||
fusCollector.signatureWarningShown(descriptor, result)
|
||||
mpCollector.signatureWarningShown(descriptor, result)
|
||||
fusCollector.signatureWarningShown(descriptor, result, sessionId.get())
|
||||
mpCollector.signatureWarningShown(descriptor, result, sessionId.get())
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import com.intellij.ide.plugins.newui.PluginsGroup
|
||||
import com.intellij.internal.statistic.eventLog.EventLogGroup
|
||||
import com.intellij.internal.statistic.eventLog.events.BaseEventId
|
||||
import com.intellij.internal.statistic.eventLog.events.EventFields
|
||||
import com.intellij.internal.statistic.eventLog.events.IntEventField
|
||||
import com.intellij.internal.statistic.service.fus.collectors.CounterUsagesCollector
|
||||
import com.intellij.internal.statistic.utils.getPluginInfoByDescriptor
|
||||
import com.intellij.internal.statistic.utils.getPluginInfoById
|
||||
@@ -21,84 +22,97 @@ import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
internal const val PM_FUS_GROUP_ID = "plugin.manager"
|
||||
internal const val PM_FUS_GROUP_VERSION = 8
|
||||
internal const val PM_FUS_GROUP_VERSION = 9
|
||||
private val EVENT_GROUP = EventLogGroup(PM_FUS_GROUP_ID, PM_FUS_GROUP_VERSION)
|
||||
|
||||
@ApiStatus.Internal
|
||||
open class PluginManagerFUSCollector : CounterUsagesCollector() {
|
||||
override fun getGroup() = EVENT_GROUP
|
||||
|
||||
@Suppress("PropertyName")
|
||||
protected val PLUGIN_MANAGER_SESSION_ID = IntEventField("sessionId")
|
||||
@Suppress("PropertyName")
|
||||
protected val PLUGIN_MANAGER_SEARCH_INDEX = IntEventField("searchIndex")
|
||||
|
||||
private val PLUGINS_GROUP_TYPE = EventFields.Enum<PluginsGroupType>("group")
|
||||
private val ENABLE_DISABLE_ACTION = EventFields.Enum<PluginEnabledState>("enabled_state")
|
||||
private val ACCEPTANCE_RESULT = EventFields.Enum<DialogAcceptanceResultEnum>("acceptance_result")
|
||||
private val PLUGIN_SOURCE = EventFields.Enum<InstallationSourceEnum>("source")
|
||||
private val PREVIOUS_VERSION = PluginVersionEventField("previous_version")
|
||||
private val SIGNATURE_CHECK_RESULT = EventFields.Enum<SignatureVerificationResult>("signature_check_result")
|
||||
private val PLUGIN_LIST_INDEX = EventFields.Int("index")
|
||||
|
||||
private val PLUGIN_CARD_OPENED = group.registerEvent(
|
||||
"plugin.search.card.opened", EventFields.PluginInfo, PLUGINS_GROUP_TYPE, EventFields.Int("index")
|
||||
private val PLUGIN_CARD_OPENED = group.registerVarargEvent(
|
||||
"plugin.search.card.opened", EventFields.PluginInfo, PLUGINS_GROUP_TYPE,
|
||||
PLUGIN_LIST_INDEX, PLUGIN_MANAGER_SESSION_ID
|
||||
)
|
||||
private val THIRD_PARTY_ACCEPTANCE_CHECK = group.registerEvent("plugin.install.third.party.check",
|
||||
ACCEPTANCE_RESULT)
|
||||
ACCEPTANCE_RESULT, PLUGIN_MANAGER_SESSION_ID)
|
||||
private val PLUGIN_SIGNATURE_WARNING = group.registerEvent(
|
||||
"plugin.signature.warning.shown", EventFields.PluginInfo, ACCEPTANCE_RESULT
|
||||
"plugin.signature.warning.shown", EventFields.PluginInfo, ACCEPTANCE_RESULT, PLUGIN_MANAGER_SESSION_ID
|
||||
)
|
||||
private val PLUGIN_SIGNATURE_CHECK_RESULT = group.registerEvent(
|
||||
"plugin.signature.check.result", EventFields.PluginInfo, SIGNATURE_CHECK_RESULT
|
||||
"plugin.signature.check.result", EventFields.PluginInfo, SIGNATURE_CHECK_RESULT, PLUGIN_MANAGER_SESSION_ID
|
||||
)
|
||||
private val PLUGIN_STATE_CHANGED = group.registerEvent(
|
||||
"plugin.state.changed", EventFields.PluginInfo, ENABLE_DISABLE_ACTION
|
||||
"plugin.state.changed", EventFields.PluginInfo, ENABLE_DISABLE_ACTION, PLUGIN_MANAGER_SESSION_ID
|
||||
)
|
||||
private val PLUGIN_INSTALLATION_STARTED = group.registerEvent(
|
||||
"plugin.installation.started", PLUGIN_SOURCE, EventFields.PluginInfo, PREVIOUS_VERSION
|
||||
private val PLUGIN_INSTALLATION_STARTED = group.registerVarargEvent(
|
||||
"plugin.installation.started", PLUGIN_SOURCE, EventFields.PluginInfo, PREVIOUS_VERSION, PLUGIN_MANAGER_SESSION_ID
|
||||
)
|
||||
private val PLUGIN_INSTALLATION_FINISHED = group.registerEvent("plugin.installation.finished", EventFields.PluginInfo)
|
||||
private val PLUGIN_REMOVED = group.registerEvent("plugin.was.removed", EventFields.PluginInfo)
|
||||
private val PLUGIN_INSTALLATION_FINISHED = group.registerEvent("plugin.installation.finished", EventFields.PluginInfo, PLUGIN_MANAGER_SESSION_ID)
|
||||
private val PLUGIN_REMOVED = group.registerEvent("plugin.was.removed", EventFields.PluginInfo, PLUGIN_MANAGER_SESSION_ID)
|
||||
|
||||
fun pluginCardOpened(descriptor: IdeaPluginDescriptor, group: PluginsGroup?): Unit? = group?.let {
|
||||
PLUGIN_CARD_OPENED.log(getPluginInfoByDescriptor(descriptor), it.type, it.getPluginIndex(descriptor.pluginId))
|
||||
fun pluginCardOpened(descriptor: IdeaPluginDescriptor, group: PluginsGroup?, sessionId: Int): Unit? = group?.let {
|
||||
PLUGIN_CARD_OPENED.log(
|
||||
EventFields.PluginInfo.with(getPluginInfoByDescriptor(descriptor)),
|
||||
PLUGINS_GROUP_TYPE.with(it.type),
|
||||
EventFields.Int("index").with(it.getPluginIndex(descriptor.pluginId)),
|
||||
PLUGIN_MANAGER_SESSION_ID.with(sessionId)
|
||||
)
|
||||
}
|
||||
|
||||
fun thirdPartyAcceptanceCheck(result: DialogAcceptanceResultEnum) {
|
||||
THIRD_PARTY_ACCEPTANCE_CHECK.getIfInitializedOrNull()?.log(result)
|
||||
fun thirdPartyAcceptanceCheck(result: DialogAcceptanceResultEnum, sessionId: Int) {
|
||||
THIRD_PARTY_ACCEPTANCE_CHECK.getIfInitializedOrNull()?.log(result, sessionId)
|
||||
}
|
||||
|
||||
fun pluginsStateChanged(
|
||||
descriptors: Collection<IdeaPluginDescriptor>,
|
||||
enable: Boolean,
|
||||
project: Project? = null,
|
||||
sessionId: Int
|
||||
) {
|
||||
PLUGIN_STATE_CHANGED.getIfInitializedOrNull()?.let { event ->
|
||||
descriptors.forEach { descriptor ->
|
||||
event.log(
|
||||
project,
|
||||
getPluginInfoByDescriptor(descriptor),
|
||||
PluginEnabledState.getState(enable),
|
||||
)
|
||||
event.log(project, getPluginInfoByDescriptor(descriptor), PluginEnabledState.getState(enable), sessionId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun pluginRemoved(pluginId: PluginId): Unit? = PLUGIN_REMOVED.getIfInitializedOrNull()?.log(getPluginInfoById(pluginId))
|
||||
fun pluginRemoved(pluginId: PluginId, sessionId: Int): Unit? = PLUGIN_REMOVED.getIfInitializedOrNull()
|
||||
?.log(getPluginInfoById(pluginId), sessionId)
|
||||
|
||||
fun pluginInstallationStarted(
|
||||
descriptor: IdeaPluginDescriptor,
|
||||
source: InstallationSourceEnum,
|
||||
previousVersion: String? = null
|
||||
sessionId: Int,
|
||||
previousVersion: String? = null,
|
||||
) {
|
||||
val pluginInfo = getPluginInfoByDescriptor(descriptor)
|
||||
PLUGIN_INSTALLATION_STARTED.getIfInitializedOrNull()?.log(source, pluginInfo, pluginInfo to previousVersion)
|
||||
PLUGIN_INSTALLATION_STARTED.getIfInitializedOrNull()?.log(
|
||||
PLUGIN_SOURCE.with(source), EventFields.PluginInfo.with(pluginInfo),
|
||||
PREVIOUS_VERSION.with(pluginInfo to previousVersion), PLUGIN_MANAGER_SESSION_ID.with(sessionId))
|
||||
}
|
||||
|
||||
fun pluginInstallationFinished(descriptor: IdeaPluginDescriptor): Unit? = getPluginInfoByDescriptor(descriptor).let {
|
||||
PLUGIN_INSTALLATION_FINISHED.getIfInitializedOrNull()?.log(it)
|
||||
fun pluginInstallationFinished(descriptor: IdeaPluginDescriptor, sessionId: Int): Unit? = getPluginInfoByDescriptor(descriptor).let {
|
||||
PLUGIN_INSTALLATION_FINISHED.getIfInitializedOrNull()?.log(it, sessionId)
|
||||
}
|
||||
|
||||
fun signatureCheckResult(descriptor: IdeaPluginDescriptor, result: SignatureVerificationResult): Unit? =
|
||||
PLUGIN_SIGNATURE_CHECK_RESULT.getIfInitializedOrNull()?.log(getPluginInfoByDescriptor(descriptor), result)
|
||||
fun signatureCheckResult(descriptor: IdeaPluginDescriptor, result: SignatureVerificationResult, sessionId: Int): Unit? =
|
||||
PLUGIN_SIGNATURE_CHECK_RESULT.getIfInitializedOrNull()?.log(getPluginInfoByDescriptor(descriptor), result, sessionId)
|
||||
|
||||
fun signatureWarningShown(descriptor: IdeaPluginDescriptor, result: DialogAcceptanceResultEnum): Unit? =
|
||||
PLUGIN_SIGNATURE_WARNING.getIfInitializedOrNull()?.log(getPluginInfoByDescriptor(descriptor), result)
|
||||
fun signatureWarningShown(descriptor: IdeaPluginDescriptor, result: DialogAcceptanceResultEnum, sessionId: Int): Unit? =
|
||||
PLUGIN_SIGNATURE_WARNING.getIfInitializedOrNull()?.log(getPluginInfoByDescriptor(descriptor), result, sessionId)
|
||||
|
||||
// We don't want to log actions when app did not initialize yet (e.g. migration process)
|
||||
protected fun <T : BaseEventId> T.getIfInitializedOrNull(): T? = if (ApplicationManager.getApplication() == null) null else this
|
||||
|
||||
@@ -16,7 +16,7 @@ import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
|
||||
private const val PM_MP_GROUP_ID = "mp.$PM_FUS_GROUP_ID"
|
||||
private const val PM_MP_GROUP_VERSION = 1
|
||||
private const val PM_MP_GROUP_VERSION = 2
|
||||
private val EVENT_GROUP = EventLogGroup(
|
||||
PM_MP_GROUP_ID,
|
||||
// this is needed to be able to change `PM_MP_GROUP_ID` child group without a requirement to update `PM_FUS_GROUP_ID` parent group version.
|
||||
@@ -43,14 +43,18 @@ class PluginManagerMPCollector : PluginManagerFUSCollector() {
|
||||
)
|
||||
|
||||
private val MARKETPLACE_TAB_SEARCH_PERFORMED = group.registerVarargEvent(
|
||||
"marketplace.tab.search", USER_QUERY_FEATURES_DATA_KEY, MARKETPLACE_SEARCH_FEATURES_DATA_KEY, SEARCH_RESULTS_FEATURES_DATA_KEY
|
||||
"marketplace.tab.search", USER_QUERY_FEATURES_DATA_KEY, MARKETPLACE_SEARCH_FEATURES_DATA_KEY,
|
||||
SEARCH_RESULTS_FEATURES_DATA_KEY, PLUGIN_MANAGER_SESSION_ID, PLUGIN_MANAGER_SEARCH_INDEX
|
||||
)
|
||||
private val INSTALLED_TAB_SEARCH_PERFORMED = group.registerVarargEvent(
|
||||
"installed.tab.search", USER_QUERY_FEATURES_DATA_KEY, LOCAL_SEARCH_FEATURES_DATA_KEY, SEARCH_RESULTS_FEATURES_DATA_KEY
|
||||
"installed.tab.search", USER_QUERY_FEATURES_DATA_KEY, LOCAL_SEARCH_FEATURES_DATA_KEY,
|
||||
SEARCH_RESULTS_FEATURES_DATA_KEY, PLUGIN_MANAGER_SESSION_ID, PLUGIN_MANAGER_SEARCH_INDEX
|
||||
)
|
||||
private val SEARCH_RESET = group.registerEvent("search.reset")
|
||||
private val SEARCH_RESET = group.registerEvent("search.reset", PLUGIN_MANAGER_SESSION_ID)
|
||||
|
||||
fun performMarketplaceSearch(project: Project?, query: SearchQueryParser.Marketplace, results: List<IdeaPluginDescriptor>) {
|
||||
fun performMarketplaceSearch(project: Project?, query: SearchQueryParser.Marketplace,
|
||||
results: List<IdeaPluginDescriptor>, searchIndex: Int, sessionId: Int,
|
||||
pluginToScore: Map<IdeaPluginDescriptor, Double>? = null) {
|
||||
MARKETPLACE_TAB_SEARCH_PERFORMED.getIfInitializedOrNull()?.log(project) {
|
||||
add(USER_QUERY_FEATURES_DATA_KEY.with(ObjectEventData(
|
||||
PluginManagerUserQueryFeatureProvider.getSearchStateFeatures(query.searchQuery)
|
||||
@@ -59,12 +63,16 @@ class PluginManagerMPCollector : PluginManagerFUSCollector() {
|
||||
PluginManagerMarketplaceSearchFeatureProvider.getSearchStateFeatures(query)
|
||||
)))
|
||||
add(SEARCH_RESULTS_FEATURES_DATA_KEY.with(ObjectEventData(
|
||||
PluginManagerSearchResultsFeatureProvider.getSearchStateFeatures(query.searchQuery, results)
|
||||
PluginManagerSearchResultsFeatureProvider.getSearchStateFeatures(query.searchQuery, results, pluginToScore)
|
||||
)))
|
||||
add(PLUGIN_MANAGER_SESSION_ID.with(sessionId))
|
||||
add(PLUGIN_MANAGER_SEARCH_INDEX.with(searchIndex))
|
||||
}
|
||||
}
|
||||
|
||||
fun performInstalledTabSearch(project: Project?, query: SearchQueryParser.Installed, results: List<IdeaPluginDescriptor>) {
|
||||
fun performInstalledTabSearch(project: Project?, query: SearchQueryParser.Installed,
|
||||
results: List<IdeaPluginDescriptor>, searchIndex: Int, sessionId: Int,
|
||||
pluginToScore: Map<IdeaPluginDescriptor, Double>? = null) {
|
||||
INSTALLED_TAB_SEARCH_PERFORMED.getIfInitializedOrNull()?.log(project) {
|
||||
add(USER_QUERY_FEATURES_DATA_KEY.with(ObjectEventData(
|
||||
PluginManagerUserQueryFeatureProvider.getSearchStateFeatures(query.searchQuery)
|
||||
@@ -73,12 +81,14 @@ class PluginManagerMPCollector : PluginManagerFUSCollector() {
|
||||
PluginManagerLocalSearchFeatureProvider.getSearchStateFeatures(query)
|
||||
)))
|
||||
add(SEARCH_RESULTS_FEATURES_DATA_KEY.with(ObjectEventData(
|
||||
PluginManagerSearchResultsFeatureProvider.getSearchStateFeatures(query.searchQuery, results)
|
||||
PluginManagerSearchResultsFeatureProvider.getSearchStateFeatures(query.searchQuery, results, pluginToScore)
|
||||
)))
|
||||
add(PLUGIN_MANAGER_SESSION_ID.with(sessionId))
|
||||
add(PLUGIN_MANAGER_SEARCH_INDEX.with(searchIndex))
|
||||
}
|
||||
}
|
||||
|
||||
fun searchReset() {
|
||||
SEARCH_RESET.log()
|
||||
fun searchReset(sessionId: Int) {
|
||||
SEARCH_RESET.log(sessionId)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// 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.ide.plugins.marketplace.statistics.features
|
||||
|
||||
import com.intellij.internal.statistic.eventLog.events.EventField
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
import com.intellij.internal.statistic.eventLog.events.EventPair
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
@ApiStatus.Internal
|
||||
interface MarketplaceTextualFeaturesProvider {
|
||||
companion object {
|
||||
val EP_NAME: ExtensionPointName<MarketplaceTextualFeaturesProvider> = ExtensionPointName.create("com.intellij.marketplaceTextualFeaturesProvider")
|
||||
|
||||
@JvmStatic
|
||||
fun getInstanceIfEnabled(): MarketplaceTextualFeaturesProvider? {
|
||||
return EP_NAME.extensionList.firstOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
fun getFeaturesDefinition(): Array<EventField<*>>
|
||||
|
||||
fun getTextualFeatures(query: String, match: String): List<EventPair<*>>
|
||||
}
|
||||
@@ -2,13 +2,15 @@
|
||||
package com.intellij.ide.plugins.marketplace.statistics.features
|
||||
|
||||
import com.intellij.ide.plugins.enums.SortBy
|
||||
import com.intellij.ide.plugins.marketplace.statistics.validators.MarketplaceTagsListValidator
|
||||
import com.intellij.ide.plugins.marketplace.ranking.MarketplaceLocalRanker
|
||||
import com.intellij.ide.plugins.marketplace.statistics.validators.MarketplaceTagValidator
|
||||
import com.intellij.ide.plugins.marketplace.statistics.validators.MarketplaceVendorsListValidator
|
||||
import com.intellij.ide.plugins.marketplace.utils.MarketplaceUrls
|
||||
import com.intellij.ide.plugins.newui.SearchQueryParser
|
||||
import com.intellij.internal.statistic.eventLog.events.EventField
|
||||
import com.intellij.internal.statistic.eventLog.events.EventFields
|
||||
import com.intellij.internal.statistic.eventLog.events.EventPair
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
|
||||
object PluginManagerMarketplaceSearchFeatureProvider {
|
||||
private val IS_SUGGESTED_DATA_KEY = EventFields.Boolean("isSuggested")
|
||||
@@ -20,24 +22,39 @@ object PluginManagerMarketplaceSearchFeatureProvider {
|
||||
"vendorsListFilter", MarketplaceVendorsListValidator::class.java
|
||||
)
|
||||
private val TAGS_LIST_FILTER_DATA_KEY = EventFields.StringListValidatedByCustomRule(
|
||||
"tagsListFilter", MarketplaceTagsListValidator::class.java
|
||||
"tagsListFilter", MarketplaceTagValidator::class.java
|
||||
)
|
||||
private val IS_ORDERED_BY_ML = EventFields.Boolean("isOrderedByML")
|
||||
private val ML_EXPERIMENT_GROUP = EventFields.Int("experimentGroup")
|
||||
private val ML_EXPERIMENT_VERSION = EventFields.Int("experimentVersion")
|
||||
private val IS_USER_INTERNAL = EventFields.Boolean("isUserInternal")
|
||||
|
||||
fun getFeaturesDefinition(): Array<EventField<*>> {
|
||||
return arrayOf(
|
||||
IS_SUGGESTED_DATA_KEY, IS_STAFF_PICKS_DATA_KEY, CUSTOM_REPOSITORY_COUNT_DATA_KEY, MARKETPLACE_CUSTOM_REPOSITORY_COUNT_DATA_KEY,
|
||||
SORT_BY_DATA_KEY, VENDORS_LIST_FILTER_DATA_KEY, TAGS_LIST_FILTER_DATA_KEY
|
||||
SORT_BY_DATA_KEY, VENDORS_LIST_FILTER_DATA_KEY, TAGS_LIST_FILTER_DATA_KEY, IS_ORDERED_BY_ML, ML_EXPERIMENT_GROUP,
|
||||
ML_EXPERIMENT_VERSION, IS_USER_INTERNAL
|
||||
)
|
||||
}
|
||||
|
||||
fun getSearchStateFeatures(query: SearchQueryParser.Marketplace): List<EventPair<*>> = buildList {
|
||||
val localRanker = MarketplaceLocalRanker.getInstanceIfEnabled()
|
||||
|
||||
addAll(listOf(
|
||||
IS_SUGGESTED_DATA_KEY.with(query.suggested),
|
||||
IS_STAFF_PICKS_DATA_KEY.with(query.staffPicks),
|
||||
CUSTOM_REPOSITORY_COUNT_DATA_KEY.with(query.repositories.size),
|
||||
MARKETPLACE_CUSTOM_REPOSITORY_COUNT_DATA_KEY.with(query.repositories.count { it.contains(MarketplaceUrls.getPluginManagerHost()) })
|
||||
MARKETPLACE_CUSTOM_REPOSITORY_COUNT_DATA_KEY.with(query.repositories.count { it.contains(MarketplaceUrls.getPluginManagerHost()) }),
|
||||
IS_ORDERED_BY_ML.with(localRanker != null)
|
||||
))
|
||||
|
||||
localRanker?.run {
|
||||
add(ML_EXPERIMENT_GROUP.with(experimentGroup))
|
||||
add(ML_EXPERIMENT_VERSION.with(experimentVersion))
|
||||
}
|
||||
|
||||
add(IS_USER_INTERNAL.with(ApplicationManager.getApplication().isInternal))
|
||||
|
||||
query.sortBy?.let { add(SORT_BY_DATA_KEY.with(it)) }
|
||||
query.vendors?.toList()?.let { add(VENDORS_LIST_FILTER_DATA_KEY.with(it)) }
|
||||
query.tags?.toList()?.let { add(TAGS_LIST_FILTER_DATA_KEY.with(it)) }
|
||||
|
||||
@@ -5,28 +5,44 @@ import com.intellij.ide.plugins.IdeaPluginDescriptor
|
||||
import com.intellij.ide.plugins.PluginNode
|
||||
import com.intellij.internal.statistic.eventLog.events.*
|
||||
import com.intellij.internal.statistic.utils.getPluginInfoByDescriptor
|
||||
import kotlin.math.round
|
||||
|
||||
object PluginManagerSearchResultFeatureProvider {
|
||||
private val NAME_LENGTH_DATA_KEY = EventFields.Int("nameLength")
|
||||
private val DEVELOPED_BY_JETBRAINS_DATA_KEY = EventFields.Boolean("byJetBrains")
|
||||
private val MARKETPLACE_INFO_DATA_KEY = ObjectEventField(
|
||||
"marketplaceInfo", *PluginManagerSearchResultMarketplaceFeatureProvider.getFeaturesDefinition()
|
||||
)
|
||||
private val ML_SCORE = EventFields.Double("mlScore")
|
||||
|
||||
fun getFeaturesDefinition(): Array<EventField<*>> {
|
||||
return arrayOf(
|
||||
NAME_LENGTH_DATA_KEY, DEVELOPED_BY_JETBRAINS_DATA_KEY, EventFields.PluginInfo, MARKETPLACE_INFO_DATA_KEY
|
||||
NAME_LENGTH_DATA_KEY, DEVELOPED_BY_JETBRAINS_DATA_KEY, EventFields.PluginInfo, ML_SCORE,
|
||||
*PluginManagerSearchResultMarketplaceFeatureProvider.getFeaturesDefinition(),
|
||||
*(MarketplaceTextualFeaturesProvider.getInstanceIfEnabled()?.getFeaturesDefinition() ?: emptyArray())
|
||||
)
|
||||
}
|
||||
|
||||
fun getSearchStateFeatures(userQuery: String?, descriptor: IdeaPluginDescriptor): List<EventPair<*>> = buildList {
|
||||
val pluginInfo = getPluginInfoByDescriptor(descriptor)
|
||||
fun getSearchStateFeatures(userQuery: String?, descriptor: IdeaPluginDescriptor,
|
||||
pluginToScore: Map<IdeaPluginDescriptor, Double>? = null): List<EventPair<*>> {
|
||||
return buildList {
|
||||
val pluginInfo = getPluginInfoByDescriptor(descriptor)
|
||||
|
||||
add(NAME_LENGTH_DATA_KEY.with(descriptor.name.length))
|
||||
add(DEVELOPED_BY_JETBRAINS_DATA_KEY.with(pluginInfo.isDevelopedByJetBrains()))
|
||||
add(EventFields.PluginInfo.with(pluginInfo))
|
||||
if (pluginInfo.isSafeToReport() && descriptor is PluginNode) {
|
||||
add(MARKETPLACE_INFO_DATA_KEY.with(PluginManagerSearchResultMarketplaceFeatureProvider.getSearchStateFeatures(descriptor)))
|
||||
add(NAME_LENGTH_DATA_KEY.with(descriptor.name.length))
|
||||
add(DEVELOPED_BY_JETBRAINS_DATA_KEY.with(pluginInfo.isDevelopedByJetBrains()))
|
||||
add(EventFields.PluginInfo.with(pluginInfo))
|
||||
if (pluginInfo.isSafeToReport() && descriptor is PluginNode) {
|
||||
addAll(PluginManagerSearchResultMarketplaceFeatureProvider.getSearchStateFeatures(descriptor))
|
||||
}
|
||||
|
||||
if (userQuery != null) {
|
||||
MarketplaceTextualFeaturesProvider.getInstanceIfEnabled()
|
||||
?.getTextualFeatures(userQuery, descriptor.name)
|
||||
?.also { addAll(it) }
|
||||
}
|
||||
|
||||
pluginToScore?.get(descriptor)?.let {
|
||||
add(ML_SCORE.with(roundDouble(it)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun roundDouble(value: Double) = if (!value.isFinite()) -1.0 else round(value * 100000) / 100000
|
||||
}
|
||||
@@ -4,7 +4,7 @@ package com.intellij.ide.plugins.marketplace.statistics.features
|
||||
import com.intellij.ide.plugins.PluginNode
|
||||
import com.intellij.internal.statistic.eventLog.events.EventField
|
||||
import com.intellij.internal.statistic.eventLog.events.EventFields
|
||||
import com.intellij.internal.statistic.eventLog.events.ObjectEventData
|
||||
import com.intellij.internal.statistic.eventLog.events.EventPair
|
||||
|
||||
|
||||
object PluginManagerSearchResultMarketplaceFeatureProvider {
|
||||
@@ -13,6 +13,13 @@ object PluginManagerSearchResultMarketplaceFeatureProvider {
|
||||
private val MARKETPLACE_DOWNLOADS_DATA_KEY = EventFields.Int("downloads")
|
||||
private val MARKETPLACE_PLUGIN_ID_DATA_KEY = EventFields.Int("marketplaceId")
|
||||
private val MARKETPLACE_PLUGIN_CDATE_DATA_KEY = EventFields.Long("date")
|
||||
private val MARKETPLACE_PLUGIN_DAYS_SINCE_LATEST_UPDATE = EventFields.Long("daysSinceLatestUpdate")
|
||||
// TODO: add when sent from backend:
|
||||
// private val MARKETPLACE_PLUGIN_TAGS = EventFields.StringListValidatedByCustomRule<MarketplaceTagValidator>("pluginTags")
|
||||
// private val MARKETPLACE_PLUGIN_HAS_SCREENSHOTS = EventFields.Boolean("hasScreenshots")
|
||||
// TODO: add how often plugin gets updated
|
||||
|
||||
private const val MILLIS_IN_DAY = 1000 * 60 * 60 * 24
|
||||
|
||||
fun getFeaturesDefinition(): Array<EventField<*>> {
|
||||
return arrayOf(
|
||||
@@ -20,11 +27,12 @@ object PluginManagerSearchResultMarketplaceFeatureProvider {
|
||||
MARKETPLACE_PAID_DATA_KEY,
|
||||
MARKETPLACE_DOWNLOADS_DATA_KEY,
|
||||
MARKETPLACE_PLUGIN_ID_DATA_KEY,
|
||||
MARKETPLACE_PLUGIN_CDATE_DATA_KEY
|
||||
MARKETPLACE_PLUGIN_CDATE_DATA_KEY,
|
||||
MARKETPLACE_PLUGIN_DAYS_SINCE_LATEST_UPDATE,
|
||||
)
|
||||
}
|
||||
|
||||
fun getSearchStateFeatures(pluginNode: PluginNode): ObjectEventData = ObjectEventData(buildList {
|
||||
fun getSearchStateFeatures(pluginNode: PluginNode): List<EventPair<*>> = buildList {
|
||||
add(MARKETPLACE_PAID_DATA_KEY.with(pluginNode.isPaid))
|
||||
pluginNode.rating?.toFloatOrNull()?.let {
|
||||
add(MARKETPLACE_RATING_DATA_KEY.with(it))
|
||||
@@ -35,6 +43,9 @@ object PluginManagerSearchResultMarketplaceFeatureProvider {
|
||||
pluginNode.externalPluginId?.toIntOrNull()?.let {
|
||||
add(MARKETPLACE_PLUGIN_ID_DATA_KEY.with(it))
|
||||
}
|
||||
add(MARKETPLACE_PLUGIN_CDATE_DATA_KEY.with(pluginNode.date))
|
||||
})
|
||||
if (pluginNode.date != Long.MAX_VALUE) {
|
||||
add(MARKETPLACE_PLUGIN_CDATE_DATA_KEY.with(pluginNode.date))
|
||||
add(MARKETPLACE_PLUGIN_DAYS_SINCE_LATEST_UPDATE.with((System.currentTimeMillis() - pluginNode.date) / MILLIS_IN_DAY))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,12 +20,18 @@ object PluginManagerSearchResultsFeatureProvider {
|
||||
)
|
||||
}
|
||||
|
||||
fun getSearchStateFeatures(userQuery: String?, result: List<IdeaPluginDescriptor>) = arrayListOf<EventPair<*>>(
|
||||
fun getCommonFeatures(userQuery: String?, result: List<IdeaPluginDescriptor>) = arrayListOf<EventPair<*>>(
|
||||
IS_EMPTY_DATA_KEY.with(result.isEmpty()),
|
||||
RESULTS_COUNT_DATA_KEY.with(result.size),
|
||||
RESULTS_COUNT_LIMIT_DATA_KEY.with(RESULTS_REPORT_COUNT),
|
||||
RESULTS_DATA_KEY.with(result.take(RESULTS_REPORT_COUNT).map {
|
||||
ObjectEventData(PluginManagerSearchResultFeatureProvider.getSearchStateFeatures(userQuery, it))
|
||||
})
|
||||
RESULTS_COUNT_LIMIT_DATA_KEY.with(RESULTS_REPORT_COUNT)
|
||||
)
|
||||
|
||||
fun getSearchStateFeatures(userQuery: String?, result: List<IdeaPluginDescriptor>,
|
||||
pluginToScore: Map<IdeaPluginDescriptor, Double>?): List<EventPair<*>> {
|
||||
return getCommonFeatures(userQuery, result).apply {
|
||||
add(RESULTS_DATA_KEY.with(result.take(RESULTS_REPORT_COUNT).map {
|
||||
ObjectEventData(PluginManagerSearchResultFeatureProvider.getSearchStateFeatures(userQuery, it, pluginToScore))
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import com.intellij.internal.statistic.eventLog.validator.rules.impl.CustomValid
|
||||
/*
|
||||
Validates if the tag name was provided by Marketplace and is therefore safe to report.
|
||||
*/
|
||||
class MarketplaceTagsListValidator : CustomValidationRule() {
|
||||
class MarketplaceTagValidator : CustomValidationRule() {
|
||||
override fun getRuleId(): String {
|
||||
return "mp_tags_list"
|
||||
}
|
||||
|
||||
@@ -697,5 +697,9 @@
|
||||
<extensionPoint name="editorSearchAreaProvider" interface="com.intellij.find.impl.livePreview.EditorSearchAreaProvider" dynamic="true"/>
|
||||
<extensionPoint qualifiedName="com.intellij.intentionPopupProvider" dynamic="true"
|
||||
interface="com.intellij.codeInsight.intention.impl.IntentionPopupProvider"/>
|
||||
<extensionPoint qualifiedName="com.intellij.marketplaceLocalRanker" dynamic="true"
|
||||
interface="com.intellij.ide.plugins.marketplace.ranking.MarketplaceLocalRanker"/>
|
||||
<extensionPoint qualifiedName="com.intellij.marketplaceTextualFeaturesProvider" dynamic="true"
|
||||
interface="com.intellij.ide.plugins.marketplace.statistics.features.MarketplaceTextualFeaturesProvider"/>
|
||||
</extensionPoints>
|
||||
</idea-plugin>
|
||||
|
||||
@@ -946,7 +946,7 @@
|
||||
<statistics.counterUsagesCollector
|
||||
implementationClass="com.intellij.ide.plugins.marketplace.statistics.collectors.PluginManagerFUSCollector"/>
|
||||
<statistics.validation.customValidationRule
|
||||
implementation="com.intellij.ide.plugins.marketplace.statistics.validators.MarketplaceTagsListValidator"/>
|
||||
implementation="com.intellij.ide.plugins.marketplace.statistics.validators.MarketplaceTagValidator"/>
|
||||
<statistics.validation.customValidationRule
|
||||
implementation="com.intellij.ide.plugins.marketplace.statistics.validators.MarketplaceVendorsListValidator"/>
|
||||
<statistics.counterUsagesCollector implementationClass="com.intellij.codeInsight.template.impl.LiveTemplateRunLogger"/>
|
||||
|
||||
19
plugins/marketplace-ml/intellij.marketplaceMl.iml
Normal file
19
plugins/marketplace-ml/intellij.marketplaceMl.iml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="kotlin-stdlib" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.lang.impl" />
|
||||
<orderEntry type="module" module-name="intellij.platform.testFramework" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.platform.statistics" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ide.impl" />
|
||||
<orderEntry type="library" name="caffeine" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ml.impl" />
|
||||
</component>
|
||||
</module>
|
||||
34
plugins/marketplace-ml/resources/META-INF/plugin.xml
Normal file
34
plugins/marketplace-ml/resources/META-INF/plugin.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<idea-plugin package="com.intellij.marketplaceMl">
|
||||
<id>com.intellij.marketplace.ml</id>
|
||||
<name>Machine Learning in Marketplace</name>
|
||||
<vendor>JetBrains</vendor>
|
||||
<category>Local AI/ML Tools</category>
|
||||
|
||||
<description><![CDATA[
|
||||
<p>The plugin improves the Marketplace search feature by ordering the search results using machine learning,
|
||||
making more relevant results appear higher up the list.
|
||||
</p>
|
||||
<br>
|
||||
<i>Machine learning ranking is currently in experimental mode</i>
|
||||
]]></description>
|
||||
|
||||
<resource-bundle>messages.marketplaceMlBundle</resource-bundle>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<registryKey
|
||||
defaultValue="-1"
|
||||
description="Manual machine learning ranking experiment group in the Plugin Manager"
|
||||
key="marketplace.ml.ranking.experiment.group"/>
|
||||
|
||||
<registryKey
|
||||
defaultValue="true"
|
||||
description="Disable machine learning ranking experiment in the Plugin Manager"
|
||||
key="marketplace.ml.ranking.disable.experiments"/>
|
||||
|
||||
<marketplaceLocalRanker implementation="com.intellij.marketplaceMl.MarketplaceLocalRankerImpl"/>
|
||||
<marketplaceTextualFeaturesProvider implementation="com.intellij.marketplaceMl.features.MarketplaceTextualFeaturesProviderImpl"/>
|
||||
</extensions>
|
||||
|
||||
<extensionPoints>
|
||||
</extensionPoints>
|
||||
</idea-plugin>
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.intellij.marketplaceMl
|
||||
|
||||
import com.intellij.ide.plugins.marketplace.ranking.MarketplaceLocalRanker
|
||||
|
||||
val rankingService
|
||||
get() = MarketplaceLocalRankingService.getInstance()
|
||||
|
||||
class MarketplaceLocalRankerImpl : MarketplaceLocalRanker by rankingService
|
||||
@@ -0,0 +1,63 @@
|
||||
// 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.marketplaceMl
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache
|
||||
import com.github.benmanes.caffeine.cache.Caffeine
|
||||
import com.intellij.ide.plugins.IdeaPluginDescriptor
|
||||
import com.intellij.ide.plugins.marketplace.ranking.MarketplaceLocalRanker
|
||||
import com.intellij.ide.plugins.marketplace.statistics.features.PluginManagerMarketplaceSearchFeatureProvider
|
||||
import com.intellij.ide.plugins.marketplace.statistics.features.PluginManagerSearchResultFeatureProvider
|
||||
import com.intellij.ide.plugins.marketplace.statistics.features.PluginManagerSearchResultsFeatureProvider
|
||||
import com.intellij.ide.plugins.marketplace.statistics.features.PluginManagerUserQueryFeatureProvider
|
||||
import com.intellij.ide.plugins.newui.SearchQueryParser
|
||||
import com.intellij.marketplaceMl.model.MarketplaceRankingModel
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.marketplaceMl.MarketplaceMLExperiment.ExperimentOption
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlin.time.toJavaDuration
|
||||
|
||||
@Service(Service.Level.APP)
|
||||
class MarketplaceLocalRankingService : MarketplaceLocalRanker {
|
||||
private val modelCache: Cache<Unit, MarketplaceRankingModel> =
|
||||
Caffeine.newBuilder().expireAfterAccess(60.seconds.toJavaDuration()).maximumSize(1).build()
|
||||
val model: MarketplaceRankingModel
|
||||
get() = modelCache.get(Unit) { MarketplaceRankingModel() }
|
||||
|
||||
override fun isEnabled(): Boolean = MarketplaceMLExperiment.getExperiment() == ExperimentOption.USE_ML
|
||||
|
||||
override fun rankPlugins(
|
||||
queryParser: SearchQueryParser.Marketplace,
|
||||
plugins: MutableList<IdeaPluginDescriptor>
|
||||
): Map<IdeaPluginDescriptor, Double> {
|
||||
val pluginToScore = mutableMapOf<IdeaPluginDescriptor, Double>()
|
||||
val searchQuery = queryParser.searchQuery
|
||||
|
||||
val queryFeatures = PluginManagerUserQueryFeatureProvider.getSearchStateFeatures(searchQuery)
|
||||
val marketplaceFeatures = PluginManagerMarketplaceSearchFeatureProvider.getSearchStateFeatures(queryParser)
|
||||
val commonResultFeatures = PluginManagerSearchResultsFeatureProvider.getCommonFeatures(searchQuery, plugins)
|
||||
val allItemFeatures = plugins.map { PluginManagerSearchResultFeatureProvider.getSearchStateFeatures(searchQuery, it) }
|
||||
|
||||
for ((index, pluginWithFeatures) in (plugins zip allItemFeatures).withIndex()) {
|
||||
val (plugin, itemFeatures) = pluginWithFeatures
|
||||
val allFeatures = queryFeatures + marketplaceFeatures + commonResultFeatures + itemFeatures
|
||||
val featuresMap = allFeatures.associate { it.field.name to it.data }
|
||||
// TODO: replace with model prediction once we have a trained model
|
||||
pluginToScore[plugin] = (plugins.size - index).toDouble() / plugins.size
|
||||
// pluginToScore[plugin] = model.predictScore(featuresMap)
|
||||
}
|
||||
|
||||
plugins.sortByDescending { pluginToScore[it] }
|
||||
return pluginToScore
|
||||
}
|
||||
|
||||
override val experimentGroup: Int
|
||||
get() = MarketplaceMLExperiment.experimentGroup
|
||||
|
||||
override val experimentVersion: Int
|
||||
get() = MarketplaceMLExperiment.VERSION
|
||||
|
||||
companion object {
|
||||
fun getInstance(): MarketplaceLocalRankingService = service()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// 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.marketplaceMl
|
||||
|
||||
import com.intellij.internal.statistic.eventLog.EventLogConfiguration
|
||||
import com.intellij.internal.statistic.eventLog.mp.MP_RECORDER_ID
|
||||
import com.intellij.internal.statistic.utils.StatisticsUploadAssistant
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.util.MathUtil
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
|
||||
object MarketplaceMLExperiment {
|
||||
const val VERSION = 0
|
||||
const val NUMBER_OF_GROUPS = 4
|
||||
|
||||
enum class ExperimentOption { NO_ML, USE_ML }
|
||||
|
||||
private val defaultExperimentOption = ExperimentOption.NO_ML
|
||||
private val marketplaceExperiments = mapOf(
|
||||
0 to ExperimentOption.USE_ML,
|
||||
)
|
||||
|
||||
val experimentGroup: Int
|
||||
get() = if (isExperimentalMode) {
|
||||
val registryExperimentGroup = Registry.intValue("marketplace.ml.ranking.experiment.group", -1, -1, NUMBER_OF_GROUPS - 1)
|
||||
if (registryExperimentGroup >= 0) registryExperimentGroup else computedGroup
|
||||
}
|
||||
else -1
|
||||
|
||||
var isExperimentalMode: Boolean = StatisticsUploadAssistant.isSendAllowed() && ApplicationManager.getApplication().isEAP
|
||||
@TestOnly set
|
||||
|
||||
private val computedGroup: Int by lazy {
|
||||
val mpLogConfiguration = EventLogConfiguration.getInstance().getOrCreate(MP_RECORDER_ID)
|
||||
// experiment groups get updated on the VERSION property change:
|
||||
MathUtil.nonNegativeAbs((mpLogConfiguration.deviceId + VERSION).hashCode()) % NUMBER_OF_GROUPS
|
||||
}
|
||||
|
||||
fun getExperiment(): ExperimentOption {
|
||||
return if (Registry.`is`("marketplace.ml.ranking.disable.experiments")) defaultExperimentOption
|
||||
else marketplaceExperiments.getOrDefault(experimentGroup, defaultExperimentOption)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.intellij.marketplaceMl.features
|
||||
|
||||
import com.intellij.ide.plugins.marketplace.statistics.features.MarketplaceTextualFeaturesProvider
|
||||
import com.intellij.internal.statistic.eventLog.events.EventField
|
||||
import com.intellij.internal.statistic.eventLog.events.EventFields
|
||||
import com.intellij.internal.statistic.eventLog.events.EventPair
|
||||
import com.intellij.textMatching.PrefixMatchingType
|
||||
import com.intellij.textMatching.PrefixMatchingUtil
|
||||
import kotlin.math.round
|
||||
|
||||
class MarketplaceTextualFeaturesProviderImpl : MarketplaceTextualFeaturesProvider {
|
||||
object Fields {
|
||||
internal val SAME_START_COUNT = EventFields.Int("prefixSameStartCount")
|
||||
internal val GREEDY_SCORE = EventFields.Double("prefixGreedyScore")
|
||||
internal val GREEDY_WITH_CASE_SCORE = EventFields.Double("prefixGreedyWithCaseScore")
|
||||
internal val MATCHED_WORDS_SCORE = EventFields.Double("prefixMatchedWordsScore")
|
||||
internal val MATCHED_WORDS_RELATIVE = EventFields.Double("prefixMatchedWordsRelative")
|
||||
internal val MATCHED_WORDS_WITH_CASE_SCORE = EventFields.Double("prefixMatchedWordsWithCaseScore")
|
||||
internal val MATCHED_WORDS_WITH_CASE_RELATIVE = EventFields.Double("prefixMatchedWordsWithCaseRelative")
|
||||
internal val SKIPPED_WORDS = EventFields.Int("prefixSkippedWords")
|
||||
internal val MATCHING_TYPE = EventFields.Enum<PrefixMatchingType>("prefixMatchingType")
|
||||
internal val EXACT = EventFields.Boolean("prefixExact")
|
||||
internal val MATCHED_LAST_WORD = EventFields.Boolean("prefixMatchedLastWord")
|
||||
}
|
||||
|
||||
override fun getFeaturesDefinition(): Array<EventField<*>> {
|
||||
return Fields.run {
|
||||
arrayOf(
|
||||
SAME_START_COUNT, GREEDY_SCORE, GREEDY_WITH_CASE_SCORE, MATCHED_WORDS_SCORE, MATCHED_WORDS_RELATIVE, MATCHED_WORDS_WITH_CASE_SCORE,
|
||||
MATCHED_WORDS_WITH_CASE_RELATIVE, SKIPPED_WORDS, MATCHING_TYPE, EXACT, MATCHED_LAST_WORD
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTextualFeatures(query: String, match: String): List<EventPair<*>> {
|
||||
if (query.isEmpty() || match.isEmpty()) return emptyList()
|
||||
val scores = PrefixMatchingUtil.PrefixMatchingScores.Builder().build(query, match)
|
||||
return Fields.run {
|
||||
listOf(
|
||||
SAME_START_COUNT.with(scores.start),
|
||||
GREEDY_SCORE.with(roundDouble(scores.greedy)),
|
||||
GREEDY_WITH_CASE_SCORE.with(roundDouble(scores.greedyWithCase)),
|
||||
MATCHED_WORDS_SCORE.with(roundDouble(scores.words)),
|
||||
MATCHED_WORDS_RELATIVE.with(roundDouble(scores.wordsRelative)),
|
||||
MATCHED_WORDS_WITH_CASE_SCORE.with(roundDouble(scores.wordsWithCase)),
|
||||
MATCHED_WORDS_WITH_CASE_RELATIVE.with(roundDouble(scores.wordsWithCaseRelative)),
|
||||
SKIPPED_WORDS.with(scores.skippedWords),
|
||||
MATCHING_TYPE.with(scores.type),
|
||||
EXACT.with(scores.exact),
|
||||
MATCHED_LAST_WORD.with(scores.exactFinal)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun roundDouble(value: Double): Double {
|
||||
if (!value.isFinite()) return -1.0
|
||||
return round(value * 100000) / 100000
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// 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.marketplaceMl.model
|
||||
|
||||
import com.intellij.internal.ml.DecisionFunction
|
||||
import com.intellij.internal.ml.FeatureMapper
|
||||
import com.intellij.internal.ml.ModelMetadata
|
||||
|
||||
abstract class MarketplaceRankingDecisionFunction(private val metadata: ModelMetadata) : DecisionFunction {
|
||||
override fun getFeaturesOrder(): Array<FeatureMapper> = metadata.featuresOrder
|
||||
|
||||
override fun getRequiredFeatures(): List<String> = emptyList()
|
||||
|
||||
override fun getUnknownFeatures(features: Collection<String>): List<String> = emptyList()
|
||||
|
||||
override fun version(): String? = metadata.version
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.intellij.marketplaceMl.model
|
||||
|
||||
import com.intellij.internal.ml.DecisionFunction
|
||||
import com.intellij.internal.ml.FeatureMapper
|
||||
|
||||
class MarketplaceRankingModel {
|
||||
private val decisionFunction: DecisionFunction = MarketplaceRankingModelLoader.loadModel()
|
||||
|
||||
fun predictScore(featureMap: Map<String, Any?>): Double {
|
||||
return decisionFunction.predict(buildArray(decisionFunction.featuresOrder, featureMap))
|
||||
}
|
||||
|
||||
private fun buildArray(featuresOrder: Array<FeatureMapper>, featureMap: Map<String, Any?>): DoubleArray {
|
||||
return DoubleArray(featuresOrder.size) {
|
||||
val mapper = featuresOrder[it]
|
||||
mapper.asArrayValue(featureMap[mapper.featureName])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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.marketplaceMl.model
|
||||
|
||||
import com.intellij.internal.ml.DecisionFunction
|
||||
import com.intellij.internal.ml.FeaturesInfo
|
||||
import kotlin.random.Random
|
||||
|
||||
|
||||
class MarketplaceRankingModelLoader {
|
||||
companion object {
|
||||
fun loadModel(): DecisionFunction {
|
||||
val emptyMetadata = FeaturesInfo(emptySet(), emptyList(), emptyList(), emptyList(), emptyArray(), null)
|
||||
return object : MarketplaceRankingDecisionFunction(emptyMetadata) {
|
||||
override fun predict(features: DoubleArray?): Double = Random.nextDouble()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ intellij.markdown
|
||||
intellij.platform.langInjection
|
||||
intellij.properties
|
||||
intellij.searchEverywhereMl
|
||||
intellij.marketplaceMl
|
||||
intellij.settingsSync
|
||||
intellij.sh
|
||||
intellij.statsCollector
|
||||
|
||||
Reference in New Issue
Block a user