Remove analytics and add more update headers

This commit is contained in:
Gregory Schier
2025-02-24 06:31:49 -08:00
parent af7782c93b
commit 05ac836265
62 changed files with 146 additions and 519 deletions

View File

@@ -1,5 +0,0 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type AnalyticsAction = "cancel" | "click" | "commit" | "create" | "delete" | "delete_many" | "duplicate" | "error" | "export" | "hide" | "import" | "launch" | "launch_first" | "launch_update" | "send" | "show" | "toggle" | "update" | "upsert";
export type AnalyticsResource = "app" | "appearance" | "button" | "checkbox" | "cookie_jar" | "dialog" | "environment" | "folder" | "grpc_connection" | "grpc_event" | "grpc_request" | "http_request" | "http_response" | "key_value" | "link" | "mutation" | "plugin" | "select" | "setting" | "sidebar" | "tab" | "theme" | "websocket_connection" | "websocket_event" | "websocket_request" | "workspace";

View File

@@ -1,247 +0,0 @@
use std::fmt::Display;
use log::{debug, info};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use tauri::{Manager, Runtime, WebviewWindow};
use ts_rs::TS;
use yaak_models::queries::{
generate_id, get_key_value_int, get_key_value_string, get_or_create_settings,
set_key_value_int, set_key_value_string, UpdateSource,
};
use crate::is_dev;
const NAMESPACE: &str = "analytics";
const NUM_LAUNCHES_KEY: &str = "num_launches";
// serializable
#[derive(Serialize, Deserialize, Debug, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export, export_to = "analytics.ts")]
pub enum AnalyticsResource {
App,
Appearance,
Button,
Checkbox,
CookieJar,
Dialog,
Environment,
Folder,
GrpcConnection,
GrpcEvent,
GrpcRequest,
HttpRequest,
HttpResponse,
KeyValue,
Link,
Mutation,
Plugin,
Select,
Setting,
Sidebar,
Tab,
Theme,
WebsocketConnection,
WebsocketEvent,
WebsocketRequest,
Workspace,
}
impl AnalyticsResource {
pub fn from_str(s: &str) -> serde_json::Result<AnalyticsResource> {
serde_json::from_str(format!("\"{s}\"").as_str())
}
}
impl Display for AnalyticsResource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", serde_json::to_string(self).unwrap().replace("\"", ""))
}
}
#[derive(Serialize, Deserialize, Debug, TS)]
#[serde(rename_all = "snake_case")]
#[ts(export, export_to = "analytics.ts")]
pub enum AnalyticsAction {
Cancel,
Click,
Commit,
Create,
Delete,
DeleteMany,
Duplicate,
Error,
Export,
Hide,
Import,
Launch,
LaunchFirst,
LaunchUpdate,
Send,
Show,
Toggle,
Update,
Upsert,
}
impl AnalyticsAction {
pub fn from_str(s: &str) -> serde_json::Result<AnalyticsAction> {
serde_json::from_str(format!("\"{s}\"").as_str())
}
}
impl Display for AnalyticsAction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", serde_json::to_string(self).unwrap().replace("\"", ""))
}
}
#[derive(Default, Debug)]
pub struct LaunchEventInfo {
pub current_version: String,
pub previous_version: String,
pub launched_after_update: bool,
pub num_launches: i32,
}
pub async fn track_launch_event<R: Runtime>(w: &WebviewWindow<R>) -> LaunchEventInfo {
let last_tracked_version_key = "last_tracked_version";
let mut info = LaunchEventInfo::default();
info.num_launches = get_num_launches(w).await + 1;
info.previous_version = get_key_value_string(w, NAMESPACE, last_tracked_version_key, "").await;
info.current_version = w.package_info().version.to_string();
if info.previous_version.is_empty() {
track_event(w, AnalyticsResource::App, AnalyticsAction::LaunchFirst, None).await;
} else {
info.launched_after_update = info.current_version != info.previous_version;
if info.launched_after_update {
track_event(
w,
AnalyticsResource::App,
AnalyticsAction::LaunchUpdate,
Some(json!({ NUM_LAUNCHES_KEY: info.num_launches })),
)
.await;
}
};
// Track a launch event in all cases
track_event(
w,
AnalyticsResource::App,
AnalyticsAction::Launch,
Some(json!({ NUM_LAUNCHES_KEY: info.num_launches })),
)
.await;
// Update key values
set_key_value_string(
w,
NAMESPACE,
last_tracked_version_key,
info.current_version.as_str(),
&UpdateSource::Background,
)
.await;
set_key_value_int(w, NAMESPACE, NUM_LAUNCHES_KEY, info.num_launches, &UpdateSource::Background)
.await;
info
}
pub async fn track_event<R: Runtime>(
w: &WebviewWindow<R>,
resource: AnalyticsResource,
action: AnalyticsAction,
attributes: Option<Value>,
) {
let id = get_id(w).await;
let event = format!("{}.{}", resource, action);
let attributes_json = attributes.unwrap_or("{}".to_string().into()).to_string();
let info = w.app_handle().package_info();
let tz = datetime::sys_timezone().unwrap_or("unknown".to_string());
let site = match is_dev() {
true => "site_TkHWjoXwZPq3HfhERb",
false => "site_zOK0d7jeBy2TLxFCnZ",
};
let base_url = match is_dev() {
true => "http://localhost:7194",
false => "https://t.yaak.app",
};
let params = vec![
("u", id),
("e", event.clone()),
("a", attributes_json.clone()),
("id", site.to_string()),
("v", info.version.clone().to_string()),
("os", get_os().to_string()),
("tz", tz),
("xy", get_window_size(w)),
];
let req =
reqwest::Client::builder().build().unwrap().get(format!("{base_url}/t/e")).query(&params);
let settings = get_or_create_settings(w).await;
if !settings.telemetry {
info!("Track event (disabled): {}", event);
return;
}
// Disable analytics actual sending in dev
if is_dev() {
debug!("Track event: {} {}", event, attributes_json);
return;
}
if let Err(e) = req.send().await {
info!("Error sending analytics event: {}", e);
}
}
pub fn get_os() -> &'static str {
if cfg!(target_os = "windows") {
"windows"
} else if cfg!(target_os = "macos") {
"macos"
} else if cfg!(target_os = "linux") {
"linux"
} else {
"unknown"
}
}
fn get_window_size<R: Runtime>(w: &WebviewWindow<R>) -> String {
let current_monitor = match w.current_monitor() {
Ok(Some(m)) => m,
_ => return "unknown".to_string(),
};
let scale_factor = current_monitor.scale_factor();
let size = current_monitor.size();
let width: f64 = size.width as f64 / scale_factor;
let height: f64 = size.height as f64 / scale_factor;
format!("{}x{}", (width / 100.0).round() * 100.0, (height / 100.0).round() * 100.0)
}
async fn get_id<R: Runtime>(w: &WebviewWindow<R>) -> String {
let id = get_key_value_string(w, "analytics", "id", "").await;
if id.is_empty() {
let new_id = generate_id();
set_key_value_string(w, "analytics", "id", new_id.as_str(), &UpdateSource::Background)
.await;
new_id
} else {
id
}
}
pub async fn get_num_launches<R: Runtime>(w: &WebviewWindow<R>) -> i32 {
get_key_value_int(w, NAMESPACE, NUM_LAUNCHES_KEY, 0).await
}

64
src-tauri/src/history.rs Normal file
View File

@@ -0,0 +1,64 @@
use tauri::{Manager, Runtime, WebviewWindow};
use yaak_models::queries::{
get_key_value_int, get_key_value_string,
set_key_value_int, set_key_value_string, UpdateSource,
};
const NAMESPACE: &str = "analytics";
const NUM_LAUNCHES_KEY: &str = "num_launches";
#[derive(Default, Debug)]
pub struct LaunchEventInfo {
pub current_version: String,
pub previous_version: String,
pub launched_after_update: bool,
pub num_launches: i32,
}
pub async fn store_launch_history<R: Runtime>(w: &WebviewWindow<R>) -> LaunchEventInfo {
let last_tracked_version_key = "last_tracked_version";
let mut info = LaunchEventInfo::default();
info.num_launches = get_num_launches(w).await + 1;
info.previous_version = get_key_value_string(w, NAMESPACE, last_tracked_version_key, "").await;
info.current_version = w.package_info().version.to_string();
if info.previous_version.is_empty() {
} else {
info.launched_after_update = info.current_version != info.previous_version;
};
// Update key values
set_key_value_string(
w,
NAMESPACE,
last_tracked_version_key,
info.current_version.as_str(),
&UpdateSource::Background,
)
.await;
set_key_value_int(w, NAMESPACE, NUM_LAUNCHES_KEY, info.num_launches, &UpdateSource::Background)
.await;
info
}
pub fn get_os() -> &'static str {
if cfg!(target_os = "windows") {
"windows"
} else if cfg!(target_os = "macos") {
"macos"
} else if cfg!(target_os = "linux") {
"linux"
} else {
"unknown"
}
}
pub async fn get_num_launches<R: Runtime>(w: &WebviewWindow<R>) -> i32 {
get_key_value_int(w, NAMESPACE, NUM_LAUNCHES_KEY, 0).await
}

View File

