Skip to content

Commit 24a6624

Browse files
authored
[analytics] preliminary Analytics reporting API (#8597)
Companion to dart-lang/tools#2229 that noodles a bit on an analytics reporting API. If this looks good, I'll follow up with documentation and fill in some more implementation for the remaining actions (see: #8598). Implementing a `UnifiedAnalyticsReporter` that connects with the `UnifiedAnalytics` implementation may feed changes back to this too. (I'll schedule some time to chat through that since it'd probably be best done synchronously.) --- - [x] I’ve reviewed the contributor guide and applied the relevant portions to this PR. <details> <summary>Contribution guidelines:</summary><br> - See our [contributor guide]([https://github.com/dart-lang/sdk/blob/main/CONTRIBUTING.md](https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview) for general expectations for PRs. - Larger or significant changes should be discussed in an issue before creating a PR. - Dart contributions to our repos should follow the [Dart style guide](https://dart.dev/guides/language/effective-dart) and use `dart format`. - Java and Kotlin contributions should strive to follow Java and Kotlin best practices ([discussion](#8098)). </details>
1 parent cad44ab commit 24a6624

File tree

5 files changed

+116
-0
lines changed

5 files changed

+116
-0
lines changed

src/io/flutter/actions/FlutterAppAction.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,8 @@ public void update(@NotNull final AnActionEvent e) {
9898
public FlutterApp getApp() {
9999
return myApp;
100100
}
101+
102+
public @NotNull String getId() {
103+
return myActionId;
104+
}
101105
}

src/io/flutter/actions/FlutterSdkAction.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
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.AnalyticsData;
1618
import io.flutter.bazel.Workspace;
1719
import io.flutter.pub.PubRoot;
1820
import io.flutter.pub.PubRoots;
@@ -32,19 +34,25 @@ public abstract class FlutterSdkAction extends DumbAwareAction {
3234
public void actionPerformed(@NotNull AnActionEvent event) {
3335
final Project project = DumbAwareAction.getEventProject(event);
3436

37+
AnalyticsData analyticsData = AnalyticsData.forAction(this, event);
38+
3539
if (enableActionInBazelContext()) {
3640
// See if the Bazel workspace exists for this project.
3741
final Workspace workspace = FlutterModuleUtils.getFlutterBazelWorkspace(project);
3842
if (workspace != null) {
3943
FileDocumentManager.getInstance().saveAllDocuments();
4044
startCommandInBazelContext(project, workspace, event);
45+
analyticsData.add("inBazelContext", true);
46+
Analytics.report(analyticsData);
4147
return;
4248
}
4349
}
4450

4551
final FlutterSdk sdk = project != null ? FlutterSdk.getFlutterSdk(project) : null;
4652
if (sdk == null) {
4753
showMissingSdkDialog(project);
54+
analyticsData.add("missingSdk", true);
55+
Analytics.report(analyticsData);
4856
return;
4957
}
5058

@@ -60,6 +68,8 @@ public void actionPerformed(@NotNull AnActionEvent event) {
6068
startCommand(project, sdk, sub, context);
6169
}
6270
}
71+
72+
Analytics.report(analyticsData);
6373
}
6474

6575
public abstract void startCommand(@NotNull Project project,

src/io/flutter/actions/ReloadFlutterApp.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
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.AnalyticsData;
1517
import io.flutter.run.FlutterReloadManager;
1618
import io.flutter.run.daemon.FlutterApp;
1719
import org.jetbrains.annotations.NotNull;
@@ -42,9 +44,12 @@ public void actionPerformed(@NotNull AnActionEvent e) {
4244
return;
4345
}
4446

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

4954
var reloadManager = FlutterReloadManager.getInstance(project);
5055
if (reloadManager == null) return;
@@ -56,6 +61,8 @@ public void actionPerformed(@NotNull AnActionEvent e) {
5661
// Else perform a hot reload.
5762
reloadManager.saveAllAndReload(getApp(), FlutterConstants.RELOAD_REASON_MANUAL);
5863
}
64+
65+
Analytics.report(analyticsData);
5966
}
6067

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

src/io/flutter/actions/RestartFlutterApp.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
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.AnalyticsData;
1921
import io.flutter.bazel.WorkspaceCache;
2022
import io.flutter.run.FlutterReloadManager;
2123
import io.flutter.run.daemon.FlutterApp;
@@ -50,6 +52,8 @@ public void actionPerformed(@NotNull AnActionEvent e) {
5052
reloadManager.saveAllAndRestart(getApp(), FlutterConstants.RELOAD_REASON_MANUAL);
5153
}
5254

55+
var analyticsData = AnalyticsData.forAction(this, e);
56+
5357
if (WorkspaceCache.getInstance(project).isBazel() &&
5458
FlutterSettings.getInstance().isShowBazelHotRestartWarning() &&
5559
!FlutterSettings.getInstance().isEnableBazelHotRestart()) {
@@ -61,8 +65,12 @@ public void actionPerformed(@NotNull AnActionEvent e) {
6165
NotificationType.INFORMATION);
6266
Notifications.Bus.notify(notification, project);
6367

68+
analyticsData.add("google3", true);
69+
6470
// We only want to show this notification once.
6571
FlutterSettings.getInstance().setShowBazelHotRestartWarning(false);
6672
}
73+
74+
Analytics.report(analyticsData);
6775
}
6876
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
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) = reporter.report(data)
18+
}
19+
20+
abstract class AnalyticsReporter {
21+
22+
fun report(data: AnalyticsData) = data.reportTo(this)
23+
24+
internal abstract fun process(data: AnalyticsData)
25+
}
26+
27+
internal object PrintingReporter : AnalyticsReporter() {
28+
override fun process(data: AnalyticsData) = println(data.data)
29+
30+
}
31+
32+
internal object NoOpReporter : AnalyticsReporter() {
33+
override fun process(data: AnalyticsData) = Unit
34+
}
35+
36+
abstract class AnalyticsData(type: String) {
37+
val data = mutableMapOf<String, Any>()
38+
39+
init {
40+
add("type", type)
41+
}
42+
43+
companion object {
44+
@JvmStatic
45+
fun forAction(action: AnAction, event: AnActionEvent): ActionData = ActionData(
46+
event.actionManager.getId(action)
47+
// `FlutterAppAction`s aren't registered so ask them directly.
48+
?: (action as? FlutterAppAction)?.id,
49+
event.place
50+
)
51+
}
52+
53+
fun add(key: String, value: Boolean) {
54+
data[key] = value
55+
}
56+
57+
fun add(key: String, value: Int) {
58+
data[key] = value
59+
}
60+
61+
fun add(key: String, value: String) {
62+
data[key] = value
63+
}
64+
65+
open fun reportTo(reporter: AnalyticsReporter) = reporter.process(this)
66+
}
67+
68+
/**
69+
* Data describing an IntelliJ [com.intellij.openapi.actionSystem.AnAction] for analytics reporting.
70+
*
71+
* @param id The unique identifier of the action, typically defined in `plugin.xml`.
72+
* @param place The UI location where the action was invoked (e.g., "MainMenu", "Toolbar").
73+
* @see <a href="https://plugins.jetbrains.com/docs/intellij/basic-action-system.html">IntelliJ Action System</a>
74+
*/
75+
class ActionData(private val id: String?, private val place: String) : AnalyticsData("action") {
76+
77+
init {
78+
id?.let { add("id", it) }
79+
add("place", place)
80+
}
81+
82+
override fun reportTo(reporter: AnalyticsReporter) {
83+
// We only report if we have an id for the event.
84+
if (id == null) return
85+
super.reportTo(reporter)
86+
}
87+
}

0 commit comments

Comments
 (0)