Skip to content

Commit 20924a0

Browse files
committed
[analytics] introduces constants
1 parent 8cda6e4 commit 20924a0

4 files changed

Lines changed: 188 additions & 0 deletions

File tree

src/io/flutter/actions/FlutterSdkAction.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
import io.flutter.FlutterBundle;
1414
import io.flutter.FlutterMessages;
1515
import io.flutter.FlutterUtils;
16+
import io.flutter.analytics.Analytics;
17+
import io.flutter.analytics.AnalyticsConstants;
18+
import io.flutter.analytics.AnalyticsData;
1619
import io.flutter.bazel.Workspace;
1720
import io.flutter.pub.PubRoot;
1821
import io.flutter.pub.PubRoots;
@@ -32,19 +35,25 @@ public abstract class FlutterSdkAction extends DumbAwareAction {
3235
public void actionPerformed(@NotNull AnActionEvent event) {
3336
final Project project = DumbAwareAction.getEventProject(event);
3437

38+
AnalyticsData analyticsData = AnalyticsData.forAction(this, event);
39+
3540
if (enableActionInBazelContext()) {
3641
// See if the Bazel workspace exists for this project.
3742
final Workspace workspace = FlutterModuleUtils.getFlutterBazelWorkspace(project);
3843
if (workspace != null) {
3944
FileDocumentManager.getInstance().saveAllDocuments();
4045
startCommandInBazelContext(project, workspace, event);
46+
analyticsData.add(AnalyticsConstants.IN_BAZEL_CONTEXT, true);
47+
Analytics.report(analyticsData);
4148
return;
4249
}
4350
}
4451

4552
final FlutterSdk sdk = project != null ? FlutterSdk.getFlutterSdk(project) : null;
4653
if (sdk == null) {
4754
showMissingSdkDialog(project);
55+
analyticsData.add(AnalyticsConstants.MISSING_SDK, true);
56+
Analytics.report(analyticsData);
4857
return;
4958
}
5059

@@ -60,6 +69,8 @@ public void actionPerformed(@NotNull AnActionEvent event) {
6069
startCommand(project, sdk, sub, context);
6170
}
6271
}
72+
73+
Analytics.report(analyticsData);
6374
}
6475

6576
public abstract void startCommand(@NotNull Project project,

src/io/flutter/actions/ReloadFlutterApp.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
import icons.FlutterIcons;
1313
import io.flutter.FlutterBundle;
1414
import io.flutter.FlutterConstants;
15+
import io.flutter.analytics.Analytics;
16+
import io.flutter.analytics.AnalyticsConstants;
17+
import io.flutter.analytics.AnalyticsData;
1518
import io.flutter.run.FlutterReloadManager;
1619
import io.flutter.run.daemon.FlutterApp;
1720
import org.jetbrains.annotations.NotNull;
@@ -42,9 +45,12 @@ public void actionPerformed(@NotNull AnActionEvent e) {
4245
return;
4346
}
4447

48+
var analyticsData = AnalyticsData.forAction(this, e);
49+
4550
// If the shift key is held down, perform a restart. We check to see if we're being invoked from the
4651
// 'GoToAction' dialog. If so, the modifiers are for the command that opened the go-to action dialog.
4752
final boolean shouldRestart = (e.getModifiers() & InputEvent.SHIFT_MASK) != 0 && !"GoToAction".equals(e.getPlace());
53+
analyticsData.add(AnalyticsConstants.REQUIRES_RESTART, shouldRestart);
4854

4955
var reloadManager = FlutterReloadManager.getInstance(project);
5056
if (reloadManager == null) return;
@@ -56,6 +62,8 @@ public void actionPerformed(@NotNull AnActionEvent e) {
5662
// Else perform a hot reload.
5763
reloadManager.saveAllAndReload(getApp(), FlutterConstants.RELOAD_REASON_MANUAL);
5864
}
65+
66+
Analytics.report(analyticsData);
5967
}
6068

6169
// Override to disable the hot reload action when running flutter web apps.

src/io/flutter/actions/RestartFlutterApp.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
import io.flutter.FlutterBundle;
1717
import io.flutter.FlutterConstants;
1818
import io.flutter.FlutterMessages;
19+
import io.flutter.analytics.Analytics;
20+
import io.flutter.analytics.AnalyticsConstants;
21+
import io.flutter.analytics.AnalyticsData;
1922
import io.flutter.bazel.WorkspaceCache;
2023
import io.flutter.run.FlutterReloadManager;
2124
import io.flutter.run.daemon.FlutterApp;
@@ -50,6 +53,8 @@ public void actionPerformed(@NotNull AnActionEvent e) {
5053
reloadManager.saveAllAndRestart(getApp(), FlutterConstants.RELOAD_REASON_MANUAL);
5154
}
5255

