diff --git a/src-tauri/src/analytics.rs b/src-tauri/src/analytics.rs new file mode 100644 index 00000000..788b8845 --- /dev/null +++ b/src-tauri/src/analytics.rs @@ -0,0 +1,75 @@ +use sqlx::types::JsonValue; + +use crate::is_dev; + +pub enum AnalyticsResource { + App, + // Workspace, + // Environment, + // Folder, + // HttpRequest, + // HttpResponse, +} + +pub enum AnalyticsAction { + Launch, + // Create, + // Update, + // Upsert, + // Delete, + // Send, + // Duplicate, +} + +fn resource_name(resource: AnalyticsResource) -> &'static str { + match resource { + AnalyticsResource::App => "app", + // AnalyticsResource::Workspace => "workspace", + // AnalyticsResource::Environment => "environment", + // AnalyticsResource::Folder => "folder", + // AnalyticsResource::HttpRequest => "http_request", + // AnalyticsResource::HttpResponse => "http_response", + } +} + +fn action_name(action: AnalyticsAction) -> &'static str { + match action { + AnalyticsAction::Launch => "launch", + // AnalyticsAction::Create => "create", + // AnalyticsAction::Update => "update", + // AnalyticsAction::Upsert => "upsert", + // AnalyticsAction::Delete => "delete", + // AnalyticsAction::Send => "send", + // AnalyticsAction::Duplicate => "duplicate", + } +} + +pub async fn track_event( + resource: AnalyticsResource, + action: AnalyticsAction, + attributes: Option, +) { + let event = format!("{}.{}", resource_name(resource), action_name(action)); + let attributes_json = attributes.unwrap_or("{}".to_string().into()).to_string(); + let params = vec![ + ("e", event.clone()), + ("a", attributes_json.clone()), + ("id", "site_zOK0d7jeBy2TLxFCnZ".to_string()), + ]; + let url = format!("https://t.yaak.app/t/e"); + let req = reqwest::Client::builder() + .build() + .unwrap() + .get(&url) + .query(¶ms); + + if is_dev() { + println!("Ignore dev analytics event: {}", event); + } else { + if let Err(e) = req.send().await { + println!("Error sending analytics event: {}", e); + } else { + println!("Sent analytics event: {}", event); + } + } +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index b80bd988..c42f11ba 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -32,6 +32,9 @@ use tokio::sync::Mutex; use window_ext::TrafficLightWindowExt; +use crate::analytics::{AnalyticsAction, AnalyticsResource}; + +mod analytics; mod models; mod plugin; mod render; @@ -92,7 +95,7 @@ async fn actually_send_request( let environment_ref = environment.as_ref(); let workspace = models::get_workspace(&request.workspace_id, pool) .await - .expect("Failed to get workspace"); + .expect("Failed to get Workspace"); let mut url_string = render::render(&request.url, &workspace, environment.as_ref()); @@ -363,7 +366,7 @@ async fn create_workspace( }, ) .await - .expect("Failed to create workspace"); + .expect("Failed to create Workspace"); emit_and_return(&window, "created_model", created_workspace) } @@ -684,7 +687,7 @@ async fn list_workspaces( }, ) .await - .expect("Failed to create workspace"); + .expect("Failed to create Workspace"); Ok(vec![workspace]) } else { Ok(workspaces) @@ -706,7 +709,7 @@ async fn delete_workspace( let pool = &*db_instance.lock().await; let workspace = models::delete_workspace(workspace_id, pool) .await - .expect("Failed to delete workspace"); + .expect("Failed to delete Workspace"); emit_and_return(&window, "deleted_model", workspace) } @@ -813,6 +816,11 @@ fn main() { let w = create_window(app_handle, None); w.restore_state(StateFlags::all()) .expect("Failed to restore window state"); + + tauri::async_runtime::block_on(async move { + analytics::track_event(AnalyticsResource::App, AnalyticsAction::Launch, None) + .await; + }) } // ExitRequested { api, .. } => { diff --git a/src-tauri/src/models.rs b/src-tauri/src/models.rs index 59824a4c..fc343142 100644 --- a/src-tauri/src/models.rs +++ b/src-tauri/src/models.rs @@ -659,7 +659,7 @@ pub async fn upsert_workspace( .execute(pool) .await?; - get_workspace(&workspace.id, pool).await + get_workspace(&id, pool).await } pub async fn update_response( diff --git a/src-web/components/Sidebar.tsx b/src-web/components/Sidebar.tsx index e69a58bb..e1ca3a10 100644 --- a/src-web/components/Sidebar.tsx +++ b/src-web/components/Sidebar.tsx @@ -16,6 +16,7 @@ import { useFolders } from '../hooks/useFolders'; import { useKeyValue } from '../hooks/useKeyValue'; import { useLatestResponse } from '../hooks/useLatestResponse'; import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent'; +import { usePrompt } from '../hooks/usePrompt'; import { useRequests } from '../hooks/useRequests'; import { useSendAnyRequest } from '../hooks/useSendAnyRequest'; import { useSidebarHidden } from '../hooks/useSidebarHidden'; @@ -28,6 +29,7 @@ import { isResponseLoading } from '../lib/models'; import { Dropdown } from './core/Dropdown'; import { Icon } from './core/Icon'; import { IconButton } from './core/IconButton'; +import { InlineCode } from './core/InlineCode'; import { VStack } from './core/Stacks'; import { StatusTag } from './core/StatusTag'; import { DropMarker } from './DropMarker'; @@ -493,6 +495,8 @@ const SidebarItem = forwardRef(function SidebarItem( const deleteRequest = useDeleteFolder(itemId); const latestResponse = useLatestResponse(itemId); const updateRequest = useUpdateRequest(itemId); + const updateAnyFolder = useUpdateAnyFolder(); + const prompt = usePrompt(); const [editing, setEditing] = useState(false); const activeRequestId = useActiveRequestId(); const isActive = activeRequestId === itemId; @@ -562,6 +566,25 @@ const SidebarItem = forwardRef(function SidebarItem( }, }, { type: 'separator', label: itemName }, + { + key: 'rename', + label: 'Rename', + leftSlot: , + onSelect: async () => { + const name = await prompt({ + title: 'Rename Folder', + description: ( + <> + Enter a new name for {itemName} + + ), + name: 'name', + label: 'Name', + defaultValue: itemName, + }); + updateAnyFolder.mutate({ id: itemId, update: (f) => ({ ...f, name }) }); + }, + }, { key: 'deleteFolder', label: 'Delete', diff --git a/src-web/components/core/Editor/extensions.ts b/src-web/components/core/Editor/extensions.ts index a5843218..f540bf55 100644 --- a/src-web/components/core/Editor/extensions.ts +++ b/src-web/components/core/Editor/extensions.ts @@ -32,11 +32,11 @@ import { } from '@codemirror/view'; import { tags as t } from '@lezer/highlight'; import { graphql, graphqlLanguageSupport } from 'cm6-graphql'; +import type { Environment, Workspace } from '../../../lib/models'; import type { EditorProps } from './index'; import { text } from './text/extension'; import { twig } from './twig/extension'; import { url } from './url/extension'; -import type { Environment, Workspace } from '../../../lib/models'; export const myHighlightStyle = HighlightStyle.define([ { @@ -125,7 +125,6 @@ export const baseExtensions = [ // autocompletion({ closeOnBlur: true, interactionDelay: 200, activateOnTyping: false }), autocompletion({ // closeOnBlur: false, - interactionDelay: 200, compareCompletions: (a, b) => { // Don't sort completions at all, only on boost return (a.boost ?? 0) - (b.boost ?? 0);