mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-23 09:08:32 +02:00
Add basic analytics
This commit is contained in:
@@ -74,7 +74,7 @@ async fn send_ephemeral_request(
|
|||||||
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
db_instance: State<'_, Mutex<Pool<Sqlite>>>,
|
||||||
) -> Result<models::HttpResponse, String> {
|
) -> Result<models::HttpResponse, String> {
|
||||||
let pool = &*db_instance.lock().await;
|
let pool = &*db_instance.lock().await;
|
||||||
let response = models::HttpResponse::default();
|
let response = models::HttpResponse::new();
|
||||||
let environment_id2 = environment_id.unwrap_or("n/a").to_string();
|
let environment_id2 = environment_id.unwrap_or("n/a").to_string();
|
||||||
request.id = "".to_string();
|
request.id = "".to_string();
|
||||||
return actually_send_request(request, &response, &environment_id2, &app_handle, pool).await;
|
return actually_send_request(request, &response, &environment_id2, &app_handle, pool).await;
|
||||||
|
|||||||
@@ -118,6 +118,15 @@ pub struct HttpResponse {
|
|||||||
pub headers: Json<Vec<HttpResponseHeader>>,
|
pub headers: Json<Vec<HttpResponseHeader>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl HttpResponse {
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
model: "http_response".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize, Default)]
|
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
#[serde(default, rename_all = "camelCase")]
|
#[serde(default, rename_all = "camelCase")]
|
||||||
pub struct KeyValue {
|
pub struct KeyValue {
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { appWindow } from '@tauri-apps/api/window';
|
import { appWindow } from '@tauri-apps/api/window';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
import { useEffectOnce } from 'react-use';
|
||||||
import { keyValueQueryKey } from '../hooks/useKeyValue';
|
import { keyValueQueryKey } from '../hooks/useKeyValue';
|
||||||
|
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
||||||
|
import { useRecentEnvironments } from '../hooks/useRecentEnvironments';
|
||||||
|
import { useRecentRequests } from '../hooks/useRecentRequests';
|
||||||
|
import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
|
||||||
import { requestsQueryKey } from '../hooks/useRequests';
|
import { requestsQueryKey } from '../hooks/useRequests';
|
||||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
||||||
import { responsesQueryKey } from '../hooks/useResponses';
|
import { responsesQueryKey } from '../hooks/useResponses';
|
||||||
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
|
|
||||||
import { workspacesQueryKey } from '../hooks/useWorkspaces';
|
import { workspacesQueryKey } from '../hooks/useWorkspaces';
|
||||||
|
import { trackPage } from '../lib/analytics';
|
||||||
import { DEFAULT_FONT_SIZE } from '../lib/constants';
|
import { DEFAULT_FONT_SIZE } from '../lib/constants';
|
||||||
import { NAMESPACE_NO_SYNC } from '../lib/keyValueStore';
|
import { NAMESPACE_NO_SYNC } from '../lib/keyValueStore';
|
||||||
import type { HttpRequest, HttpResponse, Model, Workspace } from '../lib/models';
|
import type { HttpRequest, HttpResponse, Model, Workspace } from '../lib/models';
|
||||||
import { modelsEq } from '../lib/models';
|
import { modelsEq } from '../lib/models';
|
||||||
import { useRecentRequests } from '../hooks/useRecentRequests';
|
|
||||||
import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
|
|
||||||
import { useRecentEnvironments } from '../hooks/useRecentEnvironments';
|
|
||||||
import { useLocation } from 'react-router-dom';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { setPathname } from '../lib/persistPathname';
|
import { setPathname } from '../lib/persistPathname';
|
||||||
|
|
||||||
export function GlobalHooks() {
|
export function GlobalHooks() {
|
||||||
@@ -30,9 +32,13 @@ export function GlobalHooks() {
|
|||||||
// Listen for location changes and update the pathname
|
// Listen for location changes and update the pathname
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setPathname(location.pathname);
|
setPathname(location.pathname).catch(console.error);
|
||||||
}, [location.pathname]);
|
}, [location.pathname]);
|
||||||
|
|
||||||
|
useEffectOnce(() => {
|
||||||
|
trackPage('/');
|
||||||
|
});
|
||||||
|
|
||||||
useListenToTauriEvent<Model>('created_model', ({ payload, windowLabel }) => {
|
useListenToTauriEvent<Model>('created_model', ({ payload, windowLabel }) => {
|
||||||
if (shouldIgnoreEvent(payload, windowLabel)) return;
|
if (shouldIgnoreEvent(payload, windowLabel)) return;
|
||||||
|
|
||||||
@@ -82,9 +88,8 @@ export function GlobalHooks() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!shouldIgnoreModel(payload)) {
|
if (!shouldIgnoreModel(payload)) {
|
||||||
queryClient.setQueryData<Model[]>(
|
queryClient.setQueryData<Model[]>(queryKey, (values) =>
|
||||||
queryKey,
|
values?.map((v) => (modelsEq(v, payload) ? payload : v)),
|
||||||
(values) => values?.map((v) => (modelsEq(v, payload) ? payload : v)),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Prop
|
|||||||
<HStack alignItems="center">
|
<HStack alignItems="center">
|
||||||
<WorkspaceActionsDropdown
|
<WorkspaceActionsDropdown
|
||||||
leftSlot={
|
leftSlot={
|
||||||
<div className="w-5 h-5 leading-5 rounded-sm text-[0.8em] bg-[#1B88DE] bg-opacity-80 text-white mr-1">
|
<div className="w-4 h-4 leading-4 rounded text-[0.8em] bg-[#1B88DE] bg-opacity-80 text-white mr-1">
|
||||||
{activeWorkspace?.name[0]?.toUpperCase()}
|
{activeWorkspace?.name[0]?.toUpperCase()}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { invoke } from '@tauri-apps/api';
|
import { invoke } from '@tauri-apps/api';
|
||||||
|
import { trackEvent } from '../lib/analytics';
|
||||||
import type { Environment } from '../lib/models';
|
import type { Environment } from '../lib/models';
|
||||||
import { environmentsQueryKey, useEnvironments } from './useEnvironments';
|
|
||||||
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
||||||
import { useAppRoutes } from './useAppRoutes';
|
import { useAppRoutes } from './useAppRoutes';
|
||||||
|
import { environmentsQueryKey, useEnvironments } from './useEnvironments';
|
||||||
import { usePrompt } from './usePrompt';
|
import { usePrompt } from './usePrompt';
|
||||||
import { useWorkspaces } from './useWorkspaces';
|
import { useWorkspaces } from './useWorkspaces';
|
||||||
|
|
||||||
@@ -29,6 +30,7 @@ export function useCreateEnvironment() {
|
|||||||
: [];
|
: [];
|
||||||
return invoke('create_environment', { name, variables, workspaceId });
|
return invoke('create_environment', { name, variables, workspaceId });
|
||||||
},
|
},
|
||||||
|
onSettled: () => trackEvent('environment', 'create'),
|
||||||
onSuccess: async (environment) => {
|
onSuccess: async (environment) => {
|
||||||
if (workspaceId == null) return;
|
if (workspaceId == null) return;
|
||||||
routes.setEnvironment(environment);
|
routes.setEnvironment(environment);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { invoke } from '@tauri-apps/api';
|
import { invoke } from '@tauri-apps/api';
|
||||||
|
import { trackEvent } from '../lib/analytics';
|
||||||
import type { Folder } from '../lib/models';
|
import type { Folder } from '../lib/models';
|
||||||
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
||||||
import { foldersQueryKey } from './useFolders';
|
import { foldersQueryKey } from './useFolders';
|
||||||
@@ -17,6 +18,7 @@ export function useCreateFolder() {
|
|||||||
patch.sortPriority = patch.sortPriority || Date.now();
|
patch.sortPriority = patch.sortPriority || Date.now();
|
||||||
return invoke('create_folder', { workspaceId, ...patch });
|
return invoke('create_folder', { workspaceId, ...patch });
|
||||||
},
|
},
|
||||||
|
onSettled: () => trackEvent('folder', 'create'),
|
||||||
onSuccess: async (request) => {
|
onSuccess: async (request) => {
|
||||||
await queryClient.invalidateQueries(foldersQueryKey({ workspaceId: request.workspaceId }));
|
await queryClient.invalidateQueries(foldersQueryKey({ workspaceId: request.workspaceId }));
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { invoke } from '@tauri-apps/api';
|
import { invoke } from '@tauri-apps/api';
|
||||||
|
import { trackEvent } from '../lib/analytics';
|
||||||
import type { HttpRequest } from '../lib/models';
|
import type { HttpRequest } from '../lib/models';
|
||||||
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
|
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
|
||||||
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
||||||
@@ -26,6 +27,7 @@ export function useCreateRequest() {
|
|||||||
patch.sortPriority = patch.sortPriority || maxSortPriority(requests) + 1000;
|
patch.sortPriority = patch.sortPriority || maxSortPriority(requests) + 1000;
|
||||||
return invoke('create_request', { workspaceId, ...patch });
|
return invoke('create_request', { workspaceId, ...patch });
|
||||||
},
|
},
|
||||||
|
onSettled: () => trackEvent('http_request', 'create'),
|
||||||
onSuccess: async (request) => {
|
onSuccess: async (request) => {
|
||||||
queryClient.setQueryData<HttpRequest[]>(
|
queryClient.setQueryData<HttpRequest[]>(
|
||||||
requestsQueryKey({ workspaceId: request.workspaceId }),
|
requestsQueryKey({ workspaceId: request.workspaceId }),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { invoke } from '@tauri-apps/api';
|
import { invoke } from '@tauri-apps/api';
|
||||||
|
import { trackEvent } from '../lib/analytics';
|
||||||
import type { Workspace } from '../lib/models';
|
import type { Workspace } from '../lib/models';
|
||||||
import { useAppRoutes } from './useAppRoutes';
|
import { useAppRoutes } from './useAppRoutes';
|
||||||
import { workspacesQueryKey } from './useWorkspaces';
|
import { workspacesQueryKey } from './useWorkspaces';
|
||||||
@@ -11,6 +12,7 @@ export function useCreateWorkspace({ navigateAfter }: { navigateAfter: boolean }
|
|||||||
mutationFn: (patch) => {
|
mutationFn: (patch) => {
|
||||||
return invoke('create_workspace', patch);
|
return invoke('create_workspace', patch);
|
||||||
},
|
},
|
||||||
|
onSettled: () => trackEvent('workspace', 'create'),
|
||||||
onSuccess: async (workspace) => {
|
onSuccess: async (workspace) => {
|
||||||
queryClient.setQueryData<Workspace[]>(workspacesQueryKey({}), (workspaces) => [
|
queryClient.setQueryData<Workspace[]>(workspacesQueryKey({}), (workspaces) => [
|
||||||
...(workspaces ?? []),
|
...(workspaces ?? []),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { invoke } from '@tauri-apps/api';
|
import { invoke } from '@tauri-apps/api';
|
||||||
import { InlineCode } from '../components/core/InlineCode';
|
import { InlineCode } from '../components/core/InlineCode';
|
||||||
|
import { trackEvent } from '../lib/analytics';
|
||||||
import type { HttpRequest } from '../lib/models';
|
import type { HttpRequest } from '../lib/models';
|
||||||
import { getRequest } from '../lib/store';
|
import { getRequest } from '../lib/store';
|
||||||
import { useConfirm } from './useConfirm';
|
import { useConfirm } from './useConfirm';
|
||||||
@@ -26,6 +27,7 @@ export function useDeleteAnyRequest() {
|
|||||||
if (!confirmed) return null;
|
if (!confirmed) return null;
|
||||||
return invoke('delete_request', { requestId: id });
|
return invoke('delete_request', { requestId: id });
|
||||||
},
|
},
|
||||||
|
onSettled: () => trackEvent('http_request', 'delete'),
|
||||||
onSuccess: async (request) => {
|
onSuccess: async (request) => {
|
||||||
// Was it cancelled?
|
// Was it cancelled?
|
||||||
if (request === null) return;
|
if (request === null) return;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { invoke } from '@tauri-apps/api';
|
import { invoke } from '@tauri-apps/api';
|
||||||
import { InlineCode } from '../components/core/InlineCode';
|
import { InlineCode } from '../components/core/InlineCode';
|
||||||
|
import { trackEvent } from '../lib/analytics';
|
||||||
import type { Environment, Workspace } from '../lib/models';
|
import type { Environment, Workspace } from '../lib/models';
|
||||||
import { useConfirm } from './useConfirm';
|
import { useConfirm } from './useConfirm';
|
||||||
import { environmentsQueryKey } from './useEnvironments';
|
import { environmentsQueryKey } from './useEnvironments';
|
||||||
@@ -23,13 +24,13 @@ export function useDeleteEnvironment(environment: Environment | null) {
|
|||||||
if (!confirmed) return null;
|
if (!confirmed) return null;
|
||||||
return invoke('delete_environment', { environmentId: environment?.id });
|
return invoke('delete_environment', { environmentId: environment?.id });
|
||||||
},
|
},
|
||||||
|
onSettled: () => trackEvent('environment', 'delete'),
|
||||||
onSuccess: async (environment) => {
|
onSuccess: async (environment) => {
|
||||||
if (environment === null) return;
|
if (environment === null) return;
|
||||||
|
|
||||||
const { id: environmentId, workspaceId } = environment;
|
const { id: environmentId, workspaceId } = environment;
|
||||||
queryClient.setQueryData<Workspace[]>(
|
queryClient.setQueryData<Workspace[]>(environmentsQueryKey({ workspaceId }), (environments) =>
|
||||||
environmentsQueryKey({ workspaceId }),
|
environments?.filter((e) => e.id !== environmentId),
|
||||||
(environments) => environments?.filter((e) => e.id !== environmentId),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { invoke } from '@tauri-apps/api';
|
import { invoke } from '@tauri-apps/api';
|
||||||
import { InlineCode } from '../components/core/InlineCode';
|
import { InlineCode } from '../components/core/InlineCode';
|
||||||
|
import { trackEvent } from '../lib/analytics';
|
||||||
import type { Folder } from '../lib/models';
|
import type { Folder } from '../lib/models';
|
||||||
import { getFolder } from '../lib/store';
|
import { getFolder } from '../lib/store';
|
||||||
import { useConfirm } from './useConfirm';
|
import { useConfirm } from './useConfirm';
|
||||||
@@ -26,6 +27,7 @@ export function useDeleteFolder(id: string | null) {
|
|||||||
if (!confirmed) return null;
|
if (!confirmed) return null;
|
||||||
return invoke('delete_folder', { folderId: id });
|
return invoke('delete_folder', { folderId: id });
|
||||||
},
|
},
|
||||||
|
onSettled: () => trackEvent('folder', 'delete'),
|
||||||
onSuccess: async (folder) => {
|
onSuccess: async (folder) => {
|
||||||
// Was it cancelled?
|
// Was it cancelled?
|
||||||
if (folder === null) return;
|
if (folder === null) return;
|
||||||
|
|||||||
@@ -1,40 +1,11 @@
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
import { invoke } from '@tauri-apps/api';
|
|
||||||
import { InlineCode } from '../components/core/InlineCode';
|
|
||||||
import type { HttpRequest } from '../lib/models';
|
import type { HttpRequest } from '../lib/models';
|
||||||
import { getRequest } from '../lib/store';
|
import { useDeleteAnyRequest } from './useDeleteAnyRequest';
|
||||||
import { useConfirm } from './useConfirm';
|
|
||||||
import { requestsQueryKey } from './useRequests';
|
|
||||||
import { responsesQueryKey } from './useResponses';
|
|
||||||
|
|
||||||
export function useDeleteRequest(id: string | null) {
|
export function useDeleteRequest(id: string | null) {
|
||||||
const queryClient = useQueryClient();
|
const deleteAnyRequest = useDeleteAnyRequest();
|
||||||
const confirm = useConfirm();
|
|
||||||
|
|
||||||
return useMutation<HttpRequest | null, string>({
|
return useMutation<HttpRequest | null, string>({
|
||||||
mutationFn: async () => {
|
mutationFn: () => deleteAnyRequest.mutateAsync(id ?? 'n/a'),
|
||||||
const request = await getRequest(id);
|
|
||||||
const confirmed = await confirm({
|
|
||||||
title: 'Delete Request',
|
|
||||||
variant: 'delete',
|
|
||||||
description: (
|
|
||||||
<>
|
|
||||||
Permanently delete <InlineCode>{request?.name}</InlineCode>?
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
});
|
|
||||||
if (!confirmed) return null;
|
|
||||||
return invoke('delete_request', { requestId: id });
|
|
||||||
},
|
|
||||||
onSuccess: async (request) => {
|
|
||||||
// Was it cancelled?
|
|
||||||
if (request === null) return;
|
|
||||||
|
|
||||||
const { workspaceId, id: requestId } = request;
|
|
||||||
queryClient.setQueryData(responsesQueryKey({ requestId }), []); // Responses were deleted
|
|
||||||
queryClient.setQueryData<HttpRequest[]>(requestsQueryKey({ workspaceId }), (requests) =>
|
|
||||||
(requests ?? []).filter((r) => r.id !== requestId),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { invoke } from '@tauri-apps/api';
|
import { invoke } from '@tauri-apps/api';
|
||||||
|
import { trackEvent } from '../lib/analytics';
|
||||||
import type { HttpResponse } from '../lib/models';
|
import type { HttpResponse } from '../lib/models';
|
||||||
import { responsesQueryKey } from './useResponses';
|
import { responsesQueryKey } from './useResponses';
|
||||||
|
|
||||||
@@ -9,6 +10,7 @@ export function useDeleteResponse(id: string | null) {
|
|||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
return await invoke('delete_response', { id: id });
|
return await invoke('delete_response', { id: id });
|
||||||
},
|
},
|
||||||
|
onSettled: () => trackEvent('http_response', 'delete'),
|
||||||
onSuccess: ({ requestId, id: responseId }) => {
|
onSuccess: ({ requestId, id: responseId }) => {
|
||||||
queryClient.setQueryData<HttpResponse[]>(responsesQueryKey({ requestId }), (responses) =>
|
queryClient.setQueryData<HttpResponse[]>(responsesQueryKey({ requestId }), (responses) =>
|
||||||
(responses ?? []).filter((response) => response.id !== responseId),
|
(responses ?? []).filter((response) => response.id !== responseId),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { invoke } from '@tauri-apps/api';
|
import { invoke } from '@tauri-apps/api';
|
||||||
|
import { trackEvent } from '../lib/analytics';
|
||||||
import { responsesQueryKey } from './useResponses';
|
import { responsesQueryKey } from './useResponses';
|
||||||
|
|
||||||
export function useDeleteResponses(requestId?: string) {
|
export function useDeleteResponses(requestId?: string) {
|
||||||
@@ -9,6 +10,7 @@ export function useDeleteResponses(requestId?: string) {
|
|||||||
if (requestId === undefined) return;
|
if (requestId === undefined) return;
|
||||||
await invoke('delete_all_responses', { requestId });
|
await invoke('delete_all_responses', { requestId });
|
||||||
},
|
},
|
||||||
|
onSettled: () => trackEvent('http_response', 'delete_many'),
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
if (requestId === undefined) return;
|
if (requestId === undefined) return;
|
||||||
queryClient.setQueryData(responsesQueryKey({ requestId }), []);
|
queryClient.setQueryData(responsesQueryKey({ requestId }), []);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { invoke } from '@tauri-apps/api';
|
import { invoke } from '@tauri-apps/api';
|
||||||
import { InlineCode } from '../components/core/InlineCode';
|
import { InlineCode } from '../components/core/InlineCode';
|
||||||
|
import { trackEvent } from '../lib/analytics';
|
||||||
import type { Workspace } from '../lib/models';
|
import type { Workspace } from '../lib/models';
|
||||||
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
||||||
import { useAppRoutes } from './useAppRoutes';
|
import { useAppRoutes } from './useAppRoutes';
|
||||||
@@ -28,6 +29,7 @@ export function useDeleteWorkspace(workspace: Workspace | null) {
|
|||||||
if (!confirmed) return null;
|
if (!confirmed) return null;
|
||||||
return invoke('delete_workspace', { workspaceId: workspace?.id });
|
return invoke('delete_workspace', { workspaceId: workspace?.id });
|
||||||
},
|
},
|
||||||
|
onSettled: () => trackEvent('workspace', 'delete'),
|
||||||
onSuccess: async (workspace) => {
|
onSuccess: async (workspace) => {
|
||||||
if (workspace === null) return;
|
if (workspace === null) return;
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { invoke } from '@tauri-apps/api';
|
import { invoke } from '@tauri-apps/api';
|
||||||
|
import { trackEvent } from '../lib/analytics';
|
||||||
import type { HttpRequest } from '../lib/models';
|
import type { HttpRequest } from '../lib/models';
|
||||||
|
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
|
||||||
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
||||||
import { useAppRoutes } from './useAppRoutes';
|
import { useAppRoutes } from './useAppRoutes';
|
||||||
import { requestsQueryKey } from './useRequests';
|
import { requestsQueryKey } from './useRequests';
|
||||||
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
|
|
||||||
|
|
||||||
export function useDuplicateRequest({
|
export function useDuplicateRequest({
|
||||||
id,
|
id,
|
||||||
@@ -22,6 +23,7 @@ export function useDuplicateRequest({
|
|||||||
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 invoke('duplicate_request', { id });
|
return invoke('duplicate_request', { id });
|
||||||
},
|
},
|
||||||
|
onSettled: () => trackEvent('http_request', 'duplicate'),
|
||||||
onSuccess: async (request) => {
|
onSuccess: async (request) => {
|
||||||
queryClient.setQueryData<HttpRequest[]>(
|
queryClient.setQueryData<HttpRequest[]>(
|
||||||
requestsQueryKey({ workspaceId: request.workspaceId }),
|
requestsQueryKey({ workspaceId: request.workspaceId }),
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import { minPromiseMillis } from '../lib/minPromiseMillis';
|
|||||||
import type { HttpRequest } from '../lib/models';
|
import type { HttpRequest } from '../lib/models';
|
||||||
import { getResponseBodyText } from '../lib/responseBody';
|
import { getResponseBodyText } from '../lib/responseBody';
|
||||||
import { sendEphemeralRequest } from '../lib/sendEphemeralRequest';
|
import { sendEphemeralRequest } from '../lib/sendEphemeralRequest';
|
||||||
import { useDebouncedValue } from './useDebouncedValue';
|
|
||||||
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
|
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
|
||||||
|
import { useDebouncedValue } from './useDebouncedValue';
|
||||||
|
|
||||||
const introspectionRequestBody = JSON.stringify({
|
const introspectionRequestBody = JSON.stringify({
|
||||||
query: getIntrospectionQuery(),
|
query: getIntrospectionQuery(),
|
||||||
@@ -66,7 +66,7 @@ export function useIntrospectGraphQL(baseRequest: HttpRequest) {
|
|||||||
runIntrospection(); // Run immediately
|
runIntrospection(); // Run immediately
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [request.id, request.url, request.method, refetchKey]);
|
}, [request.id, request.url, request.method, refetchKey, activeEnvironmentId]);
|
||||||
|
|
||||||
const refetch = useCallback(() => {
|
const refetch = useCallback(() => {
|
||||||
setRefetchKey((k) => k + 1);
|
setRefetchKey((k) => k + 1);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
import { invoke } from '@tauri-apps/api';
|
import { invoke } from '@tauri-apps/api';
|
||||||
|
import { trackEvent } from '../lib/analytics';
|
||||||
import type { HttpResponse } from '../lib/models';
|
import type { HttpResponse } from '../lib/models';
|
||||||
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
|
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
|
||||||
|
|
||||||
@@ -7,5 +8,6 @@ export function useSendAnyRequest() {
|
|||||||
const environmentId = useActiveEnvironmentId();
|
const environmentId = useActiveEnvironmentId();
|
||||||
return useMutation<HttpResponse, string, string | null>({
|
return useMutation<HttpResponse, string, string | null>({
|
||||||
mutationFn: (id) => invoke('send_request', { requestId: id, environmentId }),
|
mutationFn: (id) => invoke('send_request', { requestId: id, environmentId }),
|
||||||
|
onSettled: () => trackEvent('http_request', 'send'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,20 +7,28 @@ import { requestsQueryKey } from './useRequests';
|
|||||||
export function useUpdateAnyRequest() {
|
export function useUpdateAnyRequest() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<void, unknown, { id: string; update: (r: HttpRequest) => HttpRequest }>({
|
return useMutation<
|
||||||
|
void,
|
||||||
|
unknown,
|
||||||
|
{ id: string; update: Partial<HttpRequest> | ((r: HttpRequest) => HttpRequest) }
|
||||||
|
>({
|
||||||
mutationFn: async ({ id, update }) => {
|
mutationFn: async ({ id, update }) => {
|
||||||
const request = await getRequest(id);
|
const request = await getRequest(id);
|
||||||
if (request === null) {
|
if (request === null) {
|
||||||
throw new Error("Can't update a null request");
|
throw new Error("Can't update a null request");
|
||||||
}
|
}
|
||||||
|
|
||||||
await invoke('update_request', { request: update(request) });
|
const patchedRequest =
|
||||||
|
typeof update === 'function' ? update(request) : { ...request, ...update };
|
||||||
|
await invoke('update_request', { request: patchedRequest });
|
||||||
},
|
},
|
||||||
onMutate: async ({ id, update }) => {
|
onMutate: async ({ id, update }) => {
|
||||||
const request = await getRequest(id);
|
const request = await getRequest(id);
|
||||||
if (request === null) return;
|
if (request === null) return;
|
||||||
|
const patchedRequest =
|
||||||
|
typeof update === 'function' ? update(request) : { ...request, ...update };
|
||||||
queryClient.setQueryData<HttpRequest[]>(requestsQueryKey(request), (requests) =>
|
queryClient.setQueryData<HttpRequest[]>(requestsQueryKey(request), (requests) =>
|
||||||
(requests ?? []).map((r) => (r.id === request.id ? update(r) : r)),
|
(requests ?? []).map((r) => (r.id === patchedRequest.id ? patchedRequest : r)),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,29 +1,10 @@
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
import { invoke } from '@tauri-apps/api';
|
|
||||||
import type { HttpRequest } from '../lib/models';
|
import type { HttpRequest } from '../lib/models';
|
||||||
import { getRequest } from '../lib/store';
|
import { useUpdateAnyRequest } from './useUpdateAnyRequest';
|
||||||
import { requestsQueryKey } from './useRequests';
|
|
||||||
|
|
||||||
export function useUpdateRequest(id: string | null) {
|
export function useUpdateRequest(id: string | null) {
|
||||||
const queryClient = useQueryClient();
|
const updateAnyRequest = useUpdateAnyRequest();
|
||||||
return useMutation<void, unknown, Partial<HttpRequest> | ((r: HttpRequest) => HttpRequest)>({
|
return useMutation<void, unknown, Partial<HttpRequest> | ((r: HttpRequest) => HttpRequest)>({
|
||||||
mutationFn: async (v) => {
|
mutationFn: async (update) => updateAnyRequest.mutateAsync({ id: id ?? 'n/a', update }),
|
||||||
const request = await getRequest(id);
|
|
||||||
if (request == null) {
|
|
||||||
throw new Error("Can't update a null request");
|
|
||||||
}
|
|
||||||
|
|
||||||
const newRequest = typeof v === 'function' ? v(request) : { ...request, ...v };
|
|
||||||
await invoke('update_request', { request: newRequest });
|
|
||||||
},
|
|
||||||
onMutate: async (v) => {
|
|
||||||
const request = await getRequest(id);
|
|
||||||
if (request === null) return;
|
|
||||||
|
|
||||||
const patchedRequest = typeof v === 'function' ? v(request) : { ...request, ...v };
|
|
||||||
queryClient.setQueryData<HttpRequest[]>(requestsQueryKey(request), (requests) =>
|
|
||||||
(requests ?? []).map((r) => (r.id === patchedRequest.id ? patchedRequest : r)),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
58
src-web/lib/analytics.ts
Normal file
58
src-web/lib/analytics.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { getVersion } from '@tauri-apps/api/app';
|
||||||
|
import type { Environment, Folder, HttpRequest, HttpResponse, KeyValue, Workspace } from './models';
|
||||||
|
|
||||||
|
const appVersion = await getVersion();
|
||||||
|
|
||||||
|
export function trackEvent(
|
||||||
|
resource:
|
||||||
|
| Workspace['model']
|
||||||
|
| Environment['model']
|
||||||
|
| Folder['model']
|
||||||
|
| HttpRequest['model']
|
||||||
|
| HttpResponse['model']
|
||||||
|
| KeyValue['model'],
|
||||||
|
event: 'create' | 'update' | 'delete' | 'delete_many' | 'send' | 'duplicate',
|
||||||
|
attributes: Record<string, string | number> = {},
|
||||||
|
) {
|
||||||
|
send('/e', [
|
||||||
|
{ name: 'e', value: `${resource}.${event}` },
|
||||||
|
{ name: 'a', value: JSON.stringify({ ...attributes, version: appVersion }) },
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function trackPage(pathname: string) {
|
||||||
|
if (pathname === sessionStorage.lastPathName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionStorage.lastPathName = pathname;
|
||||||
|
send('/p', [
|
||||||
|
{
|
||||||
|
name: 'h',
|
||||||
|
value: 'desktop.yaak.app',
|
||||||
|
},
|
||||||
|
{ name: 'p', value: pathname },
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function send(path: string, params: { name: string; value: string | number }[]) {
|
||||||
|
if (localStorage.disableAnalytics === 'true') {
|
||||||
|
console.log('Analytics disabled', path, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
params.push({ name: 'id', value: 'site_zOK0d7jeBy2TLxFCnZ' });
|
||||||
|
params.push({
|
||||||
|
name: 'tz',
|
||||||
|
value: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||||
|
});
|
||||||
|
params.push({ name: 'xy', value: screensize() });
|
||||||
|
const qs = params.map((v) => `${v.name}=${encodeURIComponent(v.value)}`).join('&');
|
||||||
|
const url = `https://t.yaak.app/t${path}?${qs}`;
|
||||||
|
fetch(url, { mode: 'no-cors' }).catch((err) => console.log('Error:', err));
|
||||||
|
}
|
||||||
|
|
||||||
|
function screensize() {
|
||||||
|
const w = window.screen.width;
|
||||||
|
const h = window.screen.height;
|
||||||
|
return `${Math.round(w / 100) * 100}x${Math.round(h / 100) * 100}`;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user