Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/io/flutter/actions/FlutterAppAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,8 @@ public void update(@NotNull final AnActionEvent e) {
public FlutterApp getApp() {
return myApp;
}

public @NotNull String getId() {
return myActionId;
}
}
10 changes: 10 additions & 0 deletions src/io/flutter/actions/FlutterSdkAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import io.flutter.FlutterBundle;
import io.flutter.FlutterMessages;
import io.flutter.FlutterUtils;
import io.flutter.analytics.Analytics;
import io.flutter.analytics.AnalyticsData;
import io.flutter.bazel.Workspace;
import io.flutter.pub.PubRoot;
import io.flutter.pub.PubRoots;
Expand All @@ -32,19 +34,25 @@ public abstract class FlutterSdkAction extends DumbAwareAction {
public void actionPerformed(@NotNull AnActionEvent event) {
final Project project = DumbAwareAction.getEventProject(event);

AnalyticsData analyticsData = AnalyticsData.forAction(this, event);

if (enableActionInBazelContext()) {
// See if the Bazel workspace exists for this project.
final Workspace workspace = FlutterModuleUtils.getFlutterBazelWorkspace(project);
if (workspace != null) {
FileDocumentManager.getInstance().saveAllDocuments();
startCommandInBazelContext(project, workspace, event);
analyticsData.add("inBazelContext", true);
Analytics.report(analyticsData);
return;
}
}

final FlutterSdk sdk = project != null ? FlutterSdk.getFlutterSdk(project) : null;
if (sdk == null) {
showMissingSdkDialog(project);
analyticsData.add("missingSdk", true);
Analytics.report(analyticsData);
return;
}

Expand All @@ -60,6 +68,8 @@ public void actionPerformed(@NotNull AnActionEvent event) {
startCommand(project, sdk, sub, context);
}
}

Analytics.report(analyticsData);
}

public abstract void startCommand(@NotNull Project project,
Expand Down
7 changes: 7 additions & 0 deletions src/io/flutter/actions/ReloadFlutterApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import icons.FlutterIcons;
import io.flutter.FlutterBundle;
import io.flutter.FlutterConstants;
import io.flutter.analytics.Analytics;
import io.flutter.analytics.AnalyticsData;
import io.flutter.run.FlutterReloadManager;
import io.flutter.run.daemon.FlutterApp;
import org.jetbrains.annotations.NotNull;
Expand Down Expand Up @@ -42,9 +44,12 @@ public void actionPerformed(@NotNull AnActionEvent e) {
return;
}

var analyticsData = AnalyticsData.forAction(this, e);

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

var reloadManager = FlutterReloadManager.getInstance(project);
if (reloadManager == null) return;
Expand All @@ -56,6 +61,8 @@ public void actionPerformed(@NotNull AnActionEvent e) {
// Else perform a hot reload.
reloadManager.saveAllAndReload(getApp(), FlutterConstants.RELOAD_REASON_MANUAL);
}

Analytics.report(analyticsData);
}

// Override to disable the hot reload action when running flutter web apps.
Expand Down
8 changes: 8 additions & 0 deletions src/io/flutter/actions/RestartFlutterApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import io.flutter.FlutterBundle;
import io.flutter.FlutterConstants;
import io.flutter.FlutterMessages;
import io.flutter.analytics.Analytics;
import io.flutter.analytics.AnalyticsData;
import io.flutter.bazel.WorkspaceCache;
import io.flutter.run.FlutterReloadManager;
import io.flutter.run.daemon.FlutterApp;
Expand Down Expand Up @@ -50,6 +52,8 @@ public void actionPerformed(@NotNull AnActionEvent e) {
reloadManager.saveAllAndRestart(getApp(), FlutterConstants.RELOAD_REASON_MANUAL);
}

var analyticsData = AnalyticsData.forAction(this, e);

if (WorkspaceCache.getInstance(project).isBazel() &&
FlutterSettings.getInstance().isShowBazelHotRestartWarning() &&
!FlutterSettings.getInstance().isEnableBazelHotRestart()) {
Expand All @@ -61,8 +65,12 @@ public void actionPerformed(@NotNull AnActionEvent e) {
NotificationType.INFORMATION);
Notifications.Bus.notify(notification, project);

analyticsData.add("google3", true);

// We only want to show this notification once.
FlutterSettings.getInstance().setShowBazelHotRestartWarning(false);
}

Analytics.report(analyticsData);
}
}
87 changes: 87 additions & 0 deletions src/io/flutter/analytics/Analytics.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright 2025 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/

package io.flutter.analytics

import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import io.flutter.actions.FlutterAppAction

object Analytics {
private val reporter = NoOpReporter

@JvmStatic
fun report(data: AnalyticsData) = reporter.report(data)
}

abstract class AnalyticsReporter {

fun report(data: AnalyticsData) = data.reportTo(this)

internal abstract fun process(data: AnalyticsData)
}

internal object PrintingReporter : AnalyticsReporter() {
override fun process(data: AnalyticsData) = println(data.data)

}

internal object NoOpReporter : AnalyticsReporter() {
override fun process(data: AnalyticsData) = Unit
}

abstract class AnalyticsData(type: String) {
val data = mutableMapOf<String, Any>()

init {
add("type", type)
}

companion object {
@JvmStatic
fun forAction(action: AnAction, event: AnActionEvent): ActionData = ActionData(
event.actionManager.getId(action)
// `FlutterAppAction`s aren't registered so ask them directly.
?: (action as? FlutterAppAction)?.id,
event.place
)
}

fun add(key: String, value: Boolean) {
data[key] = value
}

fun add(key: String, value: Int) {
data[key] = value
}

fun add(key: String, value: String) {
data[key] = value
}

open fun reportTo(reporter: AnalyticsReporter) = reporter.process(this)
}

/**
* Data describing an IntelliJ [com.intellij.openapi.actionSystem.AnAction] for analytics reporting.
*
* @param id The unique identifier of the action, typically defined in `plugin.xml`.
* @param place The UI location where the action was invoked (e.g., "MainMenu", "Toolbar").
* @see <a href="https://plugins.jetbrains.com/docs/intellij/basic-action-system.html">IntelliJ Action System</a>
*/
class ActionData(private val id: String?, private val place: String) : AnalyticsData("action") {

init {
id?.let { add("id", it) }
add("place", place)
}

override fun reportTo(reporter: AnalyticsReporter) {
// We only report if we have an id for the event.
if (id == null) return
super.reportTo(reporter)
}
}