@@ -1,13 +1,12 @@
extern crate core; extern crate core;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
extern crate objc; extern crate objc;
use crate::analytics::{AnalyticsAction, AnalyticsResource};
use crate::encoding::read_response_body; use crate::encoding::read_response_body;
use crate::grpc::metadata_to_map; use crate::grpc::metadata_to_map;
use crate::http_request::send_http_request; use crate::http_request::send_http_request;
use crate::notifications::YaakNotifier; use crate::notifications::YaakNotifier;
use crate::render::{render_grpc_request, render_template}; use crate::render::{render_grpc_request, render_template};
use crate::updates::{UpdateMode, YaakUpdater}; use crate::updates::{UpdateMode, UpdateTrigger, YaakUpdater};
use eventsource_client::{EventParser, SSE}; use eventsource_client::{EventParser, SSE};
use log::{debug, error, warn}; use log::{debug, error, warn};
use rand::random; use rand::random;
@@ -30,8 +29,30 @@ use tokio::sync::Mutex;
use tokio::task::block_in_place; use tokio::task::block_in_place;
use yaak_grpc::manager::{DynamicMessage, GrpcHandle}; use yaak_grpc::manager::{DynamicMessage, GrpcHandle};
use yaak_grpc::{deserialize_message, serialize_message, Code, ServiceDefinition}; use yaak_grpc::{deserialize_message, serialize_message, Code, ServiceDefinition};
use yaak_models::models::{CookieJar, Environment, EnvironmentVariable, Folder, GrpcConnection, GrpcConnectionState, GrpcEvent, GrpcEventType, GrpcRequest, HttpRequest, HttpResponse, HttpResponseState, KeyValue, ModelType, Plugin, Settings, WebsocketRequest, Workspace, WorkspaceMeta}; use yaak_models::models::{
use yaak_models::queries::{batch_upsert, cancel_pending_grpc_connections, cancel_pending_responses, create_default_http_response, delete_all_grpc_connections, delete_all_grpc_connections_for_workspace, delete_all_http_responses_for_request, delete_all_http_responses_for_workspace, delete_all_websocket_connections_for_workspace, delete_cookie_jar, delete_environment, delete_folder, delete_grpc_connection, delete_grpc_request, delete_http_request, delete_http_response, delete_plugin, delete_workspace, duplicate_folder, duplicate_grpc_request, duplicate_http_request, ensure_base_environment, generate_model_id, get_base_environment, get_cookie_jar, get_environment, get_folder, get_grpc_connection, get_grpc_request, get_http_request, get_http_response, get_key_value_raw, get_or_create_settings, get_or_create_workspace_meta, get_plugin, get_workspace, get_workspace_export_resources, list_cookie_jars, list_environments, list_folders, list_grpc_connections_for_workspace, list_grpc_events, list_grpc_requests, list_http_requests, list_http_responses_for_workspace, list_key_values_raw, list_plugins, list_workspaces, set_key_value_raw, update_response_if_id, update_settings, upsert_cookie_jar, upsert_environment, upsert_folder, upsert_grpc_connection, upsert_grpc_event, upsert_grpc_request, upsert_http_request, upsert_plugin, upsert_workspace, upsert_workspace_meta, BatchUpsertResult, UpdateSource}; CookieJar, Environment, EnvironmentVariable, Folder, GrpcConnection, GrpcConnectionState,
GrpcEvent, GrpcEventType, GrpcRequest, HttpRequest, HttpResponse, HttpResponseState, KeyValue,
ModelType, Plugin, Settings, WebsocketRequest, Workspace, WorkspaceMeta,
};
use yaak_models::queries::{
batch_upsert, cancel_pending_grpc_connections, cancel_pending_responses,
create_default_http_response, delete_all_grpc_connections,
delete_all_grpc_connections_for_workspace, delete_all_http_responses_for_request,
delete_all_http_responses_for_workspace, delete_all_websocket_connections_for_workspace,
delete_cookie_jar, delete_environment, delete_folder, delete_grpc_connection,
delete_grpc_request, delete_http_request, delete_http_response, delete_plugin,
delete_workspace, duplicate_folder, duplicate_grpc_request, duplicate_http_request,
ensure_base_environment, generate_model_id, get_base_environment, get_cookie_jar,
get_environment, get_folder, get_grpc_connection, get_grpc_request, get_http_request,
get_http_response, get_key_value_raw, get_or_create_settings, get_or_create_workspace_meta,
get_plugin, get_workspace, get_workspace_export_resources, list_cookie_jars, list_environments,
list_folders, list_grpc_connections_for_workspace, list_grpc_events, list_grpc_requests,
list_http_requests, list_http_responses_for_workspace, list_key_values_raw, list_plugins,
list_workspaces, set_key_value_raw, update_response_if_id, update_settings, upsert_cookie_jar,
upsert_environment, upsert_folder, upsert_grpc_connection, upsert_grpc_event,
upsert_grpc_request, upsert_http_request, upsert_plugin, upsert_workspace,
upsert_workspace_meta, BatchUpsertResult, UpdateSource,
};
use yaak_plugins::events::{ use yaak_plugins::events::{
BootResponse, CallHttpAuthenticationRequest, CallHttpRequestActionRequest, FilterResponse, BootResponse, CallHttpAuthenticationRequest, CallHttpRequestActionRequest, FilterResponse,
GetHttpAuthenticationConfigResponse, GetHttpAuthenticationSummaryResponse, GetHttpAuthenticationConfigResponse, GetHttpAuthenticationSummaryResponse,
@@ -44,9 +65,9 @@ use yaak_sse::sse::ServerSentEvent;
use yaak_templates::format::format_json; use yaak_templates::format::format_json;
use yaak_templates::{Parser, Tokens}; use yaak_templates::{Parser, Tokens};
mod analytics;
mod encoding; mod encoding;
mod grpc; mod grpc;
mod history;
mod http_request; mod http_request;
mod notifications; mod notifications;
mod plugin_events; mod plugin_events;
@@ -807,7 +828,7 @@ async fn cmd_import_data<R: Runtime>(
.await .await
.unwrap_or_else(|_| panic!("Unable to read file {}", file_path)); .unwrap_or_else(|_| panic!("Unable to read file {}", file_path));
let file_contents = file.as_str(); let file_contents = file.as_str();
let (import_result, plugin_name) = let import_result =
plugin_manager.import_data(&window, file_contents).await.map_err(|e| e.to_string())?; plugin_manager.import_data(&window, file_contents).await.map_err(|e| e.to_string())?;
let mut id_map: BTreeMap<String, String> = BTreeMap::new(); let mut id_map: BTreeMap<String, String> = BTreeMap::new();
@@ -923,14 +944,6 @@ async fn cmd_import_data<R: Runtime>(
.await .await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
analytics::track_event(
&window,
AnalyticsResource::App,
AnalyticsAction::Import,
Some(json!({ "plugin": plugin_name })),
)
.await;
Ok(upserted) Ok(upserted)
} }
@@ -1007,17 +1020,9 @@ async fn cmd_curl_to_request<R: Runtime>(
plugin_manager: State<'_, PluginManager>, plugin_manager: State<'_, PluginManager>,
workspace_id: &str, workspace_id: &str,
) -> Result<HttpRequest, String> { ) -> Result<HttpRequest, String> {
let (import_result, plugin_name) = let import_result =
{ plugin_manager.import_data(&window, command).await.map_err(|e| e.to_string())? }; { plugin_manager.import_data(&window, command).await.map_err(|e| e.to_string())? };
analytics::track_event(
&window,
AnalyticsResource::App,
AnalyticsAction::Import,
Some(json!({ "plugin": plugin_name })),
)
.await;
import_result.resources.http_requests.get(0).ok_or("No curl command found".to_string()).map( import_result.resources.http_requests.get(0).ok_or("No curl command found".to_string()).map(
|r| { |r| {
let mut request = r.clone(); let mut request = r.clone();
@@ -1051,8 +1056,6 @@ async fn cmd_export_data(
f.sync_all().expect("Failed to sync"); f.sync_all().expect("Failed to sync");
analytics::track_event(&window, AnalyticsResource::App, AnalyticsAction::Export, None).await;
Ok(()) Ok(())
} }
@@ -1131,28 +1134,6 @@ async fn response_err<R: Runtime>(
response response
} }
#[tauri::command]
async fn cmd_track_event(
window: WebviewWindow,
resource: &str,
action: &str,
attributes: Option<Value>,
) -> Result<(), String> {
match (AnalyticsResource::from_str(resource), AnalyticsAction::from_str(action)) {
(Ok(resource), Ok(action)) => {
analytics::track_event(&window, resource, action, attributes).await;
}
(r, a) => {
error!(
"Invalid action/resource for track_event: {resource}.{action} = {:?}.{:?}",
r, a
);
return Err("Invalid analytics event".to_string());
}
};
Ok(())
}
#[tauri::command] #[tauri::command]
async fn cmd_set_update_mode(update_mode: &str, w: WebviewWindow) -> Result<KeyValue, String> { async fn cmd_set_update_mode(update_mode: &str, w: WebviewWindow) -> Result<KeyValue, String> {
cmd_set_key_value("app", "update_mode", update_mode, w).await.map_err(|e| e.to_string()) cmd_set_key_value("app", "update_mode", update_mode, w).await.map_err(|e| e.to_string())
@@ -1739,7 +1720,12 @@ async fn cmd_check_for_updates(
yaak_updater: State<'_, Mutex<YaakUpdater>>, yaak_updater: State<'_, Mutex<YaakUpdater>>,
) -> Result<bool, String> { ) -> Result<bool, String> {
let update_mode = get_update_mode(&app_handle).await; let update_mode = get_update_mode(&app_handle).await;
yaak_updater.lock().await.force_check(&app_handle, update_mode).await.map_err(|e| e.to_string()) yaak_updater
.lock()
.await
.check_now(&app_handle, update_mode, UpdateTrigger::User)
.await
.map_err(|e| e.to_string())
} }
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
@@ -1918,7 +1904,6 @@ pub fn run() {
cmd_set_update_mode, cmd_set_update_mode,
cmd_template_functions, cmd_template_functions,
cmd_template_tokens_to_string, cmd_template_tokens_to_string,
cmd_track_event,
cmd_uninstall_plugin, cmd_uninstall_plugin,
cmd_update_cookie_jar, cmd_update_cookie_jar,
cmd_update_environment, cmd_update_environment,
@@ -1941,7 +1926,7 @@ pub fn run() {
RunEvent::Ready => { RunEvent::Ready => {
let w = create_main_window(app_handle, "/"); let w = create_main_window(app_handle, "/");
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
let info = analytics::track_launch_event(&w).await; let info = history::store_launch_history(&w).await;
debug!("Launched Yaak {:?}", info); debug!("Launched Yaak {:?}", info);
}); });
@@ -1961,7 +1946,7 @@ pub fn run() {
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
let val: State<'_, Mutex<YaakUpdater>> = h.state(); let val: State<'_, Mutex<YaakUpdater>> = h.state();
let update_mode = get_update_mode(&h).await; let update_mode = get_update_mode(&h).await;
if let Err(e) = val.lock().await.check(&h, update_mode).await { if let Err(e) = val.lock().await.maybe_check(&h, update_mode).await {
warn!("Failed to check for updates {e:?}"); warn!("Failed to check for updates {e:?}");
}; };
}); });
@@ -2070,7 +2055,7 @@ fn monitor_plugin_events<R: Runtime>(app_handle: &AppHandle<R>) {
// We might have recursive back-and-forth calls between app and plugin, so we don't // We might have recursive back-and-forth calls between app and plugin, so we don't
// want to block here // want to block here
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
crate::plugin_events::handle_plugin_event(&app_handle, &event, &plugin).await; plugin_events::handle_plugin_event(&app_handle, &event, &plugin).await;
}); });
} }
plugin_manager.unsubscribe(rx_id.as_str()).await; plugin_manager.unsubscribe(rx_id.as_str()).await;

View File

@@ -1,6 +1,6 @@
use std::time::SystemTime; use std::time::SystemTime;
use crate::analytics::{get_num_launches, get_os}; use crate::history::{get_num_launches, get_os};
use chrono::{DateTime, Duration, Utc}; use chrono::{DateTime, Duration, Utc};
use log::debug; use log::debug;
use reqwest::Method; use reqwest::Method;

View File

