Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.telemetry

import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.fileEditor.FileEditorManagerListener
import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.ProjectActivity
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.Alarm
import software.aws.toolkits.telemetry.IdeTelemetry

class OpenedFileTypesMetrics : ProjectActivity, Disposable {

Check warning on line 16 in plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/services/telemetry/OpenedFileTypesMetrics.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Extension class should be final and non-public

Extension class should not be public
private val currentOpenedFileTypes = mutableSetOf<String>()
private val alarm = Alarm(Alarm.ThreadToUse.POOLED_THREAD, this)
override suspend fun execute(project: Project) {
// add already open file extensions
FileEditorManager.getInstance(project).openFiles.forEach {
val extension = it.extension ?: return@forEach
addToExistingTelemetryBatch(extension)
}

// add newly opened file extensions
project.messageBus.connect(this).subscribe(
FileEditorManagerListener.FILE_EDITOR_MANAGER,
object : FileEditorManagerListener {
override fun fileOpened(source: FileEditorManager, file: VirtualFile) {
val extension = file.extension ?: return
addToExistingTelemetryBatch(extension)
}
}
)
scheduleNextMetricEvent()
}

private fun addToExistingTelemetryBatch(fileExt: String) {
val extension = ".$fileExt"
if (extension in codeFileTypes) {
currentOpenedFileTypes.add(extension)
}
}

fun scheduleNextMetricEvent() {
alarm.addRequest(this::emitFileTypeMetric, INTERVAL_BETWEEN_METRICS)
}

override fun dispose() {}

fun emitFileTypeMetric() {
ApplicationManager.getApplication().executeOnPooledThread {
currentOpenedFileTypes.forEach {
emitMetric(it)
}
currentOpenedFileTypes.clear()
if (!ApplicationManager.getApplication().isUnitTestMode) {
scheduleNextMetricEvent()
}
}
}

fun emitMetric(openFileExtension: String) {
IdeTelemetry.editCodeFile(project = null, filenameExt = openFileExtension)
}

companion object {
const val INTERVAL_BETWEEN_METRICS = 30 * 60 * 1000
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.telemetry

val codeFileTypes = setOf(
".abap",
".ada",
".adb",
".ads",
".apl",
".asm",
".awk",
".b",
".bas",
".bash",
".bat",
".boo",
".c",
".cbl",
".cc",
".cfc",
".cfm",
".cjs",
".class",
".clj",
".cljc",
".cljs",
".cls",
".cmake",
".cob",
".cobra",
".coffee",
".cpp",
".cpy",
".cr",
".cs",
".css",
".csx",
".cxx",
".d",
".dart",
".dfm",
".dpr",
".e",
".el",
".elc",
".elm",
".erl",
".ex",
".exs",
".f",
".f03",
".f08",
".f77",
".f90",
".f95",
".flow",
".for",
".fs",
".fsi",
".fsx",
".gd",
".go",
".gql",
".graphql",
".groovy",
".gs",
".gsp",
".gst",
".gsx",
".gvy",
".h",
".hack",
".hh",
".hpp",
".hrl",
".hs",
".htm",
".html",
".hy",
".idl",
".io",
".jar",
".java",
".jl",
".js",
".json",
".jsx",
".kt",
".kts",
".lean",
".lgt",
".lhs",
".lisp",
".logtalk",
".lsp",
".lua",
".m",
".ma",
".mak",
".makefile",
".md",
".mjs",
".ml",
".mli",
".mpl",
".ms",
".mu",
".mv",
".n",
".nb",
".nim",
".nix",
".oot",
".oz",
".pas",
".pasm",
".perl",
".php",
".phtml",
".pike",
".pir",
".pl",
".pm",
".pmod",
".pp",
".pro",
".prolog",
".ps1",
".psd1",
".psm1",
".purs",
".py",
".pyc",
".pyo",
".pyw",
".qs",
".r",
".raku",
".rakumod",
".rakutest",
".rb",
".rbw",
".rdata",
".re",
".red",
".reds",
".res",
".rex",
".rexx",
".ring",
".rkt",
".rktl",
".rlib",
".rm",
".rmd",
".roff",
".ron",
".rs",
".ruby",
".s",
".sas",
".sb",
".sb2",
".sb3",
".sc",
".scala",
".scd",
".scm",
".scss",
".sh",
".shen",
".sig",
".sml",
".sol",
".sql",
".ss",
".st",
".sv",
".swift",
".t",
".tcl",
".tf",
".trigger",
".ts",
".tsx",
".tu",
".v",
".vala",
".vapi",
".vb",
".vba",
".vbx",
".vhd",
".vhdl",
".vue",
".x",
".xc",
".xi",
".xml",
".yaml",
".zig",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.jetbrains.services.telemetry

import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.testFramework.LightVirtualFile
import com.intellij.testFramework.ProjectRule
import com.intellij.testFramework.runInEdtAndWait
import kotlinx.coroutines.runBlocking
import org.junit.Rule
import org.junit.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.spy
import org.mockito.kotlin.stub
import org.mockito.kotlin.verify

class OpenedFileTypeMetricsTest {

@Rule
@JvmField
val projectRule = ProjectRule()

@Test
fun `metrics are recorded for already opened file types`() {
val dummyFile = LightVirtualFile("dummy.kt")
runInEdtAndWait {
FileEditorManager.getInstance(projectRule.project).openFile(dummyFile)
}

val openedFileTypeMetrics = spy(OpenedFileTypesMetrics())
openedFileTypeMetrics.stub {
on { openedFileTypeMetrics.scheduleNextMetricEvent() }.doAnswer {
openedFileTypeMetrics.emitFileTypeMetric()
}
}

runBlocking {
openedFileTypeMetrics.execute(projectRule.project)
}
verify(openedFileTypeMetrics).emitMetric(".kt")
}

@Test
fun `duplicate metrics are not emitted`() {
val testFile = LightVirtualFile("test1.kt")
val testFile2 = LightVirtualFile("test2.kt")
runInEdtAndWait {
FileEditorManager.getInstance(projectRule.project).openFile(testFile)
FileEditorManager.getInstance(projectRule.project).openFile(testFile2)
}
val openedFileTypeMetrics = spy(OpenedFileTypesMetrics())
openedFileTypeMetrics.stub {
on { openedFileTypeMetrics.scheduleNextMetricEvent() }.doAnswer {
openedFileTypeMetrics.emitFileTypeMetric()
}
}

runBlocking {
openedFileTypeMetrics.execute(projectRule.project)
}
verify(openedFileTypeMetrics).emitMetric(any())
}
}
1 change: 1 addition & 0 deletions plugins/core/src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@
<!-- each plugin needs its own instance of these -->
<applicationService serviceImplementation="migration.software.aws.toolkits.jetbrains.core.coroutines.PluginCoroutineScopeTracker"/>
<projectService serviceImplementation="migration.software.aws.toolkits.jetbrains.core.coroutines.PluginCoroutineScopeTracker"/>
<postStartupActivity implementation="software.aws.toolkits.jetbrains.services.telemetry.OpenedFileTypesMetrics" />
</extensions>
</idea-plugin>
Loading