56+
var analyticsData = AnalyticsData.forAction(this, e);
57+
5358
if (WorkspaceCache.getInstance(project).isBazel() &&
5459
FlutterSettings.getInstance().isShowBazelHotRestartWarning() &&
5560
!FlutterSettings.getInstance().isEnableBazelHotRestart()) {
@@ -61,8 +66,12 @@ public void actionPerformed(@NotNull AnActionEvent e) {
6166
NotificationType.INFORMATION);
6267
Notifications.Bus.notify(notification, project);
6368

69+
analyticsData.add(AnalyticsConstants.GOOGLE3, true);
70+
6471
// We only want to show this notification once.
6572
FlutterSettings.getInstance().setShowBazelHotRestartWarning(false);
6673
}
74+
75+
Analytics.report(analyticsData);
6776
}
6877
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/*
2+
* Copyright 2025 The Chromium Authors. All rights reserved.
3+
* Use of this source code is governed by a BSD-style license that can be
4+
* found in the LICENSE file.
5+
*/
6+
7+
package io.flutter.analytics
8+
9+
import com.intellij.openapi.actionSystem.AnAction
10+
import com.intellij.openapi.actionSystem.AnActionEvent
11+
import io.flutter.actions.FlutterAppAction
12+
13+
object Analytics {
14+
private val reporter = NoOpReporter
15+
16+
@JvmStatic
17+
fun report(data: AnalyticsData) = data.reportTo(reporter)
18+
}
19+
20+
abstract class AnalyticsReporter {
21+
internal abstract fun process(data: AnalyticsData)
22+
}
23+
24+
internal object PrintingReporter : AnalyticsReporter() {
25+
override fun process(data: AnalyticsData) = println(data.data)
26+
}
27+
28+
internal object NoOpReporter : AnalyticsReporter() {
29+
override fun process(data: AnalyticsData) = Unit
30+
}
31+
32+
abstract class AnalyticsData(type: String) {
33+
val data = mutableMapOf<String, Any>()
34+
35+
init {
36+
add(AnalyticsConstants.TYPE, type)
37+
}
38+
39+
companion object {
40+
@JvmStatic
41+
fun forAction(action: AnAction, event: AnActionEvent): ActionData = ActionData(
42+
event.actionManager.getId(action)
43+
// `FlutterAppAction`s aren't registered so ask them directly.
44+
?: (action as? FlutterAppAction)?.id,
45+
event.place
46+
)
47+
}
48+
49+
fun <T> add(key: DataValue<T>, value: T) = key.addTo(this, value)
50+
51+
internal operator fun set(key: String, value: Boolean) {
52+
data[key] = value
53+
}
54+
55+
internal operator fun set(key: String, value: Int) {
56+
data[key] = value
57+
}
58+
59+
internal operator fun set(key: String, value: String) {
60+
data[key] = value
61+
}
62+
63+
open fun reportTo(reporter: AnalyticsReporter) = reporter.process(this)
64+
}
65+
66+
/**
67+
* Data describing an IntelliJ [com.intellij.openapi.actionSystem.AnAction] for analytics reporting.
68+
*
69+
* @param id The unique identifier of the action, typically defined in `plugin.xml`.
70+
* @param place The UI location where the action was invoked (e.g., "MainMenu", "Toolbar").
71+
* @see <a href="https://plugins.jetbrains.com/docs/intellij/basic-action-system.html">IntelliJ Action System</a>
72+
*/
73+
class ActionData(private val id: String?, private val place: String) : AnalyticsData("action") {
74+
75+
init {
76+
id?.let { add(AnalyticsConstants.ID, it) }
77+
add(AnalyticsConstants.PLACE, place)
78+
}
79+
80+
override fun reportTo(reporter: AnalyticsReporter) {
81+
// We only report if we have an id for the event.
82+
if (id == null) return
83+
super.reportTo(reporter)
84+
}
85+
}
86+
87+
/**
88+
* Defines standard keys for analytics data properties.
89+
*
90+
* The properties are exposed as `@JvmField`s to be easily accessible as static
91+
* fields from Java.
92+
*/
93+
object AnalyticsConstants {
94+
/**
95+
* Indicates if the project is a Google3 project.
96+
*/
97+
@JvmField
98+
val GOOGLE3 = BooleanValue("google3")
99+
100+
/**
101+
* The unique identifier for an action or event.
102+
*/
103+
@JvmField
104+
val ID = StringValue("id")
105+
106+
/**
107+
* Indicates if the project is in a Bazel context.
108+
*/
109+
@JvmField
110+
val IN_BAZEL_CONTEXT = BooleanValue("inBazelContext")
111+
112+
/**
113+
* Indicates if the Flutter SDK is missing.
114+
*/
115+
@JvmField
116+
val MISSING_SDK = BooleanValue("missingSdk")
117+
118+
/**
119+
* The UI location where an action was invoked, as provided by
120+
* [com.intellij.openapi.actionSystem.PlaceProvider.getPlace] (for example, "MainMenu",
121+
* "MainToolbar", "EditorPopup", "GoToAction", etc).
122+
*/
123+
@JvmField
124+
val PLACE = StringValue("place")
125+
126+
/**
127+
* Indicates if a restart is required for a hot reload request.
128+
*/
129+
@JvmField
130+
val REQUIRES_RESTART = BooleanValue("requiresRestart")
131+
132+
/**
133+
* The type of the analytics event (e.g., "action", ...).
134+
*/
135+
@JvmField
136+
val TYPE = StringValue("type")
137+
}
138+
139+
140+
sealed class DataValue<T>(val name: String) {
141+
abstract fun addTo(data: AnalyticsData, value: T);
142+
}
143+
144+
class StringValue(name: String) : DataValue<String>(name) {
145+
override fun addTo(data: AnalyticsData, value: String) {
146+
data[name] = value
147+
}
148+
}
149+
150+
class IntValue(name: String) : DataValue<Int>(name) {
151+
override fun addTo(data: AnalyticsData, value: Int) {
152+
data[name] = value
153+
}
154+
}
155+
156+
class BooleanValue(name: String) : DataValue<Boolean>(name) {
157+
override fun addTo(data: AnalyticsData, value: Boolean) {
158+
data[name] = value
159+
}
160+
}

0 commit comments

Comments
 (0)