Skip to content
Draft
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
193 changes: 193 additions & 0 deletions crates/utils/re_analytics/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,8 @@ pub struct OpenRecording {

pub store_info: Option<StoreInfo>,

pub total_open_recordings: usize,

/// How data is being loaded into the viewer.
pub data_source: Option<&'static str>,
}
Expand All @@ -360,6 +362,7 @@ impl Properties for OpenRecording {
url,
app_env,
store_info,
total_open_recordings,
data_source,
} = self;

Expand All @@ -384,6 +387,7 @@ impl Properties for OpenRecording {
event.insert("recording_id", recording_id);
event.insert("store_source", store_source);
event.insert("store_version", store_version);
event.insert("total_open_recordings", total_open_recordings as i64);
event.insert_opt("rust_version", rust_version);
event.insert_opt("llvm_version", llvm_version);
event.insert_opt("python_version", python_version);
Expand All @@ -401,6 +405,51 @@ impl Properties for OpenRecording {

// -----------------------------------------------

/// Sent when user switches between existing recordings.
///
/// Used in `re_viewer`.
pub struct SwitchRecording {
/// The URL on which the web viewer is running.
///
/// This will be used to populate `hashed_root_domain` property for all urls.
/// This will also populate `rerun_url` property if the url root domain is `rerun.io`.
pub url: Option<String>,

/// The environment in which the viewer is running.
pub app_env: &'static str,

/// The recording we're switching from (hashed if not official example).
pub previous_recording_id: Option<Id>,

/// The recording we're switching to (hashed if not official example).
pub new_recording_id: Id,

/// How the switch was initiated.
pub switch_method: &'static str,
}

impl Event for SwitchRecording {
const NAME: &'static str = "switch_recording";
}

impl Properties for SwitchRecording {
fn serialize(self, event: &mut AnalyticsEvent) {
let Self {
url,
app_env,
previous_recording_id,
new_recording_id,
switch_method,
} = self;

add_sanitized_url_properties(event, url);
event.insert("app_env", app_env);
event.insert_opt("previous_recording_id", previous_recording_id);
event.insert("new_recording_id", new_recording_id);
event.insert("switch_method", switch_method);
}
}

// -----------------------------------------------

/// Sent the first time a `?` help button is clicked.
Expand All @@ -420,6 +469,150 @@ impl Properties for HelpButtonFirstClicked {

// -----------------------------------------------

/// Tracks how much timeline content was played back.
///
/// Emitted when playback stops for any reason.
pub struct TimelineSecondsPlayed {
pub build_info: BuildInfo,

/// Name of the timeline (e.g. "log_time", "sim_time")
pub timeline_name: String,

/// Playback speed during this session (1.0 = normal, 2.0 = 2x speed)
pub playback_speed: f32,

/// Timeline duration advanced during playback (in seconds)
pub timeline_seconds_played: f64,

/// Real-world time elapsed during playback (in seconds)
pub wall_clock_seconds: f64,

/// Why playback stopped
pub stop_reason: PlaybackStopReason,

/// Which recording was being played (hashed if not official example)
pub recording_id: Id,
}

/// Reason why a playback session ended
#[derive(Clone, Debug)]
pub enum PlaybackStopReason {
/// User manually paused/stopped playback
UserStopped,
/// Playback speed changed (starts new session)
SpeedChanged,
/// Switched to different recording
RecordingSwitched,
/// Recording was closed
RecordingClosed,
/// App is exiting
AppExited,
/// Switched to different timeline
TimelineChanged,
}

impl Event for TimelineSecondsPlayed {
const NAME: &'static str = "timeline_seconds_played";
}

impl Properties for TimelineSecondsPlayed {
fn serialize(self, event: &mut AnalyticsEvent) {
let Self {
build_info,
timeline_name,
playback_speed,
timeline_seconds_played,
wall_clock_seconds,
stop_reason,
recording_id,
} = self;

build_info.serialize(event);
event.insert("timeline_name", timeline_name);
event.insert("playback_speed", playback_speed);
event.insert("timeline_seconds_played", timeline_seconds_played);
event.insert("wall_clock_seconds", wall_clock_seconds);
event.insert("stop_reason", format!("{:?}", stop_reason));
event.insert("recording_id", recording_id);
}
}

// -----------------------------------------------

/// Tracks user interaction with timeline viewing, including playback and scrubbing behavior.
///
/// Emitted when a viewing session ends (playback stops, scrubbing session ends, etc).
pub struct PlaybackSession {
pub build_info: BuildInfo,

/// Name of the timeline (e.g. "log_time", "sim_time")
pub timeline_name: String,

/// Time spent in this viewing session (in seconds)
pub wall_clock_seconds: f64,

/// Type of viewing behavior in this session
pub session_type: PlaybackSessionType,

/// Total timeline distance traveled (sum of all movements, including backwards)
pub total_time_traveled: f64,

/// Covered timeline distance (max - min of time range visited)
pub covered_time_distance: f64,

/// Unit of the timeline measurements ("seconds" or "frames")
pub time_unit: String,

/// Why the session ended
pub end_reason: PlaybackStopReason,

/// Which recording was being viewed (hashed if not official example)
pub recording_id: Id,
}

/// Type of playback/viewing session
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum PlaybackSessionType {
/// Continuous playback at a consistent speed
Playback,
/// User manually scrubbing through the timeline
Scrubbing,
/// Mixed session with both playback and scrubbing
Mixed,
}

impl Event for PlaybackSession {
const NAME: &'static str = "playback_session";
}

impl Properties for PlaybackSession {
fn serialize(self, event: &mut AnalyticsEvent) {
let Self {
build_info,
timeline_name,
wall_clock_seconds,
session_type,
total_time_traveled,
covered_time_distance,
time_unit,
end_reason,
recording_id,
} = self;

build_info.serialize(event);
event.insert("timeline_name", timeline_name);
event.insert("wall_clock_seconds", wall_clock_seconds);
event.insert("session_type", format!("{:?}", session_type));
event.insert("total_time_traveled", total_time_traveled);
event.insert("covered_time_distance", covered_time_distance);
event.insert("time_unit", time_unit);
event.insert("end_reason", format!("{:?}", end_reason));
event.insert("recording_id", recording_id);
}
}

// -----------------------------------------------

/// The user opened the settings screen.
pub struct SettingsOpened {}

Expand Down
17 changes: 11 additions & 6 deletions crates/viewer/re_data_ui/src/item_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use re_ui::list_item::ListItemContentButtonsExt as _;
use re_ui::{SyntaxHighlighting as _, UiExt as _, icons, list_item};
use re_viewer_context::open_url::ViewerOpenUrl;
use re_viewer_context::{
HoverHighlight, Item, SystemCommand, SystemCommandSender as _, UiLayout, ViewId, ViewerContext,
HoverHighlight, Item, UiLayout, ViewId, ViewerContext,
};

use super::DataUi as _;
Expand Down Expand Up @@ -687,7 +687,7 @@ pub fn entity_db_button_ui(
include_app_id: bool,
) {
use re_byte_size::SizeBytes as _;
use re_viewer_context::{SystemCommand, SystemCommandSender as _};
use re_viewer_context::{ActivationSource, SystemCommand, SystemCommandSender as _};

let app_id_prefix = if include_app_id {
format!("{} - ", entity_db.application_id())
Expand Down Expand Up @@ -807,7 +807,10 @@ pub fn entity_db_button_ui(
// for the blueprint.
if store_id.is_recording() {
ctx.command_sender()
.send_system(SystemCommand::ActivateRecordingOrTable(new_entry));
.send_system(SystemCommand::ActivateRecordingOrTable {
entry: new_entry,
source: ActivationSource::UiClick,
});
}
}

Expand All @@ -820,6 +823,7 @@ pub fn table_id_button_ui(
table_id: &TableId,
ui_layout: UiLayout,
) {
use re_viewer_context::{ActivationSource, SystemCommand, SystemCommandSender as _};
let item = re_viewer_context::Item::TableId(table_id.clone());

let mut item_content = list_item::LabelContent::new(table_id.as_str()).with_icon(&icons::TABLE);
Expand Down Expand Up @@ -862,9 +866,10 @@ pub fn table_id_button_ui(

if response.clicked() {
ctx.command_sender()
.send_system(SystemCommand::ActivateRecordingOrTable(
table_id.clone().into(),
));
.send_system(SystemCommand::ActivateRecordingOrTable {
entry: table_id.clone().into(),
source: ActivationSource::TableClick,
});
}
ctx.handle_select_hover_drag_interactions(&response, item, false);
}
28 changes: 27 additions & 1 deletion crates/viewer/re_global_context/src/command_sender.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,29 @@ use crate::RecordingOrTable;

// ----------------------------------------------------------------------------

/// How a recording was activated.
#[derive(Clone, Debug)]
pub enum ActivationSource {
/// User clicked in the UI
UiClick,
/// Keyboard shortcut
Hotkey(String),
/// Via URL parameter or fragment
Url,
/// File was opened by the viewer (either drag + drop, or File > Open)
FileOpen,
/// CLI argument, e.g. `rerun dna.rrd`
CliArgument,
/// Clicked from table view
TableClick,
/// Auto-activation (first recording, only available recording, etc.)
Auto,
/// Programmatic activation
Api,
}

// ----------------------------------------------------------------------------

/// Commands used by internal system components
// TODO(jleibs): Is there a better crate for this?
#[derive(strum_macros::IntoStaticStr)]
Expand Down Expand Up @@ -56,7 +79,10 @@ pub enum SystemCommand {
ClearActiveBlueprintAndEnableHeuristics,

/// Switch to this [`RecordingOrTable`].
ActivateRecordingOrTable(RecordingOrTable),
ActivateRecordingOrTable {
entry: RecordingOrTable,
source: ActivationSource,
},

/// Close an [`RecordingOrTable`] and free its memory.
CloseRecordingOrTable(RecordingOrTable),
Expand Down
2 changes: 1 addition & 1 deletion crates/viewer/re_global_context/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub use self::{
app_options::AppOptions,
blueprint_id::{BlueprintId, BlueprintIdRegistry, ContainerId, ViewId},
command_sender::{
CommandReceiver, CommandSender, SystemCommand, SystemCommandSender, command_channel,
ActivationSource, CommandReceiver, CommandSender, SystemCommand, SystemCommandSender, command_channel,
},
contents::{Contents, ContentsName, blueprint_id_to_tile_id},
file_dialog::santitize_file_name,
Expand Down
2 changes: 1 addition & 1 deletion crates/viewer/re_test_context/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,7 @@ impl TestContext {

// not implemented
SystemCommand::ActivateApp(_)
| SystemCommand::ActivateRecordingOrTable(_)
| SystemCommand::ActivateRecordingOrTable { .. }
| SystemCommand::CloseApp(_)
| SystemCommand::CloseRecordingOrTable(_)
| SystemCommand::LoadDataSource(_)
Expand Down
Loading
Loading