Show gRPC requests in sidebar

This commit is contained in:
Gregory Schier
2024-02-03 13:08:24 -08:00
parent 04f31cd4a7
commit 23431b40e9
33 changed files with 389 additions and 149 deletions

View File

@@ -1,8 +1,8 @@
use log::{debug, warn};
use serde::{Deserialize, Serialize};
use serde_json::json;
use sqlx::{Pool, Sqlite};
use sqlx::types::JsonValue;
use sqlx::{Pool, Sqlite};
use tauri::{AppHandle, Manager};
use crate::{is_dev, models};
@@ -15,6 +15,7 @@ pub enum AnalyticsResource {
Dialog,
Environment,
Folder,
GrpcRequest,
HttpRequest,
HttpResponse,
KeyValue,
@@ -30,6 +31,7 @@ impl AnalyticsResource {
"CookieJar" => Some(AnalyticsResource::CookieJar),
"Environment" => Some(AnalyticsResource::Environment),
"Folder" => Some(AnalyticsResource::Folder),
"GrpcRequest" => Some(AnalyticsResource::GrpcRequest),
"HttpRequest" => Some(AnalyticsResource::HttpRequest),
"HttpResponse" => Some(AnalyticsResource::HttpResponse),
"KeyValue" => Some(AnalyticsResource::KeyValue),
@@ -89,6 +91,7 @@ fn resource_name(resource: AnalyticsResource) -> &'static str {
AnalyticsResource::Dialog => "dialog",
AnalyticsResource::Environment => "environment",
AnalyticsResource::Folder => "folder",
AnalyticsResource::GrpcRequest => "grpc_request",
AnalyticsResource::HttpRequest => "http_request",
AnalyticsResource::HttpResponse => "http_response",
AnalyticsResource::KeyValue => "key_value",

View File

@@ -44,13 +44,14 @@ use crate::http::send_http_request;
use crate::models::{
cancel_pending_responses, create_response, delete_all_responses, delete_cookie_jar,
delete_environment, delete_folder, delete_request, delete_response, delete_workspace,
duplicate_request, find_cookie_jars, find_environments, find_folders, find_requests,
find_responses, find_workspaces, generate_id, get_cookie_jar, get_environment, get_folder,
get_http_request, get_key_value_raw, get_or_create_settings, get_response, get_workspace,
get_workspace_export_resources, set_key_value_raw, update_response_if_id, update_settings,
upsert_cookie_jar, upsert_environment, upsert_folder, upsert_request, upsert_workspace,
CookieJar, Environment, EnvironmentVariable, Folder, HttpRequest, HttpResponse, KeyValue,
Settings, Workspace,
duplicate_grpc_request, duplicate_http_request, list_cookie_jars, list_folders, list_requests,
list_responses, list_workspaces, generate_id, get_cookie_jar, get_environment, get_folder,
get_grpc_request, get_http_request, get_key_value_raw, get_or_create_settings, get_response,
get_workspace, get_workspace_export_resources, list_environments, list_grpc_requests,
set_key_value_raw, update_response_if_id, update_settings, upsert_cookie_jar,
upsert_environment, upsert_folder, upsert_grpc_request, upsert_http_request, upsert_workspace,
CookieJar, Environment, EnvironmentVariable, Folder, GrpcRequest, HttpRequest, HttpResponse,
KeyValue, Settings, Workspace,
};
use crate::plugin::{ImportResources, ImportResult};
use crate::updates::{update_mode_from_str, UpdateMode, YaakUpdater};
@@ -467,7 +468,7 @@ async fn cmd_import_data(
}
for r in r.resources.requests {
let x = upsert_request(db, r)
let x = upsert_http_request(db, r)
.await
.expect("Failed to create request");
imported_resources.requests.push(x.clone());
@@ -747,7 +748,46 @@ async fn cmd_create_environment(
}
#[tauri::command]
async fn cmd_create_request(
async fn cmd_create_grpc_request(
workspace_id: &str,
name: &str,
sort_priority: f64,
folder_id: Option<&str>,
window: Window<Wry>,
db_state: State<'_, Mutex<Pool<Sqlite>>>,
) -> Result<GrpcRequest, String> {
let db = &*db_state.lock().await;
let created = upsert_grpc_request(
db,
&GrpcRequest {
workspace_id: workspace_id.to_string(),
name: name.to_string(),
folder_id: folder_id.map(|s| s.to_string()),
sort_priority,
..Default::default()
},
)
.await
.expect("Failed to create grpc request");
emit_and_return(&window, "created_model", created)
}
#[tauri::command]
async fn cmd_duplicate_grpc_request(
id: &str,
window: Window<Wry>,
db_state: State<'_, Mutex<Pool<Sqlite>>>,
) -> Result<GrpcRequest, String> {
let db = &*db_state.lock().await;
let request = duplicate_grpc_request(db, id)
.await
.expect("Failed to duplicate grpc request");
emit_and_return(&window, "updated_model", request)
}
#[tauri::command]
async fn cmd_create_http_request(
workspace_id: &str,
name: &str,
sort_priority: f64,
@@ -756,7 +796,7 @@ async fn cmd_create_request(
db_state: State<'_, Mutex<Pool<Sqlite>>>,
) -> Result<HttpRequest, String> {
let db = &*db_state.lock().await;
let created_request = upsert_request(
let created_request = upsert_http_request(
db,
HttpRequest {
workspace_id: workspace_id.to_string(),
@@ -768,21 +808,21 @@ async fn cmd_create_request(
},
)
.await
.expect("Failed to create request");
.expect("Failed to create http request");
emit_and_return(&window, "created_model", created_request)
}
#[tauri::command]
async fn cmd_duplicate_request(
async fn cmd_duplicate_http_request(
id: &str,
window: Window<Wry>,
db_state: State<'_, Mutex<Pool<Sqlite>>>,
) -> Result<HttpRequest, String> {
let db = &*db_state.lock().await;
let request = duplicate_request(db, id)
let request = duplicate_http_request(db, id)
.await
.expect("Failed to duplicate request");
.expect("Failed to duplicate http request");
emit_and_return(&window, "updated_model", request)
}
@@ -815,20 +855,46 @@ async fn cmd_update_environment(
}
#[tauri::command]
async fn cmd_update_request(
async fn cmd_update_grpc_request(
request: GrpcRequest,
window: Window<Wry>,
db_state: State<'_, Mutex<Pool<Sqlite>>>,
) -> Result<GrpcRequest, String> {
let db = &*db_state.lock().await;
let updated_request = upsert_grpc_request(db, &request)
.await
.expect("Failed to update grpc request");
emit_and_return(&window, "updated_model", updated_request)
}
#[tauri::command]
async fn cmd_update_http_request(
request: HttpRequest,
window: Window<Wry>,
db_state: State<'_, Mutex<Pool<Sqlite>>>,
) -> Result<HttpRequest, String> {
let db = &*db_state.lock().await;
let updated_request = upsert_request(db, request)
let updated_request = upsert_http_request(db, request)
.await
.expect("Failed to update request");
emit_and_return(&window, "updated_model", updated_request)
}
#[tauri::command]
async fn cmd_delete_request(
async fn cmd_delete_grpc_request(
window: Window<Wry>,
db_state: State<'_, Mutex<Pool<Sqlite>>>,
request_id: &str,
) -> Result<HttpRequest, String> {
let db = &*db_state.lock().await;
let req = delete_request(db, request_id)
.await
.expect("Failed to delete request");
emit_and_return(&window, "deleted_model", req)
}
#[tauri::command]
async fn cmd_delete_http_request(
window: Window<Wry>,
db_state: State<'_, Mutex<Pool<Sqlite>>>,
request_id: &str,
@@ -846,7 +912,7 @@ async fn cmd_list_folders(
db_state: State<'_, Mutex<Pool<Sqlite>>>,
) -> Result<Vec<Folder>, String> {
let db = &*db_state.lock().await;
find_folders(db, workspace_id)
list_folders(db, workspace_id)
.await
.map_err(|e| e.to_string())
}
@@ -917,12 +983,25 @@ async fn cmd_delete_environment(
}
#[tauri::command]
async fn cmd_list_requests(
async fn cmd_list_grpc_requests(
workspace_id: &str,
db_state: State<'_, Mutex<Pool<Sqlite>>>,
) -> Result<Vec<GrpcRequest>, String> {
let db = &*db_state.lock().await;
let requests = list_grpc_requests(db, workspace_id)
.await
.expect("Failed to find grpc requests");
// .map_err(|e| e.to_string())
Ok(requests)
}
#[tauri::command]
async fn cmd_list_http_requests(
workspace_id: &str,
db_state: State<'_, Mutex<Pool<Sqlite>>>,
) -> Result<Vec<HttpRequest>, String> {
let db = &*db_state.lock().await;
let requests = find_requests(db, workspace_id)
let requests = list_requests(db, workspace_id)
.await
.expect("Failed to find requests");
// .map_err(|e| e.to_string())
@@ -935,7 +1014,7 @@ async fn cmd_list_environments(
db_state: State<'_, Mutex<Pool<Sqlite>>>,
) -> Result<Vec<Environment>, String> {
let db = &*db_state.lock().await;
let environments = find_environments(db, workspace_id)
let environments = list_environments(db, workspace_id)
.await
.expect("Failed to find environments");
@@ -972,7 +1051,16 @@ async fn cmd_get_folder(
}
#[tauri::command]
async fn cmd_get_request(
async fn cmd_get_grpc_request(
id: &str,
db_state: State<'_, Mutex<Pool<Sqlite>>>,
) -> Result<GrpcRequest, String> {
let db = &*db_state.lock().await;
get_grpc_request(db, id).await.map_err(|e| e.to_string())
}
#[tauri::command]
async fn cmd_get_http_request(
id: &str,
db_state: State<'_, Mutex<Pool<Sqlite>>>,
) -> Result<HttpRequest, String> {
@@ -995,7 +1083,7 @@ async fn cmd_list_cookie_jars(
db_state: State<'_, Mutex<Pool<Sqlite>>>,
) -> Result<Vec<CookieJar>, String> {
let db = &*db_state.lock().await;
let cookie_jars = find_cookie_jars(db, workspace_id)
let cookie_jars = list_cookie_jars(db, workspace_id)
.await
.expect("Failed to find cookie jars");
@@ -1041,7 +1129,7 @@ async fn cmd_list_responses(
db_state: State<'_, Mutex<Pool<Sqlite>>>,
) -> Result<Vec<HttpResponse>, String> {
let db = &*db_state.lock().await;
find_responses(db, request_id, limit)
list_responses(db, request_id, limit)
.await
.map_err(|e| e.to_string())
}
@@ -1075,7 +1163,7 @@ async fn cmd_list_workspaces(
db_state: State<'_, Mutex<Pool<Sqlite>>>,
) -> Result<Vec<Workspace>, String> {
let db = &*db_state.lock().await;
let workspaces = find_workspaces(db)
let workspaces = list_workspaces(db)
.await
.expect("Failed to find workspaces");
if workspaces.is_empty() {
@@ -1203,23 +1291,26 @@ fn main() {
cmd_create_cookie_jar,
cmd_create_environment,
cmd_create_folder,
cmd_create_request,
cmd_create_grpc_request,
cmd_create_http_request,
cmd_create_workspace,
cmd_delete_all_responses,
cmd_delete_cookie_jar,
cmd_delete_environment,
cmd_delete_folder,
cmd_delete_request,
cmd_delete_grpc_request,
cmd_delete_http_request,
cmd_delete_response,
cmd_delete_workspace,
cmd_duplicate_request,
cmd_duplicate_http_request,
cmd_export_data,
cmd_filter_response,
cmd_get_cookie_jar,
cmd_get_environment,
cmd_get_folder,
cmd_get_key_value,
cmd_get_request,
cmd_get_http_request,
cmd_get_grpc_request,
cmd_get_settings,
cmd_get_workspace,
cmd_grpc_call_unary,
@@ -1231,7 +1322,8 @@ fn main() {
cmd_list_cookie_jars,
cmd_list_environments,
cmd_list_folders,
cmd_list_requests,
cmd_list_http_requests,
cmd_list_grpc_requests,
cmd_list_responses,
cmd_list_workspaces,
cmd_new_window,
@@ -1243,7 +1335,8 @@ fn main() {
cmd_update_cookie_jar,
cmd_update_environment,
cmd_update_folder,
cmd_update_request,
cmd_update_grpc_request,
cmd_update_http_request,
cmd_update_settings,
cmd_update_workspace,
])

View File

@@ -355,7 +355,7 @@ pub async fn get_key_value_raw(db: &Pool<Sqlite>, namespace: &str, key: &str) ->
.ok()
}
pub async fn find_workspaces(db: &Pool<Sqlite>) -> Result<Vec<Workspace>, sqlx::Error> {
pub async fn list_workspaces(db: &Pool<Sqlite>) -> Result<Vec<Workspace>, sqlx::Error> {
sqlx::query_as!(
Workspace,
r#"
@@ -398,7 +398,7 @@ pub async fn delete_workspace(db: &Pool<Sqlite>, id: &str) -> Result<Workspace,
.execute(db)
.await;
for r in find_responses_by_workspace_id(db, id).await? {
for r in list_responses_by_workspace_id(db, id).await? {
delete_response(db, &r.id).await?;
}
@@ -420,7 +420,7 @@ pub async fn get_cookie_jar(db: &Pool<Sqlite>, id: &str) -> Result<CookieJar, sq
.await
}
pub async fn find_cookie_jars(
pub async fn list_cookie_jars(
db: &Pool<Sqlite>,
workspace_id: &str,
) -> Result<Vec<CookieJar>, sqlx::Error> {
@@ -454,6 +454,15 @@ pub async fn delete_cookie_jar(db: &Pool<Sqlite>, id: &str) -> Result<CookieJar,
Ok(cookie_jar)
}
pub async fn duplicate_grpc_request(
db: &Pool<Sqlite>,
id: &str,
) -> Result<GrpcRequest, sqlx::Error> {
let mut request = get_grpc_request(db, id).await?.clone();
request.id = "".to_string();
upsert_grpc_request(db, &request).await
}
pub async fn upsert_grpc_request(
db: &Pool<Sqlite>,
request: &GrpcRequest,
@@ -687,7 +696,7 @@ pub async fn upsert_cookie_jar(
get_cookie_jar(db, &id).await
}
pub async fn find_environments(
pub async fn list_environments(
db: &Pool<Sqlite>,
workspace_id: &str,
) -> Result<Vec<Environment>, sqlx::Error> {
@@ -832,7 +841,7 @@ pub async fn get_folder(db: &Pool<Sqlite>, id: &str) -> Result<Folder, sqlx::Err
.await
}
pub async fn find_folders(
pub async fn list_folders(
db: &Pool<Sqlite>,
workspace_id: &str,
) -> Result<Vec<Folder>, sqlx::Error> {
@@ -896,13 +905,19 @@ pub async fn upsert_folder(db: &Pool<Sqlite>, r: Folder) -> Result<Folder, sqlx:
get_folder(db, &id).await
}
pub async fn duplicate_request(db: &Pool<Sqlite>, id: &str) -> Result<HttpRequest, sqlx::Error> {
pub async fn duplicate_http_request(
db: &Pool<Sqlite>,
id: &str,
) -> Result<HttpRequest, sqlx::Error> {
let mut request = get_http_request(db, id).await?.clone();
request.id = "".to_string();
upsert_request(db, request).await
upsert_http_request(db, request).await
}
pub async fn upsert_request(db: &Pool<Sqlite>, r: HttpRequest) -> Result<HttpRequest, sqlx::Error> {
pub async fn upsert_http_request(
db: &Pool<Sqlite>,
r: HttpRequest,
) -> Result<HttpRequest, sqlx::Error> {
let id = match r.id.as_str() {
"" => generate_id(Some("rq")),
_ => r.id.to_string(),
@@ -952,7 +967,7 @@ pub async fn upsert_request(db: &Pool<Sqlite>, r: HttpRequest) -> Result<HttpReq
get_http_request(db, &id).await
}
pub async fn find_requests(
pub async fn list_requests(
db: &Pool<Sqlite>,
workspace_id: &str,
) -> Result<Vec<HttpRequest>, sqlx::Error> {
@@ -1171,7 +1186,7 @@ pub async fn get_response(db: &Pool<Sqlite>, id: &str) -> Result<HttpResponse, s
.await
}
pub async fn find_responses(
pub async fn list_responses(
db: &Pool<Sqlite>,
request_id: &str,
limit: Option<i64>,
@@ -1197,7 +1212,7 @@ pub async fn find_responses(
.await
}
pub async fn find_responses_by_workspace_id(
pub async fn list_responses_by_workspace_id(
db: &Pool<Sqlite>,
workspace_id: &str,
) -> Result<Vec<HttpResponse>, sqlx::Error> {
@@ -1243,7 +1258,7 @@ pub async fn delete_response(db: &Pool<Sqlite>, id: &str) -> Result<HttpResponse
}
pub async fn delete_all_responses(db: &Pool<Sqlite>, request_id: &str) -> Result<(), sqlx::Error> {
for r in find_responses(db, request_id, None).await? {
for r in list_responses(db, request_id, None).await? {
delete_response(db, &r.id).await?;
}
Ok(())
@@ -1289,13 +1304,13 @@ pub async fn get_workspace_export_resources(
timestamp: chrono::Utc::now().naive_utc(),
resources: WorkspaceExportResources {
workspaces: vec![workspace],
environments: find_environments(db, workspace_id)
environments: list_environments(db, workspace_id)
.await
.expect("Failed to get environments"),
folders: find_folders(db, workspace_id)
folders: list_folders(db, workspace_id)
.await
.expect("Failed to get folders"),
requests: find_requests(db, workspace_id)
requests: list_requests(db, workspace_id)
.await
.expect("Failed to get requests"),
},

View File

@@ -1,7 +1,7 @@
import { createBrowserRouter, Navigate, Outlet, RouterProvider } from 'react-router-dom';
import { routePaths, useAppRoutes } from '../hooks/useAppRoutes';
import { useRecentRequests } from '../hooks/useRecentRequests';
import { useRequests } from '../hooks/useRequests';
import { useHttpRequests } from '../hooks/useHttpRequests';
import { GlobalHooks } from './GlobalHooks';
import Workspace from './Workspace';
import Workspaces from './Workspaces';
@@ -49,7 +49,7 @@ export function AppRouter() {
function WorkspaceOrRedirect() {
const recentRequests = useRecentRequests();
const activeEnvironmentId = useActiveEnvironmentId();
const requests = useRequests();
const requests = useHttpRequests();
const request = requests.find((r) => r.id === recentRequests[0]);
const routes = useAppRoutes();

View File

@@ -1,4 +1,4 @@
import { useUpdateRequest } from '../hooks/useUpdateRequest';
import { useUpdateHttpRequest } from '../hooks/useUpdateHttpRequest';
import type { HttpRequest } from '../lib/models';
import { Input } from './core/Input';
import { VStack } from './core/Stacks';
@@ -9,7 +9,7 @@ interface Props {
}
export function BasicAuth({ requestId, authentication }: Props) {
const updateRequest = useUpdateRequest(requestId);
const updateRequest = useUpdateHttpRequest(requestId);
return (
<VStack className="my-2" space={2}>

View File

@@ -1,4 +1,4 @@
import { useUpdateRequest } from '../hooks/useUpdateRequest';
import { useUpdateHttpRequest } from '../hooks/useUpdateHttpRequest';
import type { HttpRequest } from '../lib/models';
import { Input } from './core/Input';
import { VStack } from './core/Stacks';
@@ -9,7 +9,7 @@ interface Props {
}
export function BearerAuth({ requestId, authentication }: Props) {
const updateRequest = useUpdateRequest(requestId);
const updateRequest = useUpdateHttpRequest(requestId);
return (
<VStack className="my-2" space={2}>

View File

@@ -8,7 +8,7 @@ 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 { httpRequestsQueryKey } from '../hooks/useHttpRequests';
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
import { responsesQueryKey } from '../hooks/useResponses';
import { settingsQueryKey } from '../hooks/useSettings';
@@ -47,7 +47,7 @@ export function GlobalHooks() {
const queryKey =
payload.model === 'http_request'
? requestsQueryKey(payload)
? httpRequestsQueryKey(payload)
: payload.model === 'http_response'
? responsesQueryKey(payload)
: payload.model === 'workspace'
@@ -76,7 +76,7 @@ export function GlobalHooks() {
const queryKey =
payload.model === 'http_request'
? requestsQueryKey(payload)
? httpRequestsQueryKey(payload)
: payload.model === 'http_response'
? responsesQueryKey(payload)
: payload.model === 'workspace'
@@ -115,7 +115,7 @@ export function GlobalHooks() {
if (payload.model === 'workspace') {
queryClient.setQueryData<Workspace[]>(workspacesQueryKey(), removeById(payload));
} else if (payload.model === 'http_request') {
queryClient.setQueryData<HttpRequest[]>(requestsQueryKey(payload), removeById(payload));
queryClient.setQueryData<HttpRequest[]>(httpRequestsQueryKey(payload), removeById(payload));
} else if (payload.model === 'http_response') {
queryClient.setQueryData<HttpResponse[]>(responsesQueryKey(payload), removeById(payload));
} else if (payload.model === 'key_value') {

View File

@@ -208,7 +208,7 @@ export function GrpcConnectionLayout({ style }: Props) {
className="border border-highlight"
size="sm"
title="to-do"
hotkeyAction={grpc.isStreaming ? undefined : 'request.send'}
hotkeyAction={grpc.isStreaming ? undefined : 'http_request.send'}
onClick={grpc.isStreaming ? handleCancel : handleConnect}
icon={
grpc.isStreaming
@@ -227,7 +227,7 @@ export function GrpcConnectionLayout({ style }: Props) {
className="border border-highlight"
size="sm"
title="to-do"
hotkeyAction="request.send"
hotkeyAction="grpc_request.send"
onClick={() => grpc.send.mutateAsync({ message: message.value ?? '' })}
icon="sendHorizontal"
/>
@@ -331,7 +331,7 @@ export function GrpcConnectionLayout({ style }: Props) {
forceUpdateKey={resp}
/>
) : (
<HotKeyList hotkeys={['grpc.send', 'sidebar.toggle', 'urlBar.focus']} />
<HotKeyList hotkeys={['grpc_request.send', 'sidebar.toggle', 'urlBar.focus']} />
)}
</div>
)

View File

@@ -7,7 +7,7 @@ import { useActiveWorkspaceId } from '../hooks/useActiveWorkspaceId';
import { useAppRoutes } from '../hooks/useAppRoutes';
import { useHotKey } from '../hooks/useHotKey';
import { useRecentRequests } from '../hooks/useRecentRequests';
import { useRequests } from '../hooks/useRequests';
import { useHttpRequests } from '../hooks/useHttpRequests';
import { fallbackRequestName } from '../lib/fallbackRequestName';
import type { ButtonProps } from './core/Button';
import { Button } from './core/Button';
@@ -19,7 +19,7 @@ export function RecentRequestsDropdown({ className }: Pick<ButtonProps, 'classNa
const activeRequest = useActiveRequest();
const activeWorkspaceId = useActiveWorkspaceId();
const activeEnvironmentId = useActiveEnvironmentId();
const requests = useRequests();
const requests = useHttpRequests();
const routes = useAppRoutes();
const allRecentRequestIds = useRecentRequests();
const recentRequestIds = useMemo(() => allRecentRequestIds.slice(1), [allRecentRequestIds]);

View File

@@ -6,7 +6,7 @@ import { useActiveRequest } from '../hooks/useActiveRequest';
import { useIsResponseLoading } from '../hooks/useIsResponseLoading';
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
import { useSendRequest } from '../hooks/useSendRequest';
import { useUpdateRequest } from '../hooks/useUpdateRequest';
import { useUpdateHttpRequest } from '../hooks/useUpdateHttpRequest';
import { tryFormatJson } from '../lib/formatters';
import type { HttpHeader, HttpRequest, HttpUrlParameter } from '../lib/models';
import {
@@ -43,9 +43,9 @@ interface Props {
const useActiveTab = createGlobalState<string>('body');
export const RequestPane = memo(function RequestPane({ style, fullHeight, className }: Props) {
const activeRequest = useActiveRequest();
const activeRequest = useActiveRequest('http_request');
const activeRequestId = activeRequest?.id ?? null;
const updateRequest = useUpdateRequest(activeRequestId);
const updateRequest = useUpdateHttpRequest(activeRequestId);
const [activeTab, setActiveTab] = useActiveTab();
const [forceUpdateHeaderEditorKey, setForceUpdateHeaderEditorKey] = useState<number>(0);
const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest?.id ?? null);

View File

@@ -110,7 +110,7 @@ export const ResponsePane = memo(function ResponsePane({ style, className }: Pro
<>
<span />
<HotKeyList
hotkeys={['request.send', 'request.create', 'sidebar.toggle', 'urlBar.focus']}
hotkeys={['http_request.send', 'http_request.create', 'sidebar.toggle', 'urlBar.focus']}
/>
</>
)}

View File

@@ -11,26 +11,27 @@ import { useActiveRequestId } from '../hooks/useActiveRequestId';
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
import { useAppRoutes } from '../hooks/useAppRoutes';
import { useCreateFolder } from '../hooks/useCreateFolder';
import { useCreateRequest } from '../hooks/useCreateRequest';
import { useCreateHttpRequest } from '../hooks/useCreateHttpRequest';
import { useDeleteAnyRequest } from '../hooks/useDeleteAnyRequest';
import { useDeleteFolder } from '../hooks/useDeleteFolder';
import { useDeleteRequest } from '../hooks/useDeleteRequest';
import { useDuplicateRequest } from '../hooks/useDuplicateRequest';
import { useFolders } from '../hooks/useFolders';
import { useGrpcRequests } from '../hooks/useGrpcRequests';
import { useHotKey } from '../hooks/useHotKey';
import { useKeyValue } from '../hooks/useKeyValue';
import { useLatestResponse } from '../hooks/useLatestResponse';
import { usePrompt } from '../hooks/usePrompt';
import { useRequests } from '../hooks/useRequests';
import { useHttpRequests } from '../hooks/useHttpRequests';
import { useSendManyRequests } from '../hooks/useSendFolder';
import { useSendRequest } from '../hooks/useSendRequest';
import { useSidebarHidden } from '../hooks/useSidebarHidden';
import { useUpdateAnyFolder } from '../hooks/useUpdateAnyFolder';
import { useUpdateAnyRequest } from '../hooks/useUpdateAnyRequest';
import { useUpdateRequest } from '../hooks/useUpdateRequest';
import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest';
import { useUpdateHttpRequest } from '../hooks/useUpdateHttpRequest';
import { fallbackRequestName } from '../lib/fallbackRequestName';
import { NAMESPACE_NO_SYNC } from '../lib/keyValueStore';
import type { Folder, HttpRequest, Workspace } from '../lib/models';
import type { Folder, GrpcRequest, HttpRequest, Workspace } from '../lib/models';
import { isResponseLoading } from '../lib/models';
import { ContextMenu } from './core/Dropdown';
import { Icon } from './core/Icon';
@@ -48,7 +49,7 @@ enum ItemTypes {
}
interface TreeNode {
item: Workspace | Folder | HttpRequest;
item: Workspace | Folder | HttpRequest | GrpcRequest;
children: TreeNode[];
depth: number;
}
@@ -58,7 +59,8 @@ export function Sidebar({ className }: Props) {
const sidebarRef = useRef<HTMLLIElement>(null);
const activeRequestId = useActiveRequestId();
const activeEnvironmentId = useActiveEnvironmentId();
const requests = useRequests();
const httpRequests = useHttpRequests();
const grpcRequests = useGrpcRequests();
const folders = useFolders();
const deleteAnyRequest = useDeleteAnyRequest();
const activeWorkspace = useActiveWorkspace();
@@ -67,7 +69,7 @@ export function Sidebar({ className }: Props) {
const [hasFocus, setHasFocus] = useState<boolean>(false);
const [selectedId, setSelectedId] = useState<string | null>(null);
const [selectedTree, setSelectedTree] = useState<TreeNode | null>(null);
const updateAnyRequest = useUpdateAnyRequest();
const updateAnyRequest = useUpdateAnyHttpRequest();
const updateAnyFolder = useUpdateAnyFolder();
const [draggingId, setDraggingId] = useState<string | null>(null);
const [hoveredTree, setHoveredTree] = useState<TreeNode | null>(null);
@@ -78,7 +80,7 @@ export function Sidebar({ className }: Props) {
namespace: NAMESPACE_NO_SYNC,
});
useHotKey('request.duplicate', () => {
useHotKey('http_request.duplicate', () => {
duplicateRequest.mutate();
});
@@ -110,7 +112,7 @@ export function Sidebar({ className }: Props) {
// Put requests and folders into a tree structure
const next = (node: TreeNode): TreeNode => {
const childItems = [...requests, ...folders].filter((f) =>
const childItems = [...httpRequests, ...grpcRequests, ...folders].filter((f) =>
node.item.model === 'workspace' ? f.folderId == null : f.folderId === node.item.id,
);
@@ -119,7 +121,7 @@ export function Sidebar({ className }: Props) {
for (const item of childItems) {
treeParentMap[item.id] = node;
node.children.push(next({ item, children: [], depth }));
if (item.model === 'http_request') {
if (item.model !== 'folder') {
selectableRequests.push({ id: item.id, index: selectableRequestIndex++, tree: node });
}
}
@@ -129,7 +131,7 @@ export function Sidebar({ className }: Props) {
const tree = next({ item: activeWorkspace, children: [], depth: 0 });
return { tree, treeParentMap, selectableRequests };
}, [activeWorkspace, requests, folders]);
}, [activeWorkspace, httpRequests, grpcRequests, folders]);
const focusActiveRequest = useCallback(
(
@@ -160,7 +162,7 @@ export function Sidebar({ className }: Props) {
);
const handleSelect = useCallback(
(id: string) => {
async (id: string) => {
const tree = treeParentMap[id ?? 'n/a'] ?? null;
const children = tree?.children ?? [];
const node = children.find((m) => m.item.id === id) ?? null;
@@ -171,7 +173,7 @@ export function Sidebar({ className }: Props) {
const { item } = node;
if (item.model === 'folder') {
collapsed.set((c) => ({ ...c, [item.id]: !c[item.id] }));
await collapsed.set((c) => ({ ...c, [item.id]: !c[item.id] }));
} else {
routes.navigate('request', {
requestId: id,
@@ -339,6 +341,10 @@ export function Sidebar({ className }: Props) {
if (child.item.model === 'folder') {
const updateFolder = (f: Folder) => ({ ...f, sortPriority, folderId });
return updateAnyFolder.mutateAsync({ id: child.item.id, update: updateFolder });
} else if (child.item.model === 'grpc_request') {
// TODO
// const updateRequest = (r: HttpRequest) => ({ ...r, sortPriority, folderId });
// return updateAnyRequest.mutateAsync({ id: child.item.id, update: updateRequest });
} else if (child.item.model === 'http_request') {
const updateRequest = (r: HttpRequest) => ({ ...r, sortPriority, folderId });
return updateAnyRequest.mutateAsync({ id: child.item.id, update: updateRequest });
@@ -350,6 +356,10 @@ export function Sidebar({ className }: Props) {
if (child.item.model === 'folder') {
const updateFolder = (f: Folder) => ({ ...f, sortPriority, folderId });
await updateAnyFolder.mutateAsync({ id: child.item.id, update: updateFolder });
} else if (child.item.model === 'grpc_request') {
// TODO
// const updateRequest = (r: HttpRequest) => ({ ...r, sortPriority, folderId });
// await updateAnyRequest.mutateAsync({ id: child.item.id, update: updateRequest });
} else if (child.item.model === 'http_request') {
const updateRequest = (r: HttpRequest) => ({ ...r, sortPriority, folderId });
await updateAnyRequest.mutateAsync({ id: child.item.id, update: updateRequest });
@@ -454,7 +464,9 @@ function SidebarItems({
itemId={child.item.id}
itemName={child.item.name}
itemFallbackName={
child.item.model === 'http_request' ? fallbackRequestName(child.item) : 'New Folder'
child.item.model === 'http_request' || child.item.model === 'grpc_request'
? fallbackRequestName(child.item)
: 'New Folder'
}
itemModel={child.item.model}
onMove={handleMove}
@@ -524,16 +536,15 @@ const SidebarItem = forwardRef(function SidebarItem(
ref: ForwardedRef<HTMLLIElement>,
) {
const activeRequest = useActiveRequest();
const createRequest = useCreateRequest();
const createRequest = useCreateHttpRequest();
const createFolder = useCreateFolder();
const deleteFolder = useDeleteFolder(itemId);
const deleteRequest = useDeleteRequest(itemId);
const duplicateRequest = useDuplicateRequest({ id: itemId, navigateAfter: true });
const sendRequest = useSendRequest(itemId);
const sendAndDownloadRequest = useSendRequest(itemId, { download: true });
const sendManyRequests = useSendManyRequests();
const latestResponse = useLatestResponse(itemId);
const updateRequest = useUpdateRequest(itemId);
const updateRequest = useUpdateHttpRequest(itemId);
const updateAnyFolder = useUpdateAnyFolder();
const prompt = usePrompt();
const [editing, setEditing] = useState<boolean>(false);
@@ -570,7 +581,7 @@ const SidebarItem = forwardRef(function SidebarItem(
);
const handleStartEditing = useCallback(() => {
if (itemModel !== 'http_request') return;
if (itemModel !== 'http_request' && itemModel !== 'grpc_request') return;
setEditing(true);
}, [setEditing, itemModel]);
@@ -652,7 +663,7 @@ const SidebarItem = forwardRef(function SidebarItem(
{
key: 'sendRequest',
label: 'Send',
hotKeyAction: 'request.send',
hotKeyAction: 'http_request.send',
hotKeyLabelOnly: true, // Already bound in URL bar
leftSlot: <Icon icon="sendHorizontal" />,
onSelect: () => sendRequest.mutate(),
@@ -661,7 +672,7 @@ const SidebarItem = forwardRef(function SidebarItem(
{
key: 'duplicateRequest',
label: 'Duplicate',
hotKeyAction: 'request.duplicate',
hotKeyAction: 'http_request.duplicate',
hotKeyLabelOnly: true, // Would trigger for every request (bad)
leftSlot: <Icon icon="copy" />,
onSelect: () => {

View File

@@ -1,6 +1,7 @@
import { memo } from 'react';
import { useCreateFolder } from '../hooks/useCreateFolder';
import { useCreateRequest } from '../hooks/useCreateRequest';
import { useCreateGrpcRequest } from '../hooks/useCreateGrpcRequest';
import { useCreateHttpRequest } from '../hooks/useCreateHttpRequest';
import { useSidebarHidden } from '../hooks/useSidebarHidden';
import { trackEvent } from '../lib/analytics';
import { Dropdown } from './core/Dropdown';
@@ -8,16 +9,17 @@ import { IconButton } from './core/IconButton';
import { HStack } from './core/Stacks';
export const SidebarActions = memo(function SidebarActions() {
const createRequest = useCreateRequest();
const createHttpRequest = useCreateHttpRequest();
const createGrpcRequest = useCreateGrpcRequest();
const createFolder = useCreateFolder();
const { hidden, toggle } = useSidebarHidden();
return (
<HStack>
<IconButton
onClick={() => {
onClick={async () => {
trackEvent('Sidebar', 'Toggle');
toggle();
await toggle();
}}
className="pointer-events-auto"
size="sm"
@@ -28,14 +30,19 @@ export const SidebarActions = memo(function SidebarActions() {
<Dropdown
items={[
{
key: 'create-request',
label: 'New Request',
hotKeyAction: 'request.create',
onSelect: () => createRequest.mutate({}),
key: 'create-http-request',
label: 'HTTP Request',
hotKeyAction: 'http_request.create',
onSelect: () => createHttpRequest.mutate({}),
},
{
key: 'create-grpc-request',
label: 'GRPC Request',
onSelect: () => createGrpcRequest.mutate({}),
},
{
key: 'create-folder',
label: 'New Folder',
label: 'Folder',
onSelect: () => createFolder.mutate({}),
},
]}

View File

@@ -83,7 +83,7 @@ export const UrlBar = memo(function UrlBar({
className="w-8 mr-0.5 my-0.5"
icon={isLoading ? 'update' : submitIcon}
spin={isLoading}
hotkeyAction="request.send"
hotkeyAction="http_request.send"
/>
)
}

View File

@@ -34,6 +34,7 @@ export default function Workspace() {
const { setWidth, width, resetWidth } = useSidebarWidth();
const { hide, show, hidden } = useSidebarHidden();
const activeRequest = useActiveRequest();
console.log('ACTIVE REQUEST', activeRequest);
const windowSize = useWindowSize();
const [floating, setFloating] = useState<boolean>(false);
@@ -166,7 +167,7 @@ export default function Workspace() {
>
<WorkspaceHeader className="pointer-events-none" />
</HeaderSize>
{activeRequest?.name.includes('gRPC') ? (
{activeRequest?.model === 'grpc_request' ? (
<GrpcConnectionLayout style={body} />
) : (
<HttpRequestLayout style={body} />

View File

@@ -140,7 +140,7 @@ export function SplitLayout({
const activeRequestId = useActiveRequestId();
if (activeRequestId === null) {
return <HotKeyList hotkeys={['request.create', 'sidebar.toggle']} />;
return <HotKeyList hotkeys={['http_request.create', 'sidebar.toggle']} />;
}
return (

View File

@@ -1,9 +1,28 @@
import type { HttpRequest } from '../lib/models';
import { r } from 'vitest/dist/types-94cfe4b4';
import type { GrpcRequest, HttpRequest } from '../lib/models';
import { useActiveRequestId } from './useActiveRequestId';
import { useRequests } from './useRequests';
import { useGrpcRequests } from './useGrpcRequests';
import { useHttpRequests } from './useHttpRequests';
export function useActiveRequest(): HttpRequest | null {
const requestId = useActiveRequestId();
const requests = useRequests();
return requests.find((r) => r.id === requestId) ?? null;
interface TypeMap {
http_request: HttpRequest;
grpc_request: GrpcRequest;
}
export function useActiveRequest<T extends keyof TypeMap>(
model?: T | undefined,
): TypeMap[T] | null {
const requestId = useActiveRequestId();
const httpRequests = useHttpRequests();
const grpcRequests = useGrpcRequests();
if (model === 'http_request') {
return (httpRequests.find((r) => r.id === requestId) ?? null) as TypeMap[T] | null;
} else if (model === 'grpc_request') {
return (grpcRequests.find((r) => r.id === requestId) ?? null) as TypeMap[T] | null;
} else {
return (grpcRequests.find((r) => r.id === requestId) ??
httpRequests.find((r) => r.id === requestId) ??
null) as TypeMap[T] | null;
}
}

View File

@@ -0,0 +1,55 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api';
import { trackEvent } from '../lib/analytics';
import type { GrpcRequest, HttpRequest } from '../lib/models';
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
import { useActiveRequest } from './useActiveRequest';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useAppRoutes } from './useAppRoutes';
import { grpcRequestsQueryKey } from './useGrpcRequests';
import { httpRequestsQueryKey } from './useHttpRequests';
export function useCreateGrpcRequest() {
const workspaceId = useActiveWorkspaceId();
const activeEnvironmentId = useActiveEnvironmentId();
// const activeRequest = useActiveRequest();
const activeRequest = null;
const routes = useAppRoutes();
const queryClient = useQueryClient();
return useMutation<
GrpcRequest,
unknown,
Partial<Pick<GrpcRequest, 'name' | 'sortPriority' | 'folderId'>>
>({
mutationFn: (patch) => {
if (workspaceId === null) {
throw new Error("Cannot create grpc request when there's no active workspace");
}
if (patch.sortPriority === undefined) {
if (activeRequest != null) {
// Place above currently-active request
// patch.sortPriority = activeRequest.sortPriority + 0.0001;
} else {
// Place at the very top
patch.sortPriority = -Date.now();
}
}
// patch.folderId = patch.folderId; // TODO: || activeRequest?.folderId;
return invoke('cmd_create_grpc_request', { workspaceId, name: '', ...patch });
},
onSettled: () => trackEvent('GrpcRequest', 'Create'),
onSuccess: async (request) => {
queryClient.setQueryData<GrpcRequest[]>(
grpcRequestsQueryKey({ workspaceId: request.workspaceId }),
(requests) => [...(requests ?? []), request],
);
// TODO: This should navigate to the new request
routes.navigate('request', {
workspaceId: request.workspaceId,
requestId: request.id,
environmentId: activeEnvironmentId ?? undefined,
});
},
});
}

View File

@@ -6,9 +6,9 @@ import { useActiveEnvironmentId } from './useActiveEnvironmentId';
import { useActiveRequest } from './useActiveRequest';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useAppRoutes } from './useAppRoutes';
import { requestsQueryKey } from './useRequests';
import { httpRequestsQueryKey } from './useHttpRequests';
export function useCreateRequest() {
export function useCreateHttpRequest() {
const workspaceId = useActiveWorkspaceId();
const activeEnvironmentId = useActiveEnvironmentId();
const activeRequest = useActiveRequest();
@@ -34,12 +34,12 @@ export function useCreateRequest() {
}
}
patch.folderId = patch.folderId || activeRequest?.folderId;
return invoke('cmd_create_request', { workspaceId, name: '', ...patch });
return invoke('cmd_create_http_request', { workspaceId, name: '', ...patch });
},
onSettled: () => trackEvent('HttpRequest', 'Create'),
onSuccess: async (request) => {
queryClient.setQueryData<HttpRequest[]>(
requestsQueryKey({ workspaceId: request.workspaceId }),
httpRequestsQueryKey({ workspaceId: request.workspaceId }),
(requests) => [...(requests ?? []), request],
);
routes.navigate('request', {

View File

@@ -6,7 +6,7 @@ import { fallbackRequestName } from '../lib/fallbackRequestName';
import type { HttpRequest } from '../lib/models';
import { getRequest } from '../lib/store';
import { useConfirm } from './useConfirm';
import { requestsQueryKey } from './useRequests';
import { httpRequestsQueryKey } from './useHttpRequests';
import { responsesQueryKey } from './useResponses';
export function useDeleteAnyRequest() {
@@ -27,7 +27,7 @@ export function useDeleteAnyRequest() {
),
});
if (!confirmed) return null;
return invoke('cmd_delete_request', { requestId: id });
return invoke('cmd_delete_http_request', { requestId: id });
},
onSettled: () => trackEvent('HttpRequest', 'Delete'),
onSuccess: async (request) => {
@@ -36,7 +36,7 @@ export function useDeleteAnyRequest() {
const { workspaceId, id: requestId } = request;
queryClient.setQueryData(responsesQueryKey({ requestId }), []); // Responses were deleted
queryClient.setQueryData<HttpRequest[]>(requestsQueryKey({ workspaceId }), (requests) =>
queryClient.setQueryData<HttpRequest[]>(httpRequestsQueryKey({ workspaceId }), (requests) =>
(requests ?? []).filter((r) => r.id !== requestId),
);
},

View File

@@ -6,7 +6,7 @@ import type { Folder } from '../lib/models';
import { getFolder } from '../lib/store';
import { useConfirm } from './useConfirm';
import { foldersQueryKey } from './useFolders';
import { requestsQueryKey } from './useRequests';
import { httpRequestsQueryKey } from './useHttpRequests';
export function useDeleteFolder(id: string | null) {
const queryClient = useQueryClient();
@@ -36,7 +36,7 @@ export function useDeleteFolder(id: string | null) {
const { workspaceId } = folder;
// Nesting makes it hard to clean things up, so just clear everything that could have been deleted
await queryClient.invalidateQueries(requestsQueryKey({ workspaceId }));
await queryClient.invalidateQueries(httpRequestsQueryKey({ workspaceId }));
await queryClient.invalidateQueries(foldersQueryKey({ workspaceId }));
},
});

View File

@@ -6,7 +6,7 @@ import type { Workspace } from '../lib/models';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useAppRoutes } from './useAppRoutes';
import { useConfirm } from './useConfirm';
import { requestsQueryKey } from './useRequests';
import { httpRequestsQueryKey } from './useHttpRequests';
import { workspacesQueryKey } from './useWorkspaces';
export function useDeleteWorkspace(workspace: Workspace | null) {
@@ -43,8 +43,8 @@ export function useDeleteWorkspace(workspace: Workspace | null) {
}
// Also clean up other things that may have been deleted
queryClient.setQueryData(requestsQueryKey({ workspaceId }), []);
await queryClient.invalidateQueries(requestsQueryKey({ workspaceId }));
queryClient.setQueryData(httpRequestsQueryKey({ workspaceId }), []);
await queryClient.invalidateQueries(httpRequestsQueryKey({ workspaceId }));
},
});
}

View File

@@ -5,7 +5,7 @@ import type { HttpRequest } from '../lib/models';
import { useActiveEnvironmentId } from './useActiveEnvironmentId';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
import { useAppRoutes } from './useAppRoutes';
import { requestsQueryKey } from './useRequests';
import { httpRequestsQueryKey } from './useHttpRequests';
export function useDuplicateRequest({
id,
@@ -21,12 +21,12 @@ export function useDuplicateRequest({
return useMutation<HttpRequest, string>({
mutationFn: async () => {
if (id === null) throw new Error("Can't duplicate a null request");
return invoke('cmd_duplicate_request', { id });
return invoke('cmd_duplicate_http_request', { id });
},
onSettled: () => trackEvent('HttpRequest', 'Duplicate'),
onSuccess: async (request) => {
queryClient.setQueryData<HttpRequest[]>(
requestsQueryKey({ workspaceId: request.workspaceId }),
httpRequestsQueryKey({ workspaceId: request.workspaceId }),
(requests) => [...(requests ?? []), request],
);
if (navigateAfter && activeWorkspaceId !== null) {

View File

@@ -0,0 +1,22 @@
import { useQuery } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api';
import type { GrpcRequest } from '../lib/models';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
export function grpcRequestsQueryKey({ workspaceId }: { workspaceId: string }) {
return ['grpc_requests', { workspaceId }];
}
export function useGrpcRequests() {
const workspaceId = useActiveWorkspaceId();
return (
useQuery({
enabled: workspaceId != null,
queryKey: grpcRequestsQueryKey({ workspaceId: workspaceId ?? 'n/a' }),
queryFn: async () => {
if (workspaceId == null) return [];
return (await invoke('cmd_list_grpc_requests', { workspaceId })) as GrpcRequest[];
},
}).data ?? []
);
}

View File

@@ -6,11 +6,11 @@ import { useOsInfo } from './useOsInfo';
export type HotkeyAction =
| 'environmentEditor.toggle'
| 'grpc.send'
| 'hotkeys.showHelp'
| 'request.create'
| 'request.duplicate'
| 'request.send'
| 'grpc_request.send'
| 'http_request.create'
| 'http_request.duplicate'
| 'http_request.send'
| 'requestSwitcher.next'
| 'requestSwitcher.prev'
| 'settings.show'
@@ -20,11 +20,11 @@ export type HotkeyAction =
const hotkeys: Record<HotkeyAction, string[]> = {
'environmentEditor.toggle': ['CmdCtrl+Shift+e'],
'grpc.send': ['CmdCtrl+Enter', 'CmdCtrl+r'],
'grpc_request.send': ['CmdCtrl+Enter', 'CmdCtrl+r'],
'hotkeys.showHelp': ['CmdCtrl+Shift+/'],
'request.create': ['CmdCtrl+n'],
'request.duplicate': ['CmdCtrl+d'],
'request.send': ['CmdCtrl+Enter', 'CmdCtrl+r'],
'http_request.create': ['CmdCtrl+n'],
'http_request.duplicate': ['CmdCtrl+d'],
'http_request.send': ['CmdCtrl+Enter', 'CmdCtrl+r'],
'requestSwitcher.next': ['Control+Shift+Tab'],
'requestSwitcher.prev': ['Control+Tab'],
'settings.show': ['CmdCtrl+,'],
@@ -35,11 +35,11 @@ const hotkeys: Record<HotkeyAction, string[]> = {
const hotkeyLabels: Record<HotkeyAction, string> = {
'environmentEditor.toggle': 'Edit Environments',
'grpc.send': 'Send Message',
'grpc_request.send': 'Send Message',
'hotkeys.showHelp': 'Show Keyboard Shortcuts',
'request.create': 'New Request',
'request.duplicate': 'Duplicate Request',
'request.send': 'Send Request',
'http_request.create': 'New Request',
'http_request.duplicate': 'Duplicate Request',
'http_request.send': 'Send Request',
'requestSwitcher.next': 'Go To Previous Request',
'requestSwitcher.prev': 'Go To Next Request',
'settings.show': 'Open Settings',

View File

@@ -3,19 +3,19 @@ import { invoke } from '@tauri-apps/api';
import type { HttpRequest } from '../lib/models';
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
export function requestsQueryKey({ workspaceId }: { workspaceId: string }) {
export function httpRequestsQueryKey({ workspaceId }: { workspaceId: string }) {
return ['http_requests', { workspaceId }];
}
export function useRequests() {
export function useHttpRequests() {
const workspaceId = useActiveWorkspaceId();
return (
useQuery({
enabled: workspaceId != null,
queryKey: requestsQueryKey({ workspaceId: workspaceId ?? 'n/a' }),
queryKey: httpRequestsQueryKey({ workspaceId: workspaceId ?? 'n/a' }),
queryFn: async () => {
if (workspaceId == null) return [];
return (await invoke('cmd_list_requests', { workspaceId })) as HttpRequest[];
return (await invoke('cmd_list_http_requests', { workspaceId })) as HttpRequest[];
},
}).data ?? []
);

View File

@@ -1,7 +1,7 @@
import type { HttpRequest } from '../lib/models';
import { useRequests } from './useRequests';
import { useHttpRequests } from './useHttpRequests';
export function useRequest(id: string | null): HttpRequest | null {
const requests = useRequests();
const requests = useHttpRequests();
return requests.find((r) => r.id === id) ?? null;
}

View File

@@ -2,9 +2,9 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import { invoke } from '@tauri-apps/api';
import type { HttpRequest } from '../lib/models';
import { getRequest } from '../lib/store';
import { requestsQueryKey } from './useRequests';
import { httpRequestsQueryKey } from './useHttpRequests';
export function useUpdateAnyRequest() {
export function useUpdateAnyHttpRequest() {
const queryClient = useQueryClient();
return useMutation<
@@ -20,14 +20,14 @@ export function useUpdateAnyRequest() {
const patchedRequest =
typeof update === 'function' ? update(request) : { ...request, ...update };
await invoke('cmd_update_request', { request: patchedRequest });
await invoke('cmd_update_http_request', { request: patchedRequest });
},
onMutate: async ({ id, update }) => {
const request = await getRequest(id);
if (request === null) return;
const patchedRequest =
typeof update === 'function' ? update(request) : { ...request, ...update };
queryClient.setQueryData<HttpRequest[]>(requestsQueryKey(request), (requests) =>
queryClient.setQueryData<HttpRequest[]>(httpRequestsQueryKey(request), (requests) =>
(requests ?? []).map((r) => (r.id === patchedRequest.id ? patchedRequest : r)),
);
},

View File

@@ -1,9 +1,9 @@
import { useMutation } from '@tanstack/react-query';
import type { HttpRequest } from '../lib/models';
import { useUpdateAnyRequest } from './useUpdateAnyRequest';
import { useUpdateAnyHttpRequest } from './useUpdateAnyHttpRequest';
export function useUpdateRequest(id: string | null) {
const updateAnyRequest = useUpdateAnyRequest();
export function useUpdateHttpRequest(id: string | null) {
const updateAnyRequest = useUpdateAnyHttpRequest();
return useMutation<void, unknown, Partial<HttpRequest> | ((r: HttpRequest) => HttpRequest)>({
mutationFn: async (update) => updateAnyRequest.mutateAsync({ id: id ?? 'n/a', update }),
});

View File

@@ -9,6 +9,7 @@ export function trackEvent(
| 'Workspace'
| 'Environment'
| 'Folder'
| 'GrpcRequest'
| 'HttpRequest'
| 'HttpResponse'
| 'KeyValue',

View File

@@ -1,6 +1,6 @@
import type { HttpRequest } from './models';
import type { GrpcRequest, HttpRequest } from './models';
export function fallbackRequestName(r: HttpRequest | null): string {
export function fallbackRequestName(r: HttpRequest | GrpcRequest | null): string {
if (r == null) return '';
if (r.name) {
@@ -9,7 +9,7 @@ export function fallbackRequestName(r: HttpRequest | null): string {
const withoutVariables = r.url.replace(/\$\{\[[^\]]+]}/g, '');
if (withoutVariables.trim() === '') {
return 'New Request';
return r.model === 'http_request' ? 'New HTTP Request' : 'new gRPC Request';
}
const fixedUrl = r.url.match(/^https?:\/\//) ? r.url : 'http://' + r.url;

View File

@@ -12,6 +12,7 @@ export const AUTH_TYPE_BEARER = 'bearer';
export type Model =
| Settings
| Workspace
| GrpcRequest
| HttpRequest
| HttpResponse
| KeyValue
@@ -101,6 +102,18 @@ export interface HttpUrlParameter {
enabled?: boolean;
}
export interface GrpcRequest extends BaseModel {
readonly workspaceId: string;
readonly model: 'grpc_request';
folderId: string | null;
sortPriority: number;
name: string;
url: string;
service: string | null;
method: string | null;
message: string;
}
export interface HttpRequest extends BaseModel {
readonly workspaceId: string;
readonly model: 'http_request';

View File

@@ -7,7 +7,7 @@ export async function getSettings(): Promise<Settings> {
export async function getRequest(id: string | null): Promise<HttpRequest | null> {
if (id === null) return null;
const request: HttpRequest = (await invoke('cmd_get_request', { id })) ?? null;
const request: HttpRequest = (await invoke('cmd_get_http_request', { id })) ?? null;
if (request == null) {
return null;
}