@@ -6,6 +6,7 @@ use tauri::{AppHandle, Manager};
use tauri_plugin_dialog::{DialogExt, MessageDialogButtons}; use tauri_plugin_dialog::{DialogExt, MessageDialogButtons};
use tauri_plugin_updater::UpdaterExt; use tauri_plugin_updater::UpdaterExt;
use tokio::task::block_in_place; use tokio::task::block_in_place;
use yaak_models::queries::get_or_create_settings;
use yaak_plugins::manager::PluginManager; use yaak_plugins::manager::PluginManager;
use crate::is_dev; use crate::is_dev;
@@ -46,6 +47,11 @@ impl UpdateMode {
} }
} }
pub enum UpdateTrigger {
Background,
User,
}
impl YaakUpdater { impl YaakUpdater {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
@@ -53,11 +59,14 @@ impl YaakUpdater {
} }
} }
pub async fn force_check( pub async fn check_now(
&mut self, &mut self,
app_handle: &AppHandle, app_handle: &AppHandle,
mode: UpdateMode, mode: UpdateMode,
update_trigger: UpdateTrigger,
) -> Result<bool, tauri_plugin_updater::Error> { ) -> Result<bool, tauri_plugin_updater::Error> {
let settings = get_or_create_settings(app_handle).await;
let update_key = format!("{:x}", md5::compute(settings.id));
self.last_update_check = SystemTime::now(); self.last_update_check = SystemTime::now();
info!("Checking for updates mode={}", mode); info!("Checking for updates mode={}", mode);
@@ -79,6 +88,14 @@ impl YaakUpdater {
}); });
}) })
.header("X-Update-Mode", mode.to_string())? .header("X-Update-Mode", mode.to_string())?
.header("X-Update-Key", update_key)?
.header(
"X-Update-Trigger",
match update_trigger {
UpdateTrigger::Background => "background",
UpdateTrigger::User => "user",
},
)?
.build()? .build()?
.check() .check()
.await; .await;
@@ -129,7 +146,7 @@ impl YaakUpdater {
Err(e) => Err(e), Err(e) => Err(e),
} }
} }
pub async fn check( pub async fn maybe_check(
&mut self, &mut self,
app_handle: &AppHandle, app_handle: &AppHandle,
mode: UpdateMode, mode: UpdateMode,
@@ -150,6 +167,6 @@ impl YaakUpdater {
return Ok(false); return Ok(false);
} }
self.force_check(app_handle, mode).await self.check_now(app_handle, mode, UpdateTrigger::Background).await
} }
} }

View File

@@ -1,5 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { SyncModel } from "./gen_models"; import type { SyncModel } from "./gen_models.js";
export type GitAuthor = { name: string | null, email: string | null, }; export type GitAuthor = { name: string | null, email: string | null, };

View File

@@ -54,7 +54,7 @@ export type ProxySetting = { "type": "enabled", http: string, https: string, aut
export type ProxySettingAuth = { user: string, password: string, }; export type ProxySettingAuth = { user: string, password: string, };
export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, editorFontSize: number, editorSoftWrap: boolean, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, proxy: ProxySetting | null, telemetry: boolean, theme: string, themeDark: string, themeLight: string, updateChannel: string, editorKeymap: EditorKeymap, }; export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, editorFontSize: number, editorSoftWrap: boolean, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, proxy: ProxySetting | null, theme: string, themeDark: string, themeLight: string, updateChannel: string, editorKeymap: EditorKeymap, };
export type SyncHistory = { model: "sync_history", id: string, workspaceId: string, createdAt: string, states: Array<SyncState>, checksum: string, relPath: string, syncDir: string, }; export type SyncHistory = { model: "sync_history", id: string, workspaceId: string, createdAt: string, states: Array<SyncState>, checksum: string, relPath: string, syncDir: string, };

View File

@@ -87,7 +87,6 @@ pub struct Settings {
pub interface_scale: f32, pub interface_scale: f32,
pub open_workspace_new_window: Option<bool>, pub open_workspace_new_window: Option<bool>,
pub proxy: Option<ProxySetting>, pub proxy: Option<ProxySetting>,
pub telemetry: bool,
pub theme: String, pub theme: String,
pub theme_dark: String, pub theme_dark: String,
pub theme_light: String, pub theme_light: String,
@@ -112,7 +111,6 @@ pub enum SettingsIden {
InterfaceScale, InterfaceScale,
OpenWorkspaceNewWindow, OpenWorkspaceNewWindow,
Proxy, Proxy,
Telemetry,
Theme, Theme,
ThemeDark, ThemeDark,
ThemeLight, ThemeLight,
@@ -138,7 +136,6 @@ impl<'s> TryFrom<&Row<'s>> for Settings {
interface_scale: r.get("interface_scale")?, interface_scale: r.get("interface_scale")?,
open_workspace_new_window: r.get("open_workspace_new_window")?, open_workspace_new_window: r.get("open_workspace_new_window")?,
proxy: proxy.map(|p| -> ProxySetting { serde_json::from_str(p.as_str()).unwrap() }), proxy: proxy.map(|p| -> ProxySetting { serde_json::from_str(p.as_str()).unwrap() }),
telemetry: r.get("telemetry")?,
theme: r.get("theme")?, theme: r.get("theme")?,
theme_dark: r.get("theme_dark")?, theme_dark: r.get("theme_dark")?,
theme_light: r.get("theme_light")?, theme_light: r.get("theme_light")?,

View File

@@ -1478,7 +1478,6 @@ pub async fn update_settings<R: Runtime>(
(SettingsIden::EditorFontSize, settings.editor_font_size.into()), (SettingsIden::EditorFontSize, settings.editor_font_size.into()),
(SettingsIden::EditorKeymap, settings.editor_keymap.to_string().into()), (SettingsIden::EditorKeymap, settings.editor_keymap.to_string().into()),
(SettingsIden::EditorSoftWrap, settings.editor_soft_wrap.into()), (SettingsIden::EditorSoftWrap, settings.editor_soft_wrap.into()),
(SettingsIden::Telemetry, settings.telemetry.into()),
(SettingsIden::OpenWorkspaceNewWindow, settings.open_workspace_new_window.into()), (SettingsIden::OpenWorkspaceNewWindow, settings.open_workspace_new_window.into()),
( (
SettingsIden::Proxy, SettingsIden::Proxy,

View File

@@ -559,7 +559,7 @@ impl PluginManager {
Some(JsonPrimitive::Boolean(v)) => v.clone(), Some(JsonPrimitive::Boolean(v)) => v.clone(),
_ => false, _ => false,
}; };
// Auth is disabled, so don't do anything // Auth is disabled, so don't do anything
if disabled { if disabled {
info!("Not applying disabled auth {:?}", auth_name); info!("Not applying disabled auth {:?}", auth_name);
@@ -623,7 +623,7 @@ impl PluginManager {
&self, &self,
window: &WebviewWindow<R>, window: &WebviewWindow<R>,
content: &str, content: &str,
) -> Result<(ImportResponse, String)> { ) -> Result<ImportResponse> {
let reply_events = self let reply_events = self
.send_and_wait( .send_and_wait(
&WindowContext::from_window(window), &WindowContext::from_window(window),
@@ -635,19 +635,13 @@ impl PluginManager {
// TODO: Don't just return the first valid response // TODO: Don't just return the first valid response
let result = reply_events.into_iter().find_map(|e| match e.payload { let result = reply_events.into_iter().find_map(|e| match e.payload {
InternalEventPayload::ImportResponse(resp) => Some((resp, e.plugin_ref_id)), InternalEventPayload::ImportResponse(resp) => Some(resp),
_ => None, _ => None,
}); });
match result { match result {
None => Err(PluginErr("No importers found for file contents".to_string())), None => Err(PluginErr("No importers found for file contents".to_string())),
Some((resp, ref_id)) => { Some(resp) => Ok(resp),
let plugin = self
.get_plugin_by_ref_id(ref_id.as_str())
.await
.ok_or(PluginNotFoundErr(ref_id))?;
Ok((resp, plugin.info().await.name))
}
} }
} }

View File

@@ -5,7 +5,6 @@ import { InlineCode } from '../components/core/InlineCode';
import { VStack } from '../components/core/Stacks'; import { VStack } from '../components/core/Stacks';
import { getActiveWorkspaceId } from '../hooks/useActiveWorkspace'; import { getActiveWorkspaceId } from '../hooks/useActiveWorkspace';
import { createFastMutation } from '../hooks/useFastMutation'; import { createFastMutation } from '../hooks/useFastMutation';
import { trackEvent } from '../lib/analytics';
import { showConfirm } from '../lib/confirm'; import { showConfirm } from '../lib/confirm';
import { resolvedModelNameWithFolders } from '../lib/resolvedModelName'; import { resolvedModelNameWithFolders } from '../lib/resolvedModelName';
import { pluralizeCount } from '../lib/pluralize'; import { pluralizeCount } from '../lib/pluralize';
@@ -42,7 +41,6 @@ export const createFolder = createFastMutation<
patch.sortPriority = patch.sortPriority || -Date.now(); patch.sortPriority = patch.sortPriority || -Date.now();
return invokeCmd<Folder>('cmd_update_folder', { folder: { workspaceId, ...patch } }); return invokeCmd<Folder>('cmd_update_folder', { folder: { workspaceId, ...patch } });
}, },
onSettled: () => trackEvent('folder', 'create'),
}); });
export const syncWorkspace = createFastMutation< export const syncWorkspace = createFastMutation<

View File

@@ -1,14 +1,10 @@
import type { WebsocketConnection } from '@yaakapp-internal/models'; import type { WebsocketConnection } from '@yaakapp-internal/models';
import { deleteWebsocketConnection as cmdDeleteWebsocketConnection } from '@yaakapp-internal/ws'; import { deleteWebsocketConnection as cmdDeleteWebsocketConnection } from '@yaakapp-internal/ws';
import { createFastMutation } from '../hooks/useFastMutation'; import { createFastMutation } from '../hooks/useFastMutation';
import { trackEvent } from '../lib/analytics';
export const deleteWebsocketConnection = createFastMutation({ export const deleteWebsocketConnection = createFastMutation({
mutationKey: ['delete_websocket_connection'], mutationKey: ['delete_websocket_connection'],
mutationFn: async function (connection: WebsocketConnection) { mutationFn: async function (connection: WebsocketConnection) {
return cmdDeleteWebsocketConnection(connection.id); return cmdDeleteWebsocketConnection(connection.id);
}, },
onSuccess: async () => {
trackEvent('websocket_connection', 'delete');
},
}); });

View File

@@ -1,14 +1,10 @@
import type { WebsocketRequest } from '@yaakapp-internal/models'; import type { WebsocketRequest } from '@yaakapp-internal/models';
import { deleteWebsocketConnections as cmdDeleteWebsocketConnections } from '@yaakapp-internal/ws'; import { deleteWebsocketConnections as cmdDeleteWebsocketConnections } from '@yaakapp-internal/ws';
import { createFastMutation } from '../hooks/useFastMutation'; import { createFastMutation } from '../hooks/useFastMutation';
import { trackEvent } from '../lib/analytics';
export const deleteWebsocketConnections = createFastMutation({ export const deleteWebsocketConnections = createFastMutation({
mutationKey: ['delete_websocket_connections'], mutationKey: ['delete_websocket_connections'],
mutationFn: async function (request: WebsocketRequest) { mutationFn: async function (request: WebsocketRequest) {
return cmdDeleteWebsocketConnections(request.id); return cmdDeleteWebsocketConnections(request.id);
}, },
onSuccess: async () => {
trackEvent('websocket_connection', 'delete_many');
},
}); });

View File

@@ -2,7 +2,6 @@ import type { WebsocketRequest } from '@yaakapp-internal/models';
import { deleteWebsocketRequest as cmdDeleteWebsocketRequest } from '@yaakapp-internal/ws'; import { deleteWebsocketRequest as cmdDeleteWebsocketRequest } from '@yaakapp-internal/ws';
import { InlineCode } from '../components/core/InlineCode'; import { InlineCode } from '../components/core/InlineCode';
import { createFastMutation } from '../hooks/useFastMutation'; import { createFastMutation } from '../hooks/useFastMutation';
import { trackEvent } from '../lib/analytics';
import { showConfirmDelete } from '../lib/confirm'; import { showConfirmDelete } from '../lib/confirm';
import { resolvedModelName } from '../lib/resolvedModelName'; import { resolvedModelName } from '../lib/resolvedModelName';
@@ -24,7 +23,4 @@ export const deleteWebsocketRequest = createFastMutation({
return cmdDeleteWebsocketRequest(request.id); return cmdDeleteWebsocketRequest(request.id);
}, },
onSuccess: async () => {
trackEvent('websocket_request', 'delete');
},
}); });

