IJPL-175686 Add statistics for structure toolwindow usage

GitOrigin-RevId: ac2e065a46020ea18d0261ed2b6be793f1b2c2b5
This commit is contained in:
Anton Kozub
2025-02-19 12:14:11 +01:00
committed by intellij-monorepo-bot
parent a67f5da3aa
commit e412c274f6
7 changed files with 135 additions and 7 deletions

View File

@@ -8,6 +8,7 @@ import com.intellij.ide.structureView.StructureView
import com.intellij.ide.structureView.StructureViewBuilder
import com.intellij.ide.structureView.StructureViewWrapper
import com.intellij.ide.structureView.TextEditorBasedStructureViewModel
import com.intellij.ide.structureView.StructureViewEventsCollector
import com.intellij.ide.structureView.impl.StructureViewComposite
import com.intellij.ide.structureView.impl.StructureViewComposite.StructureViewDescriptor
import com.intellij.ide.structureView.impl.StructureViewState
@@ -47,6 +48,7 @@ import com.intellij.ui.ExperimentalUI
import com.intellij.ui.components.JBPanelWithEmptyText
import com.intellij.ui.content.ContentFactory
import com.intellij.ui.content.ContentManagerEvent
import com.intellij.ui.content.ContentManagerEvent.ContentOperation
import com.intellij.ui.content.ContentManagerListener
import com.intellij.ui.switcher.QuickActionProvider
import com.intellij.util.BitUtil
@@ -72,7 +74,6 @@ import java.util.concurrent.atomic.AtomicReference
import javax.swing.JComponent
import javax.swing.JPanel
import javax.swing.SwingUtilities
import kotlin.jvm.Throws
@OptIn(FlowPreview::class)
class StructureViewWrapperImpl(
@@ -140,15 +141,20 @@ class StructureViewWrapperImpl(
scheduleRebuild()
}
myToolWindow.contentManager.addContentManagerListener(object : ContentManagerListener {
var currentIndex = -1 // to distinguish event "another tab selected" from "contents were removed and added"
override fun selectionChanged(event: ContentManagerEvent) {
if (myStructureView is StructureViewComposite) {
val views = (myStructureView as StructureViewComposite).structureViews
for (view in views) {
views.forEachIndexed { i, view ->
if (view.title == event.content.tabName) {
coroutineScope.launch {
updateHeaderActions(view.structureView)
}
break
if (myToolWindow.contentManager.contentCount == 2 && i != currentIndex && event.operation == ContentOperation.add) {
if (i != -1) StructureViewEventsCollector.logTabSelected(view)
currentIndex = i
}
return@forEachIndexed
}
}
}
@@ -426,10 +432,16 @@ class StructureViewWrapperImpl(
myFileEditor = editor
Disposer.register(this@StructureViewWrapperImpl, structureView)
val previouslySelectedTab = StructureViewState.getInstance(project).selectedTab
if (structureView is StructureViewComposite) {
val views: Array<StructureViewDescriptor> = structureView.structureViews
names = views.map { it.title }.toTypedArray()
panels = views.map { createContentPanel(it.structureView.component) }
panels = views.map {
if (previouslySelectedTab == it.title) {
StructureViewEventsCollector.logBuildStructure(it)
}
createContentPanel(it.structureView.component)
}
}
else {
createSinglePanel(structureView.component)

View File

@@ -1036,6 +1036,7 @@
<projectService serviceInterface="com.intellij.ide.structureView.StructureViewFactory"
serviceImplementation="com.intellij.ide.structureView.impl.StructureViewFactoryImpl"/>
<statistics.counterUsagesCollector implementationClass="com.intellij.ide.structureView.StructureViewEventsCollector"/>
<projectService serviceInterface="com.intellij.codeInspection.InspectionManager"
serviceImplementation="com.intellij.codeInspection.ex.InspectionManagerEx"/>
<documentationProvider implementation="com.intellij.codeInspection.actions.InspectionDescriptionDocumentationProvider"/>

View File

@@ -21,5 +21,6 @@
<orderEntry type="module" module-name="intellij.platform.util.ui" />
<orderEntry type="library" name="fastutil-min" level="project" />
<orderEntry type="module" module-name="intellij.platform.core.ui" />
<orderEntry type="module" module-name="intellij.platform.statistics" />
</component>
</module>

View File

@@ -0,0 +1,80 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide.structureView
import com.intellij.ide.structureView.impl.StructureViewComposite.StructureViewDescriptor
import com.intellij.ide.structureView.logical.StructureViewTab
import com.intellij.ide.structureView.logical.impl.LogicalStructureViewModel
import com.intellij.ide.structureView.logical.impl.LogicalStructureViewTreeElement
import com.intellij.internal.statistic.eventLog.EventLogGroup
import com.intellij.internal.statistic.eventLog.events.EventFields
import com.intellij.internal.statistic.service.fus.collectors.CounterUsagesCollector
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Internal
object StructureViewEventsCollector: CounterUsagesCollector() {
private val GROUP = EventLogGroup("structure.view", 1)
override fun getGroup(): EventLogGroup = GROUP
private val TAB = EventFields.Enum<StructureViewTab>("tab")
private val MODEL_CLASS = EventFields.Class("model.class")
private val BUILD_STRUCTURE = GROUP.registerEvent(
"toolwindow.shown",
TAB, MODEL_CLASS,
"Toolwindow is opened, first time or after changing a file"
)
private val TAB_SELECTED = GROUP.registerEvent(
"tab.selected",
TAB, MODEL_CLASS,
"User selected another tab"
)
private val NAVIGATE = GROUP.registerEvent(
"navigate",
MODEL_CLASS,
"Navigate to psiElement"
)
private val CUSTOM_CLICK_HANDLED = GROUP.registerEvent(
"custom.click.handled",
MODEL_CLASS,
"Click event was handled by custom handler"
)
fun logBuildStructure(viewDescriptor: StructureViewDescriptor) {
val tab = viewDescriptor.title?.let { StructureViewTab.ofTitle(it) } ?: return
BUILD_STRUCTURE.log(tab, getModelClass(viewDescriptor))
}
fun logTabSelected(viewDescriptor: StructureViewDescriptor) {
val tab = viewDescriptor.title?.let { StructureViewTab.ofTitle(it) } ?: return
TAB_SELECTED.log(tab, getModelClass(viewDescriptor))
}
fun logNavigate(modelClass: Class<*>) {
NAVIGATE.log(modelClass)
}
fun logCustomClickHandled(modelClass: Class<*>) {
CUSTOM_CLICK_HANDLED.log(modelClass)
}
private fun getModelClass(viewDescriptor: StructureViewDescriptor): Class<*>? {
var model: Any = viewDescriptor.structureModel
if (model is LogicalStructureViewModel) {
val root = model.root
if (root is LogicalStructureViewTreeElement<*>) {
var assembledModel = root.getLogicalAssembledModel()
val children = assembledModel.getChildren()
if (children.size == 1)
return children[0].model?.javaClass
else
return assembledModel.model?.javaClass
}
else {
return root::class.java
}
}
return null
}
}

View File

@@ -44,12 +44,12 @@ class PhysicalAndLogicalStructureViewBuilder(
return StructureViewComposite(
StructureViewComposite.StructureViewDescriptor(
StructureViewBundle.message("structureview.tab.logical"),
StructureViewTab.LOGICAL.title,
logicalBuilder.createStructureView(fileEditor, project),
null
),
StructureViewComposite.StructureViewDescriptor(
StructureViewBundle.message("structureview.tab.physical"),
StructureViewTab.PHYSICAL.title,
physicalBuilder.createStructureView(fileEditor, project),
null
)

View File

@@ -0,0 +1,24 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide.structureView.logical
import com.intellij.ide.structureView.StructureViewBundle
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Internal
enum class StructureViewTab(
val title: String,
) {
PHYSICAL(StructureViewBundle.message("structureview.tab.physical")),
LOGICAL(StructureViewBundle.message("structureview.tab.logical"));
companion object {
fun ofTitle(title: String): StructureViewTab? = entries.firstOrNull { it.title == title }
}
fun not(): StructureViewTab {
return when (this) {
PHYSICAL -> LOGICAL
LOGICAL -> PHYSICAL
}
}
}

View File

@@ -3,6 +3,7 @@ package com.intellij.ide.structureView.logical.impl
import com.intellij.ide.TypePresentationService
import com.intellij.ide.projectView.PresentationData
import com.intellij.ide.structureView.StructureViewEventsCollector
import com.intellij.ide.structureView.StructureViewModel
import com.intellij.ide.structureView.StructureViewModelBase
import com.intellij.ide.structureView.StructureViewTreeElement
@@ -54,7 +55,11 @@ class LogicalStructureViewModel private constructor(psiFile: PsiFile, editor: Ed
override fun handleClick(element: StructureViewTreeElement, fragmentIndex: Int): Boolean {
val model = getModel(element) ?: return false
return LogicalModelPresentationProvider.getForObject(model)?.handleClick(model, fragmentIndex) ?: false
val handled = LogicalModelPresentationProvider.getForObject(model)?.handleClick(model, fragmentIndex) ?: false
if (handled) {
StructureViewEventsCollector.logCustomClickHandled(model::class.java)
}
return handled
}
private fun getModel(element: StructureViewTreeElement): Any? {
@@ -213,6 +218,11 @@ private class ElementsBuilder {
override fun getLogicalAssembledModel() = assembledModel
override fun navigate(requestFocus: Boolean) {
StructureViewEventsCollector.logNavigate(assembledModel.model!!::class.java)
super<PsiTreeElementBase>.navigate(requestFocus)
}
override fun equals(other: Any?): Boolean {
if (other !is PsiElementStructureElement<*>) return false
return assembledModel == other.assembledModel