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
5 changes: 3 additions & 2 deletions src/io/flutter/actions/FlutterSdkAction.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import io.flutter.FlutterMessages;
import io.flutter.FlutterUtils;
import io.flutter.analytics.Analytics;
import io.flutter.analytics.AnalyticsConstants;
import io.flutter.analytics.AnalyticsData;
import io.flutter.bazel.Workspace;
import io.flutter.pub.PubRoot;
Expand Down Expand Up @@ -42,7 +43,7 @@ public void actionPerformed(@NotNull AnActionEvent event) {
if (workspace != null) {
FileDocumentManager.getInstance().saveAllDocuments();
startCommandInBazelContext(project, workspace, event);
analyticsData.add("inBazelContext", true);
analyticsData.add(AnalyticsConstants.IN_BAZEL_CONTEXT, true);
Analytics.report(analyticsData);
return;
}
Expand All @@ -51,7 +52,7 @@ public void actionPerformed(@NotNull AnActionEvent event) {
final FlutterSdk sdk = project != null ? FlutterSdk.getFlutterSdk(project) : null;
if (sdk == null) {
showMissingSdkDialog(project);
analyticsData.add("missingSdk", true);
analyticsData.add(AnalyticsConstants.MISSING_SDK, true);
Analytics.report(analyticsData);
return;
}
Expand Down
5 changes: 3 additions & 2 deletions src/io/flutter/actions/ReloadFlutterApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import io.flutter.FlutterBundle;
import io.flutter.FlutterConstants;
import io.flutter.analytics.Analytics;
import io.flutter.analytics.AnalyticsConstants;
import io.flutter.analytics.AnalyticsData;
import io.flutter.run.FlutterReloadManager;
import io.flutter.run.daemon.FlutterApp;
Expand Down Expand Up @@ -49,7 +50,7 @@ public void actionPerformed(@NotNull AnActionEvent 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);
analyticsData.add(AnalyticsConstants.REQUIRES_RESTART, shouldRestart);

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

Analytics.report(analyticsData);
}

Expand Down
3 changes: 2 additions & 1 deletion src/io/flutter/actions/RestartFlutterApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import io.flutter.FlutterConstants;
import io.flutter.FlutterMessages;
import io.flutter.analytics.Analytics;
import io.flutter.analytics.AnalyticsConstants;
import io.flutter.analytics.AnalyticsData;
import io.flutter.bazel.WorkspaceCache;
import io.flutter.run.FlutterReloadManager;
Expand Down Expand Up @@ -65,7 +66,7 @@ public void actionPerformed(@NotNull AnActionEvent e) {
NotificationType.INFORMATION);
Notifications.Bus.notify(notification, project);

analyticsData.add("google3", true);
analyticsData.add(AnalyticsConstants.GOOGLE3, true);

// We only want to show this notification once.
FlutterSettings.getInstance().setShowBazelHotRestartWarning(false);
Expand Down
95 changes: 84 additions & 11 deletions src/io/flutter/analytics/Analytics.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,15 @@ object Analytics {
private val reporter = NoOpReporter

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

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() {
Expand All @@ -37,7 +33,7 @@ abstract class AnalyticsData(type: String) {
val data = mutableMapOf<String, Any>()

init {
add("type", type)
add(AnalyticsConstants.TYPE, type)
}

companion object {
Expand All @@ -50,15 +46,17 @@ abstract class AnalyticsData(type: String) {
)
}

fun add(key: String, value: Boolean) {
fun <T> add(key: DataValue<T>, value: T) = key.addTo(this, value)

internal operator fun set(key: String, value: Boolean) {
data[key] = value
}

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

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

Expand All @@ -75,8 +73,8 @@ abstract class AnalyticsData(type: String) {
class ActionData(private val id: String?, private val place: String) : AnalyticsData("action") {

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

override fun reportTo(reporter: AnalyticsReporter) {
Expand All @@ -85,3 +83,78 @@ class ActionData(private val id: String?, private val place: String) : Analytics
super.reportTo(reporter)
}
}

/**
* Defines standard keys for analytics data properties.
*
* The properties are exposed as `@JvmField`s to be easily accessible as static
* fields from Java.
*/
object AnalyticsConstants {
/**
* Indicates if the project is a Google3 project.
*/
@JvmField
val GOOGLE3 = BooleanValue("google3")

/**
* The unique identifier for an action or event.
*/
@JvmField
val ID = StringValue("id")

/**
* Indicates if the project is in a Bazel context.
*/
@JvmField
val IN_BAZEL_CONTEXT = BooleanValue("inBazelContext")

/**
* Indicates if the Flutter SDK is missing.
*/
@JvmField
val MISSING_SDK = BooleanValue("missingSdk")

/**
* The UI location where an action was invoked, as provided by
* [com.intellij.openapi.actionSystem.PlaceProvider.getPlace] (for example, "MainMenu",
* "MainToolbar", "EditorPopup", "GoToAction", etc).
*/
@JvmField
val PLACE = StringValue("place")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might expect these to be namespaced, e.g. ACTION_PLACE or AnalyticsConstants.Actions.PLACE

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My sense is that's unneeded since action-ness is encoded in the TYPE property already? Anyway, for sure, let's talk about it and do a full audit before we start actually capturing any data. Thanks!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah true! Yes, sgtm


/**
* Indicates if a restart is required for a hot reload request.
*/
@JvmField
val REQUIRES_RESTART = BooleanValue("requiresRestart")

/**
* The type of the analytics event (e.g., "action", ...).
*/
@JvmField
val TYPE = StringValue("type")
}


sealed class DataValue<T>(val name: String) {
abstract fun addTo(data: AnalyticsData, value: T);
}

class StringValue(name: String) : DataValue<String>(name) {
override fun addTo(data: AnalyticsData, value: String) {
data[name] = value
}
}

class IntValue(name: String) : DataValue<Int>(name) {
override fun addTo(data: AnalyticsData, value: Int) {
data[name] = value
}
}

class BooleanValue(name: String) : DataValue<Boolean>(name) {
override fun addTo(data: AnalyticsData, value: Boolean) {
data[name] = value
}
}