View File

@@ -1,7 +1,6 @@
import type { WebsocketRequest } from '@yaakapp-internal/models'; import type { WebsocketRequest } from '@yaakapp-internal/models';
import { duplicateWebsocketRequest as cmdDuplicateWebsocketRequest } from '@yaakapp-internal/ws'; import { duplicateWebsocketRequest as cmdDuplicateWebsocketRequest } from '@yaakapp-internal/ws';
import { createFastMutation } from '../hooks/useFastMutation'; import { createFastMutation } from '../hooks/useFastMutation';
import { trackEvent } from '../lib/analytics';
import { router } from '../lib/router'; import { router } from '../lib/router';
export const duplicateWebsocketRequest = createFastMutation({ export const duplicateWebsocketRequest = createFastMutation({
@@ -10,7 +9,6 @@ export const duplicateWebsocketRequest = createFastMutation({
return cmdDuplicateWebsocketRequest(request.id); return cmdDuplicateWebsocketRequest(request.id);
}, },
onSuccess: async (request) => { onSuccess: async (request) => {
trackEvent('websocket_request', 'duplicate');
await router.navigate({ await router.navigate({
to: '/workspaces/$workspaceId', to: '/workspaces/$workspaceId',
params: { workspaceId: request.workspaceId }, params: { workspaceId: request.workspaceId },

View File

@@ -1,7 +1,6 @@
import { SettingsTab } from '../components/Settings/SettingsTab'; import { SettingsTab } from '../components/Settings/SettingsTab';
import { getActiveWorkspaceId } from '../hooks/useActiveWorkspace'; import { getActiveWorkspaceId } from '../hooks/useActiveWorkspace';
import { createFastMutation } from '../hooks/useFastMutation'; import { createFastMutation } from '../hooks/useFastMutation';
import { trackEvent } from '../lib/analytics';
import { router } from '../lib/router'; import { router } from '../lib/router';
import { invokeCmd } from '../lib/tauri'; import { invokeCmd } from '../lib/tauri';
@@ -11,7 +10,6 @@ export const openSettings = createFastMutation<void, string, SettingsTab | null>
const workspaceId = getActiveWorkspaceId(); const workspaceId = getActiveWorkspaceId();
if (workspaceId == null) return; if (workspaceId == null) return;
trackEvent('dialog', 'show', { id: 'settings', tab: `${tab}` });
const location = router.buildLocation({ const location = router.buildLocation({
to: '/workspaces/$workspaceId/settings', to: '/workspaces/$workspaceId/settings',
params: { workspaceId }, params: { workspaceId },

View File

@@ -2,7 +2,6 @@ import type { WebsocketRequest } from '@yaakapp-internal/models';
import { upsertWebsocketRequest as cmdUpsertWebsocketRequest } from '@yaakapp-internal/ws'; import { upsertWebsocketRequest as cmdUpsertWebsocketRequest } from '@yaakapp-internal/ws';
import { differenceInMilliseconds } from 'date-fns'; import { differenceInMilliseconds } from 'date-fns';
import { createFastMutation } from '../hooks/useFastMutation'; import { createFastMutation } from '../hooks/useFastMutation';
import { trackEvent } from '../lib/analytics';
import { router } from '../lib/router'; import { router } from '../lib/router';
export const upsertWebsocketRequest = createFastMutation< export const upsertWebsocketRequest = createFastMutation<
@@ -16,12 +15,11 @@ export const upsertWebsocketRequest = createFastMutation<
const isNew = differenceInMilliseconds(new Date(), request.createdAt + 'Z') < 100; const isNew = differenceInMilliseconds(new Date(), request.createdAt + 'Z') < 100;
if (isNew) { if (isNew) {
trackEvent('websocket_request', 'create');
await router.navigate({ await router.navigate({
to: '/workspaces/$workspaceId', to: '/workspaces/$workspaceId',
params: { workspaceId: request.workspaceId }, params: { workspaceId: request.workspaceId },
search: (prev) => ({ ...prev, request_id: request.id }), search: (prev) => ({ ...prev, request_id: request.id }),
}); });
} else trackEvent('websocket_request', 'update'); }
}, },
}); });

View File

@@ -1,7 +1,5 @@
import type { Workspace } from '@yaakapp-internal/models'; import type { Workspace } from '@yaakapp-internal/models';
import { differenceInMilliseconds } from 'date-fns';
import { createFastMutation } from '../hooks/useFastMutation'; import { createFastMutation } from '../hooks/useFastMutation';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri'; import { invokeCmd } from '../lib/tauri';
export const upsertWorkspace = createFastMutation< export const upsertWorkspace = createFastMutation<
@@ -11,10 +9,4 @@ export const upsertWorkspace = createFastMutation<
>({ >({
mutationKey: ['upsert_workspace'], mutationKey: ['upsert_workspace'],
mutationFn: (workspace) => invokeCmd<Workspace>('cmd_update_workspace', { workspace }), mutationFn: (workspace) => invokeCmd<Workspace>('cmd_update_workspace', { workspace }),
onSuccess: async (workspace) => {
const isNew = differenceInMilliseconds(new Date(), workspace.createdAt + 'Z') < 100;
if (isNew) trackEvent('workspace', 'create');
else trackEvent('workspace', 'update');
},
}); });

View File

@@ -60,6 +60,7 @@ export function LicenseBadge() {
size="2xs" size="2xs"
variant="border" variant="border"
className="!rounded-full mx-1" className="!rounded-full mx-1"
color={detail.color}
onClick={async () => { onClick={async () => {
if (check.data.type === 'trialing') { if (check.data.type === 'trialing') {
await setLicenseDetails((v) => ({ await setLicenseDetails((v) => ({
@@ -69,8 +70,6 @@ export function LicenseBadge() {
} }
openSettings.mutate(SettingsTab.License); openSettings.mutate(SettingsTab.License);
}} }}
color={detail.color}
event={{ id: 'license-badge', status: check.data.type }}
> >
{detail.label} {detail.label}
</Button> </Button>

View File

@@ -66,18 +66,8 @@ export function MarkdownEditor({
onChange={setViewMode} onChange={setViewMode}
value={viewMode} value={viewMode}
options={[ options={[
{ { icon: 'eye', label: 'Preview mode', value: 'preview' },
event: { id: 'md_mode', mode: 'preview' }, { icon: 'pencil', label: 'Edit mode', value: 'edit' },
icon: 'eye',
label: 'Preview mode',
value: 'preview',
},
{
event: { id: 'md_mode', mode: 'edit' },
icon: 'pencil',
label: 'Edit mode',
value: 'edit',
},
]} ]}
/> />
</div> </div>

View File

@@ -96,7 +96,6 @@ export function SettingsAppearance() {
value={`${settings.interfaceFontSize}`} value={`${settings.interfaceFontSize}`}
options={fontSizeOptions} options={fontSizeOptions}
onChange={(v) => updateSettings.mutate({ interfaceFontSize: parseInt(v) })} onChange={(v) => updateSettings.mutate({ interfaceFontSize: parseInt(v) })}
event="ui-font-size"
/> />
<Select <Select
size="sm" size="sm"
@@ -106,7 +105,6 @@ export function SettingsAppearance() {
value={`${settings.editorFontSize}`} value={`${settings.editorFontSize}`}
options={fontSizeOptions} options={fontSizeOptions}
onChange={(v) => updateSettings.mutate({ editorFontSize: clamp(parseInt(v) || 14, 8, 30) })} onChange={(v) => updateSettings.mutate({ editorFontSize: clamp(parseInt(v) || 14, 8, 30) })}
event="editor-font-size"
/> />
<Select <Select
size="sm" size="sm"
@@ -116,13 +114,11 @@ export function SettingsAppearance() {
value={`${settings.editorKeymap}`} value={`${settings.editorKeymap}`}
options={keymaps} options={keymaps}
onChange={(v) => updateSettings.mutate({ editorKeymap: v })} onChange={(v) => updateSettings.mutate({ editorKeymap: v })}
event="editor-keymap"
/> />
<Checkbox <Checkbox
checked={settings.editorSoftWrap} checked={settings.editorSoftWrap}
title="Wrap Editor Lines" title="Wrap Editor Lines"
onChange={(editorSoftWrap) => updateSettings.mutate({ editorSoftWrap })} onChange={(editorSoftWrap) => updateSettings.mutate({ editorSoftWrap })}
event="editor-wrap-lines"
/> />
<Separator className="my-4" /> <Separator className="my-4" />
@@ -134,7 +130,6 @@ export function SettingsAppearance() {
size="sm" size="sm"
value={settings.appearance} value={settings.appearance}
onChange={(appearance) => updateSettings.mutate({ appearance })} onChange={(appearance) => updateSettings.mutate({ appearance })}
event="appearance"
options={[ options={[
{ label: 'Automatic', value: 'system' }, { label: 'Automatic', value: 'system' },
{ label: 'Light', value: 'light' }, { label: 'Light', value: 'light' },
@@ -152,7 +147,6 @@ export function SettingsAppearance() {
className="flex-1" className="flex-1"
value={activeTheme.light.id} value={activeTheme.light.id}
options={lightThemes} options={lightThemes}
event="theme.light"
onChange={(themeLight) => updateSettings.mutate({ ...settings, themeLight })} onChange={(themeLight) => updateSettings.mutate({ ...settings, themeLight })}
/> />
)} )}
@@ -166,7 +160,6 @@ export function SettingsAppearance() {
size="sm" size="sm"
value={activeTheme.dark.id} value={activeTheme.dark.id}
options={darkThemes} options={darkThemes}
event="theme.dark"
onChange={(themeDark) => updateSettings.mutate({ ...settings, themeDark })} onChange={(themeDark) => updateSettings.mutate({ ...settings, themeDark })}
/> />
)} )}

View File

@@ -38,7 +38,6 @@ export function SettingsGeneral() {
size="sm" size="sm"
value={settings.updateChannel} value={settings.updateChannel}
onChange={(updateChannel) => updateSettings.mutate({ updateChannel })} onChange={(updateChannel) => updateSettings.mutate({ updateChannel })}
event="update-channel"
options={[ options={[
{ label: 'Stable (less frequent)', value: 'stable' }, { label: 'Stable (less frequent)', value: 'stable' },
{ label: 'Beta (more frequent)', value: 'beta' }, { label: 'Beta (more frequent)', value: 'beta' },
@@ -59,7 +58,6 @@ export function SettingsGeneral() {
labelPosition="left" labelPosition="left"
labelClassName="w-[14rem]" labelClassName="w-[14rem]"
size="sm" size="sm"
event="workspace-switch-behavior"
value={ value={
settings.openWorkspaceNewWindow === true settings.openWorkspaceNewWindow === true
? 'new' ? 'new'
@@ -81,9 +79,9 @@ export function SettingsGeneral() {
<Checkbox <Checkbox
className="mt-3" className="mt-3"
checked={settings.telemetry} checked={false}
title="Send Usage Statistics" title="Send Usage Statistics (all tracking was removed in 2025.1.2)"
event="usage-statistics" disabled
onChange={(telemetry) => updateSettings.mutate({ telemetry })} onChange={(telemetry) => updateSettings.mutate({ telemetry })}
/> />
@@ -115,7 +113,6 @@ export function SettingsGeneral() {
<Checkbox <Checkbox
checked={workspace.settingValidateCertificates} checked={workspace.settingValidateCertificates}
title="Validate TLS Certificates" title="Validate TLS Certificates"
event="validate-certs"
onChange={(settingValidateCertificates) => onChange={(settingValidateCertificates) =>
upsertWorkspace.mutate({ ...workspace, settingValidateCertificates }) upsertWorkspace.mutate({ ...workspace, settingValidateCertificates })
} }
@@ -124,7 +121,6 @@ export function SettingsGeneral() {
<Checkbox <Checkbox
checked={workspace.settingFollowRedirects} checked={workspace.settingFollowRedirects}
title="Follow Redirects" title="Follow Redirects"
event="follow-redirects"
onChange={(settingFollowRedirects) => onChange={(settingFollowRedirects) =>
upsertWorkspace.mutate({ upsertWorkspace.mutate({
...workspace, ...workspace,

View File

@@ -81,7 +81,6 @@ export function SettingsLicense() {
color="secondary" color="secondary"
size="sm" size="sm"
onClick={toggleActivateFormVisible} onClick={toggleActivateFormVisible}
event="license.another"
> >
Activate Another License Activate Another License
</Button> </Button>
@@ -90,7 +89,6 @@ export function SettingsLicense() {
size="sm" size="sm"
onClick={() => openUrl('https://yaak.app/dashboard')} onClick={() => openUrl('https://yaak.app/dashboard')}
rightSlot={<Icon icon="external_link" />} rightSlot={<Icon icon="external_link" />}
event="license.support"
> >
Direct Support Direct Support
</Button> </Button>
@@ -101,7 +99,6 @@ export function SettingsLicense() {
color="primary" color="primary"
size="sm" size="sm"
onClick={toggleActivateFormVisible} onClick={toggleActivateFormVisible}
event="license.activate"
> >
Activate Activate
</Button> </Button>
@@ -110,7 +107,6 @@ export function SettingsLicense() {
size="sm" size="sm"
onClick={() => openUrl('https://yaak.app/pricing?ref=app.yaak.desktop')} onClick={() => openUrl('https://yaak.app/pricing?ref=app.yaak.desktop')}
rightSlot={<Icon icon="external_link" />} rightSlot={<Icon icon="external_link" />}
event="license.purchase"
> >
Purchase Purchase
</Button> </Button>
@@ -140,7 +136,6 @@ export function SettingsLicense() {
color="primary" color="primary"
size="sm" size="sm"
isLoading={activate.isPending} isLoading={activate.isPending}
event="license.submit"
> >
Submit Submit
</Button> </Button>

View File

@@ -66,7 +66,6 @@ export function SettingsPlugins() {
type="submit" type="submit"
color="primary" color="primary"
className="ml-auto" className="ml-auto"
event="plugin.add"
> >
Add Plugin Add Plugin
</Button> </Button>
@@ -76,14 +75,12 @@ export function SettingsPlugins() {
icon="refresh" icon="refresh"
title="Reload plugins" title="Reload plugins"
spin={refreshPlugins.isPending} spin={refreshPlugins.isPending}
event="plugin.reload"
onClick={() => refreshPlugins.mutate()} onClick={() => refreshPlugins.mutate()}
/> />
<IconButton <IconButton
size="sm" size="sm"
icon="help" icon="help"
title="View documentation" title="View documentation"
event="plugin.docs"
onClick={() => openUrl('https://feedback.yaak.app/help/articles/6911763-quick-start')} onClick={() => openUrl('https://feedback.yaak.app/help/articles/6911763-quick-start')}
/> />
</HStack> </HStack>
@@ -107,7 +104,6 @@ function PluginInfo({ plugin }: { plugin: Plugin }) {
size="sm" size="sm"
icon="trash" icon="trash"
title="Uninstall plugin" title="Uninstall plugin"
event="plugin.delete"
onClick={() => deletePlugin.mutate()} onClick={() => deletePlugin.mutate()}
/> />
</td> </td>

View File

@@ -19,7 +19,6 @@ export function SettingsProxy() {
hideLabel hideLabel
size="sm" size="sm"
value={settings.proxy?.type ?? 'automatic'} value={settings.proxy?.type ?? 'automatic'}
event="proxy"
onChange={(v) => { onChange={(v) => {
if (v === 'automatic') { if (v === 'automatic') {
updateSettings.mutate({ proxy: undefined }); updateSettings.mutate({ proxy: undefined });

View File

@@ -18,7 +18,6 @@ import { requestsAtom } from '../hooks/useRequests';
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey'; import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest'; import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest';
import { useLatestWebsocketConnection } from '../hooks/useWebsocketConnections'; import { useLatestWebsocketConnection } from '../hooks/useWebsocketConnections';
import { trackEvent } from '../lib/analytics';
import { deepEqualAtom } from '../lib/atoms'; import { deepEqualAtom } from '../lib/atoms';
import { languageFromContentType } from '../lib/contentType'; import { languageFromContentType } from '../lib/contentType';
import { generateId } from '../lib/generateId'; import { generateId } from '../lib/generateId';
@@ -190,7 +189,6 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
environmentId: getActiveEnvironment()?.id ?? null, environmentId: getActiveEnvironment()?.id ?? null,
cookieJarId: getActiveCookieJar()?.id ?? null, cookieJarId: getActiveCookieJar()?.id ?? null,
}); });
trackEvent('websocket_request', 'send');
}, [activeRequest.id]); }, [activeRequest.id]);
const handleSend = useCallback(async () => { const handleSend = useCallback(async () => {
@@ -199,13 +197,11 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
connectionId: connection?.id, connectionId: connection?.id,
environmentId: getActiveEnvironment()?.id ?? null, environmentId: getActiveEnvironment()?.id ?? null,
}); });
trackEvent('websocket_connection', 'send');
}, [connection]); }, [connection]);
const handleCancel = useCallback(async () => { const handleCancel = useCallback(async () => {
if (connection == null) return; if (connection == null) return;
await closeWebsocket({ connectionId: connection?.id }); await closeWebsocket({ connectionId: connection?.id });
trackEvent('websocket_connection', 'cancel');
}, [connection]); }, [connection]);
const handleUrlChange = useCallback( const handleUrlChange = useCallback(

View File

@@ -46,7 +46,6 @@ export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Prop
icon="search" icon="search"
title="Search or execute a command" title="Search or execute a command"
size="sm" size="sm"
event="search"
iconColor="secondary" iconColor="secondary"
onClick={togglePalette} onClick={togglePalette}
/> />

View File

@@ -4,7 +4,6 @@ import type { HTMLAttributes, ReactNode } from 'react';
import { forwardRef, useImperativeHandle, useRef } from 'react'; import { forwardRef, useImperativeHandle, useRef } from 'react';
import type { HotkeyAction } from '../../hooks/useHotKey'; import type { HotkeyAction } from '../../hooks/useHotKey';
import { useFormattedHotkey, useHotKey } from '../../hooks/useHotKey'; import { useFormattedHotkey, useHotKey } from '../../hooks/useHotKey';
import { trackEvent } from '../../lib/analytics';
import { Icon } from './Icon'; import { Icon } from './Icon';
import { LoadingIcon } from './LoadingIcon'; import { LoadingIcon } from './LoadingIcon';
@@ -22,7 +21,6 @@ export type ButtonProps = Omit<HTMLAttributes<HTMLButtonElement>, 'color' | 'onC
leftSlot?: ReactNode; leftSlot?: ReactNode;
rightSlot?: ReactNode; rightSlot?: ReactNode;
hotkeyAction?: HotkeyAction; hotkeyAction?: HotkeyAction;
event?: string | { id: string; [attr: string]: number | string };
}; };
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button( export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
@@ -43,7 +41,6 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
hotkeyAction, hotkeyAction,
title, title,
onClick, onClick,
event,
...props ...props
}: ButtonProps, }: ButtonProps,
ref, ref,
@@ -107,12 +104,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button
type={type} type={type}
className={classes} className={classes}
disabled={disabled} disabled={disabled}
onClick={(e) => { onClick={onClick}
onClick?.(e);
if (event != null) {
trackEvent('button', 'click', typeof event === 'string' ? { id: event } : event);
}
}}
onDoubleClick={(e) => { onDoubleClick={(e) => {
// Kind of a hack? This prevents double-clicks from going through buttons. For example, when // Kind of a hack? This prevents double-clicks from going through buttons. For example, when
// double-clicking the workspace header to toggle window maximization // double-clicking the workspace header to toggle window maximization

View File

@@ -1,6 +1,5 @@
import classNames from 'classnames'; import classNames from 'classnames';
import { type ReactNode } from 'react'; import { type ReactNode } from 'react';
import { trackEvent } from '../../lib/analytics';
import { Icon } from './Icon'; import { Icon } from './Icon';
import { HStack } from './Stacks'; import { HStack } from './Stacks';
@@ -13,7 +12,6 @@ export interface CheckboxProps {
inputWrapperClassName?: string; inputWrapperClassName?: string;
hideLabel?: boolean; hideLabel?: boolean;
fullWidth?: boolean; fullWidth?: boolean;
event?: string;
} }
export function Checkbox({ export function Checkbox({
@@ -25,7 +23,6 @@ export function Checkbox({
title, title,
hideLabel, hideLabel,
fullWidth, fullWidth,
event,
}: CheckboxProps) { }: CheckboxProps) {
return ( return (
<HStack as="label" space={2} className={classNames(className, 'text-text mr-auto')}> <HStack as="label" space={2} className={classNames(className, 'text-text mr-auto')}>
@@ -42,9 +39,6 @@ export function Checkbox({
disabled={disabled} disabled={disabled}
onChange={() => { onChange={() => {
onChange(checked === 'indeterminate' ? true : !checked); onChange(checked === 'indeterminate' ? true : !checked);
if (event != null) {
trackEvent('button', 'click', { id: event, checked: checked ? 'on' : 'off' });
}
}} }}
/> />
<div className="absolute inset-0 flex items-center justify-center"> <div className="absolute inset-0 flex items-center justify-center">

View File

@@ -1,15 +1,13 @@
import { Link as RouterLink } from '@tanstack/react-router';
import classNames from 'classnames'; import classNames from 'classnames';
import type { HTMLAttributes } from 'react'; import type { HTMLAttributes } from 'react';
import { Link as RouterLink } from '@tanstack/react-router';
import { trackEvent } from '../../lib/analytics';
import { Icon } from './Icon'; import { Icon } from './Icon';
interface Props extends HTMLAttributes<HTMLAnchorElement> { interface Props extends HTMLAttributes<HTMLAnchorElement> {
href: string; href: string;
event?: string;
} }
export function Link({ href, children, className, event, ...other }: Props) { export function Link({ href, children, className, ...other }: Props) {
const isExternal = href.match(/^https?:\/\//); const isExternal = href.match(/^https?:\/\//);
className = classNames(className, 'relative underline hover:text-violet-600'); className = classNames(className, 'relative underline hover:text-violet-600');
@@ -23,9 +21,6 @@ export function Link({ href, children, className, event, ...other }: Props) {
className={classNames(className, 'pr-4 inline-flex items-center')} className={classNames(className, 'pr-4 inline-flex items-center')}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
if (event != null) {
trackEvent('link', 'click', { id: event });
}
}} }}
{...other} {...other}
> >

View File

@@ -261,7 +261,6 @@ export const PairEditor = forwardRef<PairEditorRef, PairEditorProps>(function Pa
variant="border" variant="border"
className="m-2" className="m-2"
size="xs" size="xs"
event="pairs.reveal-more"
> >
Show {pairs.length - MAX_INITIAL_PAIRS} More Show {pairs.length - MAX_INITIAL_PAIRS} More
</Button> </Button>

View File

@@ -2,18 +2,17 @@ import classNames from 'classnames';
import { useRef } from 'react'; import { useRef } from 'react';
import { useStateWithDeps } from '../../hooks/useStateWithDeps'; import { useStateWithDeps } from '../../hooks/useStateWithDeps';
import type { IconProps } from './Icon'; import type { IconProps } from './Icon';
import type { IconButtonProps } from './IconButton';
import { IconButton } from './IconButton'; import { IconButton } from './IconButton';
import { HStack } from './Stacks'; import { HStack } from './Stacks';
interface Props<T extends string> { interface Props<T extends string> {
options: { value: T; label: string; icon: IconProps['icon']; event?: IconButtonProps['event'] }[]; options: { value: T; label: string; icon: IconProps['icon'] }[];
onChange: (value: T) => void; onChange: (value: T) => void;
value: T; value: T;
name: string; name: string;
} }
export function SegmentedControl<T extends string>({ value, onChange, options, name }: Props<T>) { export function SegmentedControl<T extends string>({ value, onChange, options }: Props<T>) {
const [selectedValue, setSelectedValue] = useStateWithDeps<T>(value, [value]); const [selectedValue, setSelectedValue] = useStateWithDeps<T>(value, [value]);
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
return ( return (
@@ -45,14 +44,13 @@ export function SegmentedControl<T extends string>({ value, onChange, options, n
<IconButton <IconButton
size="xs" size="xs"
variant="solid" variant="solid"
color={isActive ? "secondary" : undefined} color={isActive ? 'secondary' : undefined}
role="radio" role="radio"
event={{ id: name, value: String(o.value) }}
tabIndex={isSelected ? 0 : -1} tabIndex={isSelected ? 0 : -1}
className={classNames( className={classNames(
isActive && '!text-text', isActive && '!text-text',
'!px-1.5 !w-auto', '!px-1.5 !w-auto',
'focus:ring-border-focus', 'focus:ring-border-focus',
)} )}
key={i} key={i}
title={o.label} title={o.label}

View File

@@ -2,7 +2,6 @@ import classNames from 'classnames';
import type { CSSProperties, ReactNode } from 'react'; import type { CSSProperties, ReactNode } from 'react';
import { useState } from 'react'; import { useState } from 'react';
import { useOsInfo } from '../../hooks/useOsInfo'; import { useOsInfo } from '../../hooks/useOsInfo';
import { trackEvent } from '../../lib/analytics';
import type { ButtonProps } from './Button'; import type { ButtonProps } from './Button';
import { Button } from './Button'; import { Button } from './Button';
import { Label } from './Label'; import { Label } from './Label';
@@ -22,7 +21,6 @@ export interface SelectProps<T extends string> {
onChange: (value: T) => void; onChange: (value: T) => void;
size?: ButtonProps['size']; size?: ButtonProps['size'];
className?: string; className?: string;
event?: string;
disabled?: boolean; disabled?: boolean;
} }
@@ -38,7 +36,6 @@ export function Select<T extends string>({
leftSlot, leftSlot,
onChange, onChange,
className, className,
event,
size = 'md', size = 'md',
}: SelectProps<T>) { }: SelectProps<T>) {
const osInfo = useOsInfo(); const osInfo = useOsInfo();
@@ -48,9 +45,6 @@ export function Select<T extends string>({
const handleChange = (value: T) => { const handleChange = (value: T) => {
onChange?.(value); onChange?.(value);
if (event != null) {
trackEvent('select', 'click', { id: event, value });
}
}; };
return ( return (

View File

@@ -1,7 +1,6 @@
import classNames from 'classnames'; import classNames from 'classnames';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { memo, useEffect, useRef } from 'react'; import { memo, useEffect, useRef } from 'react';
import { trackEvent } from '../../../lib/analytics';
import { Icon } from '../Icon'; import { Icon } from '../Icon';
import type { RadioDropdownProps } from '../RadioDropdown'; import type { RadioDropdownProps } from '../RadioDropdown';
import { RadioDropdown } from '../RadioDropdown'; import { RadioDropdown } from '../RadioDropdown';
@@ -104,14 +103,7 @@ export function Tabs({
onChange={t.options.onChange} onChange={t.options.onChange}
> >
<button <button
onClick={ onClick={isActive ? undefined : () => onChangeValue(t.value)}
isActive
? undefined
: () => {
trackEvent('tab', 'click', { label, tab: t.value });
onChangeValue(t.value);
}
}
className={btnClassName} className={btnClassName}
> >
{option && 'shortLabel' in option && option.shortLabel {option && 'shortLabel' in option && option.shortLabel
@@ -133,14 +125,7 @@ export function Tabs({
return ( return (
<button <button
key={t.value} key={t.value}
onClick={ onClick={isActive ? undefined : () => onChangeValue(t.value)}
isActive
? undefined
: () => {
trackEvent('tab', 'click', { label, tab: t.value });
onChangeValue(t.value);
}
}
className={btnClassName} className={btnClassName}
> >
{t.label} {t.label}

View File

@@ -2,7 +2,6 @@ import { useMemo } from 'react';
import { useFloatingSidebarHidden } from '../../hooks/useFloatingSidebarHidden'; import { useFloatingSidebarHidden } from '../../hooks/useFloatingSidebarHidden';
import { useShouldFloatSidebar } from '../../hooks/useShouldFloatSidebar'; import { useShouldFloatSidebar } from '../../hooks/useShouldFloatSidebar';
import { useSidebarHidden } from '../../hooks/useSidebarHidden'; import { useSidebarHidden } from '../../hooks/useSidebarHidden';
import { trackEvent } from '../../lib/analytics';
import { IconButton } from '../core/IconButton'; import { IconButton } from '../core/IconButton';
import { HStack } from '../core/Stacks'; import { HStack } from '../core/Stacks';
import { CreateDropdown } from '../CreateDropdown'; import { CreateDropdown } from '../CreateDropdown';
@@ -22,8 +21,6 @@ export function SidebarActions() {
<HStack className="h-full"> <HStack className="h-full">
<IconButton <IconButton
onClick={async () => { onClick={async () => {
trackEvent('sidebar', 'toggle');
// NOTE: We're not using the (h) => !h pattern here because the data // NOTE: We're not using the (h) => !h pattern here because the data
// might be different if another window changed it (out of sync) // might be different if another window changed it (out of sync)
await setHidden(!hidden); await setHidden(!hidden);

View File

@@ -1,11 +1,9 @@
import { useFastMutation } from './useFastMutation'; import { useFastMutation } from './useFastMutation';
import { event } from '@tauri-apps/api'; import { event } from '@tauri-apps/api';
import { trackEvent } from '../lib/analytics';
export function useCancelHttpResponse(id: string | null) { export function useCancelHttpResponse(id: string | null) {
return useFastMutation<void>({ return useFastMutation<void>({
mutationKey: ['cancel_http_response', id], mutationKey: ['cancel_http_response', id],
mutationFn: () => event.emit(`cancel_http_response_${id}`), mutationFn: () => event.emit(`cancel_http_response_${id}`),
onSettled: () => trackEvent('http_response', 'cancel'),
}); });
} }

View File

@@ -1,5 +1,4 @@
import type { CookieJar } from '@yaakapp-internal/models'; import type { CookieJar } from '@yaakapp-internal/models';
import { trackEvent } from '../lib/analytics';
import { showPrompt } from '../lib/prompt'; import { showPrompt } from '../lib/prompt';
import { invokeCmd } from '../lib/tauri'; import { invokeCmd } from '../lib/tauri';
import { getActiveWorkspaceId } from './useActiveWorkspace'; import { getActiveWorkspaceId } from './useActiveWorkspace';
@@ -25,6 +24,5 @@ export function useCreateCookieJar() {
return invokeCmd('cmd_create_cookie_jar', { workspaceId, name }); return invokeCmd('cmd_create_cookie_jar', { workspaceId, name });
}, },
onSettled: () => trackEvent('cookie_jar', 'create'),
}); });
} }

View File

@@ -1,5 +1,4 @@
import type { Environment } from '@yaakapp-internal/models'; import type { Environment } from '@yaakapp-internal/models';
import { trackEvent } from '../lib/analytics';
import { showPrompt } from '../lib/prompt'; import { showPrompt } from '../lib/prompt';
import { setWorkspaceSearchParams } from '../lib/setWorkspaceSearchParams'; import { setWorkspaceSearchParams } from '../lib/setWorkspaceSearchParams';
import { invokeCmd } from '../lib/tauri'; import { invokeCmd } from '../lib/tauri';
@@ -33,7 +32,6 @@ export function useCreateEnvironment() {
environmentId: baseEnvironment.id, environmentId: baseEnvironment.id,
}); });
}, },
onSettled: () => trackEvent('environment', 'create'),
onSuccess: async (environment) => { onSuccess: async (environment) => {
if (environment == null) return; if (environment == null) return;
setWorkspaceSearchParams({ environment_id: environment.id }); setWorkspaceSearchParams({ environment_id: environment.id });

View File

@@ -1,5 +1,4 @@
import type { GrpcRequest } from '@yaakapp-internal/models'; import type { GrpcRequest } from '@yaakapp-internal/models';
import { trackEvent } from '../lib/analytics';
import { jotaiStore } from '../lib/jotai'; import { jotaiStore } from '../lib/jotai';
import { invokeCmd } from '../lib/tauri'; import { invokeCmd } from '../lib/tauri';
import { getActiveRequest } from './useActiveRequest'; import { getActiveRequest } from './useActiveRequest';
@@ -36,7 +35,6 @@ export function useCreateGrpcRequest() {
...patch, ...patch,
}); });
}, },
onSettled: () => trackEvent('grpc_request', 'create'),
onSuccess: async (request) => { onSuccess: async (request) => {
await router.navigate({ await router.navigate({
to: '/workspaces/$workspaceId', to: '/workspaces/$workspaceId',

View File

@@ -1,5 +1,4 @@
import type { HttpRequest } from '@yaakapp-internal/models'; import type { HttpRequest } from '@yaakapp-internal/models';
import { trackEvent } from '../lib/analytics';
import { router } from '../lib/router'; import { router } from '../lib/router';
import { invokeCmd } from '../lib/tauri'; import { invokeCmd } from '../lib/tauri';
import { getActiveRequest } from './useActiveRequest'; import { getActiveRequest } from './useActiveRequest';
@@ -30,7 +29,6 @@ export function useCreateHttpRequest() {
request: { workspaceId, ...patch }, request: { workspaceId, ...patch },
}); });
}, },
onSettled: () => trackEvent('http_request', 'create'),
onSuccess: async (request) => { onSuccess: async (request) => {
await router.navigate({ await router.navigate({
to: '/workspaces/$workspaceId', to: '/workspaces/$workspaceId',

View File

@@ -1,6 +1,5 @@
import type { Workspace } from '@yaakapp-internal/models'; import type { Workspace } from '@yaakapp-internal/models';
import { InlineCode } from '../components/core/InlineCode'; import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import { showConfirmDelete } from '../lib/confirm'; import { showConfirmDelete } from '../lib/confirm';
import { router } from '../lib/router'; import { router } from '../lib/router';
import { invokeCmd } from '../lib/tauri'; import { invokeCmd } from '../lib/tauri';
@@ -24,7 +23,6 @@ export function useDeleteActiveWorkspace() {
if (!confirmed) return null; if (!confirmed) return null;
return invokeCmd('cmd_delete_workspace', { workspaceId: workspace?.id }); return invokeCmd('cmd_delete_workspace', { workspaceId: workspace?.id });
}, },
onSettled: () => trackEvent('workspace', 'delete'),
onSuccess: async (workspace) => { onSuccess: async (workspace) => {
if (workspace === null) return; if (workspace === null) return;
await router.navigate({ to: '/workspaces' }); await router.navigate({ to: '/workspaces' });

View File

@@ -1,6 +1,5 @@
import type { GrpcRequest } from '@yaakapp-internal/models'; import type { GrpcRequest } from '@yaakapp-internal/models';
import { InlineCode } from '../components/core/InlineCode'; import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import { showConfirmDelete } from '../lib/confirm'; import { showConfirmDelete } from '../lib/confirm';
import { resolvedModelName } from '../lib/resolvedModelName'; import { resolvedModelName } from '../lib/resolvedModelName';
import { invokeCmd } from '../lib/tauri'; import { invokeCmd } from '../lib/tauri';
@@ -24,6 +23,5 @@ export function useDeleteAnyGrpcRequest() {
} }
return invokeCmd('cmd_delete_grpc_request', { requestId: request.id }); return invokeCmd('cmd_delete_grpc_request', { requestId: request.id });
}, },
onSuccess: () => trackEvent('grpc_request', 'delete'),
}); });
} }

View File

@@ -1,6 +1,5 @@
import type { HttpRequest } from '@yaakapp-internal/models'; import type { HttpRequest } from '@yaakapp-internal/models';
import { InlineCode } from '../components/core/InlineCode'; import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import { showConfirmDelete } from '../lib/confirm'; import { showConfirmDelete } from '../lib/confirm';
import { resolvedModelName } from '../lib/resolvedModelName'; import { resolvedModelName } from '../lib/resolvedModelName';
import { invokeCmd } from '../lib/tauri'; import { invokeCmd } from '../lib/tauri';
@@ -24,6 +23,5 @@ export function useDeleteAnyHttpRequest() {
} }
return invokeCmd<HttpRequest>('cmd_delete_http_request', { requestId: request.id }); return invokeCmd<HttpRequest>('cmd_delete_http_request', { requestId: request.id });
}, },
onSuccess: () => trackEvent('http_request', 'delete'),
}); });
} }

View File

@@ -1,7 +1,6 @@
import type { CookieJar } from '@yaakapp-internal/models'; import type { CookieJar } from '@yaakapp-internal/models';
import { useSetAtom } from 'jotai'; import { useSetAtom } from 'jotai';
import { InlineCode } from '../components/core/InlineCode'; import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import { showConfirmDelete } from '../lib/confirm'; import { showConfirmDelete } from '../lib/confirm';
import { invokeCmd } from '../lib/tauri'; import { invokeCmd } from '../lib/tauri';
import { cookieJarsAtom } from './useCookieJars'; import { cookieJarsAtom } from './useCookieJars';
@@ -26,7 +25,6 @@ export function useDeleteCookieJar(cookieJar: CookieJar | null) {
if (!confirmed) return null; if (!confirmed) return null;
return invokeCmd('cmd_delete_cookie_jar', { cookieJarId: cookieJar?.id }); return invokeCmd('cmd_delete_cookie_jar', { cookieJarId: cookieJar?.id });
}, },
onSettled: () => trackEvent('cookie_jar', 'delete'),
onSuccess: (cookieJar) => { onSuccess: (cookieJar) => {
if (cookieJar == null) return; if (cookieJar == null) return;

View File

@@ -1,7 +1,6 @@
import type { Environment } from '@yaakapp-internal/models'; import type { Environment } from '@yaakapp-internal/models';
import { useSetAtom } from 'jotai'; import { useSetAtom } from 'jotai';
import { InlineCode } from '../components/core/InlineCode'; import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import { showConfirmDelete } from '../lib/confirm'; import { showConfirmDelete } from '../lib/confirm';
import { invokeCmd } from '../lib/tauri'; import { invokeCmd } from '../lib/tauri';
import { environmentsAtom } from './useEnvironments'; import { environmentsAtom } from './useEnvironments';
@@ -26,7 +25,6 @@ export function useDeleteEnvironment(environment: Environment | null) {
if (!confirmed) return null; if (!confirmed) return null;
return invokeCmd('cmd_delete_environment', { environmentId: environment?.id }); return invokeCmd('cmd_delete_environment', { environmentId: environment?.id });
}, },
onSettled: () => trackEvent('environment', 'delete'),
onSuccess: (environment) => { onSuccess: (environment) => {
if (environment == null) return; if (environment == null) return;

View File

@@ -1,7 +1,6 @@
import type { Folder } from '@yaakapp-internal/models'; import type { Folder } from '@yaakapp-internal/models';
import { useSetAtom } from 'jotai'; import { useSetAtom } from 'jotai';
import { InlineCode } from '../components/core/InlineCode'; import { InlineCode } from '../components/core/InlineCode';
import { trackEvent } from '../lib/analytics';
import { showConfirmDelete } from '../lib/confirm'; import { showConfirmDelete } from '../lib/confirm';
import { invokeCmd } from '../lib/tauri'; import { invokeCmd } from '../lib/tauri';
import { useFastMutation } from './useFastMutation'; import { useFastMutation } from './useFastMutation';
@@ -27,7 +26,6 @@ export function useDeleteFolder(id: string | null) {
if (!confirmed) return null; if (!confirmed) return null;
return invokeCmd('cmd_delete_folder', { folderId: id }); return invokeCmd('cmd_delete_folder', { folderId: id });
}, },
onSettled: () => trackEvent('folder', 'delete'),
onSuccess: (folder) => { onSuccess: (folder) => {
if (folder == null) return; if (folder == null) return;

View File

@@ -1,7 +1,6 @@
import { useFastMutation } from './useFastMutation'; import { useFastMutation } from './useFastMutation';
import type { GrpcConnection } from '@yaakapp-internal/models'; import type { GrpcConnection } from '@yaakapp-internal/models';
import {useSetAtom} from "jotai"; import {useSetAtom} from "jotai";
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri'; import { invokeCmd } from '../lib/tauri';
import {grpcConnectionsAtom} from "./useGrpcConnections"; import {grpcConnectionsAtom} from "./useGrpcConnections";
import {removeModelById} from "./useSyncModelStores"; import {removeModelById} from "./useSyncModelStores";
@@ -13,7 +12,6 @@ export function useDeleteGrpcConnection(id: string | null) {
mutationFn: async () => { mutationFn: async () => {
return await invokeCmd('cmd_delete_grpc_connection', { id: id }); return await invokeCmd('cmd_delete_grpc_connection', { id: id });
}, },
onSettled: () => trackEvent('grpc_connection', 'delete'),
onSuccess: (connection) => { onSuccess: (connection) => {
if (connection == null) return; if (connection == null) return;

View File

@@ -1,6 +1,5 @@
import { useFastMutation } from './useFastMutation'; import { useFastMutation } from './useFastMutation';
import { useSetAtom } from 'jotai'; import { useSetAtom } from 'jotai';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri'; import { invokeCmd } from '../lib/tauri';
import { grpcConnectionsAtom } from './useGrpcConnections'; import { grpcConnectionsAtom } from './useGrpcConnections';
@@ -12,7 +11,6 @@ export function useDeleteGrpcConnections(requestId?: string) {
if (requestId === undefined) return; if (requestId === undefined) return;
await invokeCmd('cmd_delete_all_grpc_connections', { requestId }); await invokeCmd('cmd_delete_all_grpc_connections', { requestId });
}, },
onSettled: () => trackEvent('grpc_connection', 'delete_many'),
onSuccess: () => { onSuccess: () => {
setGrpcConnections((all) => all.filter((r) => r.requestId !== requestId)); setGrpcConnections((all) => all.filter((r) => r.requestId !== requestId));
}, },

View File

@@ -1,7 +1,6 @@
import { useFastMutation } from './useFastMutation'; import { useFastMutation } from './useFastMutation';
import type { HttpResponse } from '@yaakapp-internal/models'; import type { HttpResponse } from '@yaakapp-internal/models';
import {useSetAtom} from "jotai"; import {useSetAtom} from "jotai";
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri'; import { invokeCmd } from '../lib/tauri';
import {httpResponsesAtom} from "./useHttpResponses"; import {httpResponsesAtom} from "./useHttpResponses";
import {removeModelById} from "./useSyncModelStores"; import {removeModelById} from "./useSyncModelStores";
@@ -13,7 +12,6 @@ export function useDeleteHttpResponse(id: string | null) {
mutationFn: async () => { mutationFn: async () => {
return await invokeCmd('cmd_delete_http_response', { id: id }); return await invokeCmd('cmd_delete_http_response', { id: id });
}, },
onSettled: () => trackEvent('http_response', 'delete'),
onSuccess: (response) => { onSuccess: (response) => {
setHttpResponses(removeModelById(response)); setHttpResponses(removeModelById(response));
} }

View File

@@ -1,6 +1,5 @@
import { useFastMutation } from './useFastMutation'; import { useFastMutation } from './useFastMutation';
import { useSetAtom } from 'jotai'; import { useSetAtom } from 'jotai';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri'; import { invokeCmd } from '../lib/tauri';
import { httpResponsesAtom } from './useHttpResponses'; import { httpResponsesAtom } from './useHttpResponses';
@@ -15,6 +14,5 @@ export function useDeleteHttpResponses(requestId?: string) {
onSuccess: () => { onSuccess: () => {
setHttpResponses((all) => all.filter((r) => r.requestId !== requestId)); setHttpResponses((all) => all.filter((r) => r.requestId !== requestId));
}, },
onSettled: () => trackEvent('http_response', 'delete_many'),
}); });
} }

View File

@@ -1,11 +1,9 @@
import { useFastMutation } from './useFastMutation'; import { useFastMutation } from './useFastMutation';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri'; import { invokeCmd } from '../lib/tauri';
export function useDuplicateFolder(id: string) { export function useDuplicateFolder(id: string) {
return useFastMutation<void, string>({ return useFastMutation<void, string>({
mutationKey: ['duplicate_folder', id], mutationKey: ['duplicate_folder', id],
mutationFn: () => invokeCmd('cmd_duplicate_folder', { id }), mutationFn: () => invokeCmd('cmd_duplicate_folder', { id }),
onSettled: () => trackEvent('folder', 'duplicate'),
}); });
} }

View File

@@ -1,5 +1,4 @@
import type { GrpcRequest } from '@yaakapp-internal/models'; import type { GrpcRequest } from '@yaakapp-internal/models';
import { trackEvent } from '../lib/analytics';
import { router } from '../lib/router'; import { router } from '../lib/router';
import { invokeCmd } from '../lib/tauri'; import { invokeCmd } from '../lib/tauri';
import { useFastMutation } from './useFastMutation'; import { useFastMutation } from './useFastMutation';
@@ -18,7 +17,6 @@ export function useDuplicateGrpcRequest({
if (id === null) throw new Error("Can't duplicate a null grpc request"); if (id === null) throw new Error("Can't duplicate a null grpc request");
return invokeCmd('cmd_duplicate_grpc_request', { id }); return invokeCmd('cmd_duplicate_grpc_request', { id });
}, },
onSettled: () => trackEvent('grpc_request', 'duplicate'),
onSuccess: async (request) => { onSuccess: async (request) => {
if (id == null) return; if (id == null) return;

View File

@@ -1,5 +1,4 @@
import type { HttpRequest } from '@yaakapp-internal/models'; import type { HttpRequest } from '@yaakapp-internal/models';
import { trackEvent } from '../lib/analytics';
import { router } from '../lib/router'; import { router } from '../lib/router';
import { invokeCmd } from '../lib/tauri'; import { invokeCmd } from '../lib/tauri';
import { useFastMutation } from './useFastMutation'; import { useFastMutation } from './useFastMutation';
@@ -17,7 +16,6 @@ export function useDuplicateHttpRequest({
if (id === null) throw new Error("Can't duplicate a null request"); if (id === null) throw new Error("Can't duplicate a null request");
return invokeCmd('cmd_duplicate_http_request', { id }); return invokeCmd('cmd_duplicate_http_request', { id });
}, },
onSettled: () => trackEvent('http_request', 'duplicate'),
onSuccess: async (request) => { onSuccess: async (request) => {
if (navigateAfter) { if (navigateAfter) {
await router.navigate({ await router.navigate({

View File

@@ -1,7 +1,6 @@
import type { MutationKey } from '@tanstack/react-query'; import type { MutationKey } from '@tanstack/react-query';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { showToast } from '../lib/toast'; import { showToast } from '../lib/toast';
import { trackEvent } from '../lib/analytics';
interface MutationOptions<TData, TError, TVariables> { interface MutationOptions<TData, TError, TVariables> {
mutationKey: MutationKey; mutationKey: MutationKey;
@@ -36,7 +35,6 @@ export function createFastMutation<TData = unknown, TError = unknown, TVariables
const stringKey = mutationKey.join('.'); const stringKey = mutationKey.join('.');
const e = err as TError; const e = err as TError;
console.log('mutation error', stringKey, e); console.log('mutation error', stringKey, e);
trackEvent('mutation', 'error', { key: stringKey });
if (!disableToastError) { if (!disableToastError) {
showToast({ showToast({
id: stringKey, id: stringKey,

View File

@@ -1,7 +1,6 @@
import { useMutation, useQuery } from '@tanstack/react-query'; import { useMutation, useQuery } from '@tanstack/react-query';
import { emit } from '@tauri-apps/api/event'; import { emit } from '@tauri-apps/api/event';
import type { GrpcConnection, GrpcRequest } from '@yaakapp-internal/models'; import type { GrpcConnection, GrpcRequest } from '@yaakapp-internal/models';
import { trackEvent } from '../lib/analytics';
import { minPromiseMillis } from '../lib/minPromiseMillis'; import { minPromiseMillis } from '../lib/minPromiseMillis';
import { invokeCmd } from '../lib/tauri'; import { invokeCmd } from '../lib/tauri';
import { useActiveEnvironment } from './useActiveEnvironment'; import { useActiveEnvironment } from './useActiveEnvironment';
@@ -24,26 +23,22 @@ export function useGrpc(
mutationKey: ['grpc_go', conn?.id], mutationKey: ['grpc_go', conn?.id],
mutationFn: () => mutationFn: () =>
invokeCmd<void>('cmd_grpc_go', { requestId, environmentId: environment?.id, protoFiles }), invokeCmd<void>('cmd_grpc_go', { requestId, environmentId: environment?.id, protoFiles }),
onSettled: () => trackEvent('grpc_request', 'send'),
}); });
const send = useMutation({ const send = useMutation({
mutationKey: ['grpc_send', conn?.id], mutationKey: ['grpc_send', conn?.id],
mutationFn: ({ message }: { message: string }) => mutationFn: ({ message }: { message: string }) =>
emit(`grpc_client_msg_${conn?.id ?? 'none'}`, { Message: message }), emit(`grpc_client_msg_${conn?.id ?? 'none'}`, { Message: message }),
onSettled: () => trackEvent('grpc_connection', 'send'),
}); });
const cancel = useMutation({ const cancel = useMutation({
mutationKey: ['grpc_cancel', conn?.id ?? 'n/a'], mutationKey: ['grpc_cancel', conn?.id ?? 'n/a'],
mutationFn: () => emit(`grpc_client_msg_${conn?.id ?? 'none'}`, 'Cancel'), mutationFn: () => emit(`grpc_client_msg_${conn?.id ?? 'none'}`, 'Cancel'),
onSettled: () => trackEvent('grpc_connection', 'cancel'),
}); });
const commit = useMutation({ const commit = useMutation({
mutationKey: ['grpc_commit', conn?.id ?? 'n/a'], mutationKey: ['grpc_commit', conn?.id ?? 'n/a'],
mutationFn: () => emit(`grpc_client_msg_${conn?.id ?? 'none'}`, 'Commit'), mutationFn: () => emit(`grpc_client_msg_${conn?.id ?? 'none'}`, 'Commit'),
onSettled: () => trackEvent('grpc_connection', 'commit'),
}); });
const debouncedUrl = useDebouncedValue<string>(req?.url ?? '', 1000); const debouncedUrl = useDebouncedValue<string>(req?.url ?? '', 1000);

View File

@@ -1,5 +1,4 @@
import { useFastMutation } from './useFastMutation'; import { useFastMutation } from './useFastMutation';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri'; import { invokeCmd } from '../lib/tauri';
export function useInstallPlugin() { export function useInstallPlugin() {
@@ -8,6 +7,5 @@ export function useInstallPlugin() {
mutationFn: async (directory: string) => { mutationFn: async (directory: string) => {
await invokeCmd('cmd_install_plugin', { directory }); await invokeCmd('cmd_install_plugin', { directory });
}, },
onSettled: () => trackEvent('plugin', 'create'),
}); });
} }

View File

@@ -1,5 +1,4 @@
import type { HttpResponse } from '@yaakapp-internal/models'; import type { HttpResponse } from '@yaakapp-internal/models';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri'; import { invokeCmd } from '../lib/tauri';
import { getActiveCookieJar } from './useActiveCookieJar'; import { getActiveCookieJar } from './useActiveCookieJar';
import { getActiveEnvironment } from './useActiveEnvironment'; import { getActiveEnvironment } from './useActiveEnvironment';
@@ -21,6 +20,5 @@ export function useSendAnyHttpRequest() {
cookieJarId: getActiveCookieJar()?.id, cookieJarId: getActiveCookieJar()?.id,
}); });
}, },
onSettled: () => trackEvent('http_request', 'send'),
}); });
} }

View File

@@ -1,6 +1,5 @@
import { useFastMutation } from './useFastMutation'; import { useFastMutation } from './useFastMutation';
import type { Plugin } from '@yaakapp-internal/models'; import type { Plugin } from '@yaakapp-internal/models';
import { trackEvent } from '../lib/analytics';
import { invokeCmd } from '../lib/tauri'; import { invokeCmd } from '../lib/tauri';
export function useUninstallPlugin(pluginId: string) { export function useUninstallPlugin(pluginId: string) {
@@ -9,6 +8,5 @@ export function useUninstallPlugin(pluginId: string) {
mutationFn: async () => { mutationFn: async () => {
return invokeCmd('cmd_uninstall_plugin', { pluginId }); return invokeCmd('cmd_uninstall_plugin', { pluginId });
}, },
onSettled: () => trackEvent('plugin', 'delete'),
}); });
} }

View File

@@ -1,14 +0,0 @@
import type { AnalyticsAction, AnalyticsResource } from '../../src-tauri/bindings/analytics';
import { invokeCmd } from './tauri';
export function trackEvent(
resource: AnalyticsResource,
action: AnalyticsAction,
attributes: Record<string, string | number> = {},
) {
invokeCmd('cmd_track_event', {
resource: resource,
action,
attributes,
}).catch(console.error);
}

View File

@@ -1,12 +1,10 @@
import { atom } from 'jotai/index'; import { atom } from 'jotai/index';
import type { DialogInstance } from '../components/Dialogs'; import type { DialogInstance } from '../components/Dialogs';
import { trackEvent } from './analytics';
import { jotaiStore } from './jotai'; import { jotaiStore } from './jotai';
export const dialogsAtom = atom<DialogInstance[]>([]); export const dialogsAtom = atom<DialogInstance[]>([]);
export function showDialog({ id, ...props }: DialogInstance) { export function showDialog({ id, ...props }: DialogInstance) {
trackEvent('dialog', 'show', { id });
jotaiStore.set(dialogsAtom, (a) => [...a.filter((d) => d.id !== id), { id, ...props }]); jotaiStore.set(dialogsAtom, (a) => [...a.filter((d) => d.id !== id), { id, ...props }]);
} }

View File

@@ -70,7 +70,6 @@ type TauriCmd =
| 'cmd_set_update_mode' | 'cmd_set_update_mode'
| 'cmd_template_functions' | 'cmd_template_functions'
| 'cmd_template_tokens_to_string' | 'cmd_template_tokens_to_string'
| 'cmd_track_event'
| 'cmd_uninstall_plugin' | 'cmd_uninstall_plugin'
| 'cmd_update_cookie_jar' | 'cmd_update_cookie_jar'
| 'cmd_update_environment' | 'cmd_update_environment'