From bd02206df2f8b68aed763b6a5ae98b8151180d0e Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Mon, 19 Aug 2024 14:10:44 -0700 Subject: [PATCH] A bunch of improvements to chaining --- package-lock.json | 8 +- package.json | 2 +- plugin-runtime-types/package.json | 2 +- .../src/gen/FindHttpResponsesRequest.ts | 3 + .../src/gen/FindHttpResponsesResponse.ts | 4 + .../src/gen/InternalEventPayload.ts | 4 +- plugin-runtime-types/src/index.ts | 2 + plugin-runtime-types/src/plugins/Context.ts | 5 + .../src/plugins/TemplateFunctionPlugin.ts | 2 +- plugin-runtime/src/index.worker.ts | 9 ++ src-tauri/src/lib.rs | 33 +++-- src-tauri/src/render.rs | 130 +++++++++++++++--- src-tauri/yaak_models/src/queries.rs | 10 +- src-tauri/yaak_plugin_runtime/src/events.rs | 18 +++ src-tauri/yaak_plugin_runtime/src/manager.rs | 7 +- src-web/components/App.tsx | 4 +- src-web/components/GlobalHooks.tsx | 9 +- .../components/RecentResponsesDropdown.tsx | 2 +- src-web/components/TemplateFunctionDialog.tsx | 16 ++- .../core/Editor/twig/templateTags.ts | 2 +- src-web/hooks/useDebouncedValue.ts | 2 +- src-web/hooks/useSettings.ts | 20 +-- src-web/hooks/useUpdateSettings.ts | 6 +- 23 files changed, 218 insertions(+), 82 deletions(-) create mode 100644 plugin-runtime-types/src/gen/FindHttpResponsesRequest.ts create mode 100644 plugin-runtime-types/src/gen/FindHttpResponsesResponse.ts diff --git a/package-lock.json b/package-lock.json index d8a226b4..8c87ea3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "@tauri-apps/plugin-log": "^2.0.0-rc.0", "@tauri-apps/plugin-os": "^2.0.0-rc.0", "@tauri-apps/plugin-shell": "^2.0.0-rc.0", - "@yaakapp/api": "^0.1.7", + "@yaakapp/api": "^0.1.9", "buffer": "^6.0.3", "classnames": "^2.3.2", "cm6-graphql": "^0.0.9", @@ -2990,9 +2990,9 @@ "integrity": "sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==" }, "node_modules/@yaakapp/api": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@yaakapp/api/-/api-0.1.7.tgz", - "integrity": "sha512-6dheXUuhX3o1KwZ7ngtB/OcQ4qCiXxg3LN2t1jA28nnZBlGCek+h9yndiF/PGWl5yReMcXy8rn4pq4W9zAGHLg==", + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/@yaakapp/api/-/api-0.1.9.tgz", + "integrity": "sha512-5mgBoNplXUnNIUpLgGPCjcf6p/BcsU485cH3/MnIzcXIaMfFpZVVwHq5vf9cm+NDcr5hwYmPyIbwmBf9uVoV2Q==", "dependencies": { "@types/node": "^22.0.0" } diff --git a/package.json b/package.json index 4f78e09a..3c2d82e5 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "@tauri-apps/plugin-log": "^2.0.0-rc.0", "@tauri-apps/plugin-os": "^2.0.0-rc.0", "@tauri-apps/plugin-shell": "^2.0.0-rc.0", - "@yaakapp/api": "^0.1.7", + "@yaakapp/api": "^0.1.9", "buffer": "^6.0.3", "classnames": "^2.3.2", "cm6-graphql": "^0.0.9", diff --git a/plugin-runtime-types/package.json b/plugin-runtime-types/package.json index 97222c40..0bb071b2 100644 --- a/plugin-runtime-types/package.json +++ b/plugin-runtime-types/package.json @@ -1,6 +1,6 @@ { "name": "@yaakapp/api", - "version": "0.1.7", + "version": "0.1.9", "main": "lib/index.js", "typings": "./lib/index.d.ts", "files": [ diff --git a/plugin-runtime-types/src/gen/FindHttpResponsesRequest.ts b/plugin-runtime-types/src/gen/FindHttpResponsesRequest.ts new file mode 100644 index 00000000..d34d683b --- /dev/null +++ b/plugin-runtime-types/src/gen/FindHttpResponsesRequest.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type FindHttpResponsesRequest = { requestId: string, limit: bigint | null, }; diff --git a/plugin-runtime-types/src/gen/FindHttpResponsesResponse.ts b/plugin-runtime-types/src/gen/FindHttpResponsesResponse.ts new file mode 100644 index 00000000..c97506ea --- /dev/null +++ b/plugin-runtime-types/src/gen/FindHttpResponsesResponse.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { HttpResponse } from "./HttpResponse"; + +export type FindHttpResponsesResponse = { httpResponses: Array, }; diff --git a/plugin-runtime-types/src/gen/InternalEventPayload.ts b/plugin-runtime-types/src/gen/InternalEventPayload.ts index e619fb24..0d195899 100644 --- a/plugin-runtime-types/src/gen/InternalEventPayload.ts +++ b/plugin-runtime-types/src/gen/InternalEventPayload.ts @@ -10,6 +10,8 @@ import type { ExportHttpRequestRequest } from "./ExportHttpRequestRequest"; import type { ExportHttpRequestResponse } from "./ExportHttpRequestResponse"; import type { FilterRequest } from "./FilterRequest"; import type { FilterResponse } from "./FilterResponse"; +import type { FindHttpResponsesRequest } from "./FindHttpResponsesRequest"; +import type { FindHttpResponsesResponse } from "./FindHttpResponsesResponse"; import type { GetHttpRequestActionsRequest } from "./GetHttpRequestActionsRequest"; import type { GetHttpRequestActionsResponse } from "./GetHttpRequestActionsResponse"; import type { GetHttpRequestByIdRequest } from "./GetHttpRequestByIdRequest"; @@ -23,4 +25,4 @@ import type { SendHttpRequestRequest } from "./SendHttpRequestRequest"; import type { SendHttpRequestResponse } from "./SendHttpRequestResponse"; import type { ShowToastRequest } from "./ShowToastRequest"; -export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "get_http_request_actions_request" } & GetHttpRequestActionsRequest | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "copy_text_request" } & CopyTextRequest | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "empty_response" } & EmptyResponse; +export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "get_http_request_actions_request" } & GetHttpRequestActionsRequest | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "copy_text_request" } & CopyTextRequest | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "empty_response" } & EmptyResponse; diff --git a/plugin-runtime-types/src/index.ts b/plugin-runtime-types/src/index.ts index aaf02a52..85733dc7 100644 --- a/plugin-runtime-types/src/index.ts +++ b/plugin-runtime-types/src/index.ts @@ -23,6 +23,8 @@ export * from './gen/ExportHttpRequestResponse'; export * from './gen/FilterRequest'; export * from './gen/FilterResponse'; export * from './gen/Folder'; +export * from './gen/FindHttpResponsesRequest'; +export * from './gen/FindHttpResponsesResponse'; export * from './gen/GetHttpRequestActionsResponse'; export * from './gen/GetHttpRequestByIdRequest'; export * from './gen/GetHttpRequestByIdResponse'; diff --git a/plugin-runtime-types/src/plugins/Context.ts b/plugin-runtime-types/src/plugins/Context.ts index 00344104..9dc1793d 100644 --- a/plugin-runtime-types/src/plugins/Context.ts +++ b/plugin-runtime-types/src/plugins/Context.ts @@ -1,3 +1,5 @@ +import { FindHttpResponsesRequest } from '../gen/FindHttpResponsesRequest'; +import { FindHttpResponsesResponse } from '../gen/FindHttpResponsesResponse'; import { GetHttpRequestByIdRequest } from '../gen/GetHttpRequestByIdRequest'; import { GetHttpRequestByIdResponse } from '../gen/GetHttpRequestByIdResponse'; import { RenderHttpRequestRequest } from '../gen/RenderHttpRequestRequest'; @@ -18,4 +20,7 @@ export type Context = { getById(args: GetHttpRequestByIdRequest): Promise; render(args: RenderHttpRequestRequest): Promise; }; + httpResponse: { + find(args: FindHttpResponsesRequest): Promise; + }; }; diff --git a/plugin-runtime-types/src/plugins/TemplateFunctionPlugin.ts b/plugin-runtime-types/src/plugins/TemplateFunctionPlugin.ts index 78ffec7c..0774a53e 100644 --- a/plugin-runtime-types/src/plugins/TemplateFunctionPlugin.ts +++ b/plugin-runtime-types/src/plugins/TemplateFunctionPlugin.ts @@ -3,5 +3,5 @@ import { TemplateFunction } from '../gen/TemplateFunction'; import { Context } from './Context'; export type TemplateFunctionPlugin = TemplateFunction & { - onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise; + onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise; }; diff --git a/plugin-runtime/src/index.worker.ts b/plugin-runtime/src/index.worker.ts index 2a8ee9c8..c95e1c54 100644 --- a/plugin-runtime/src/index.worker.ts +++ b/plugin-runtime/src/index.worker.ts @@ -1,4 +1,5 @@ import { + FindHttpResponsesResponse, GetHttpRequestByIdResponse, HttpRequestAction, ImportResponse, @@ -17,6 +18,7 @@ import { readFileSync } from 'node:fs'; import path from 'node:path'; import * as util from 'node:util'; import { parentPort, workerData } from 'node:worker_threads'; +import { text } from '../../src-web/components/core/Editor/text/extension'; new Promise(async (resolve, reject) => { const { pluginDir, pluginRefId } = workerData; @@ -101,6 +103,13 @@ new Promise(async (resolve, reject) => { await sendAndWaitForReply({ type: 'show_toast_request', ...args }); }, }, + httpResponse: { + async find(args) { + const payload = { type: 'find_http_responses_request', ...args } as const; + const { httpResponses } = await sendAndWaitForReply(payload); + return httpResponses; + }, + }, httpRequest: { async getById({ id }) { const payload = { type: 'get_http_request_by_id_request', id } as const; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 7b53d6e0..e5f7af58 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -52,14 +52,14 @@ use yaak_models::queries::{ get_grpc_request, get_http_request, get_http_response, get_key_value_raw, get_or_create_settings, get_workspace, list_cookie_jars, list_environments, list_folders, list_grpc_connections, list_grpc_events, list_grpc_requests, list_http_requests, - list_responses, list_workspaces, set_key_value_raw, update_response_if_id, update_settings, - upsert_cookie_jar, upsert_environment, upsert_folder, upsert_grpc_connection, + list_http_responses, 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_workspace, }; use yaak_plugin_runtime::events::{ - CallHttpRequestActionRequest, FilterResponse, GetHttpRequestActionsResponse, - GetHttpRequestByIdResponse, GetTemplateFunctionsResponse, InternalEvent, InternalEventPayload, - RenderHttpRequestResponse, SendHttpRequestResponse, + CallHttpRequestActionRequest, FilterResponse, FindHttpResponsesResponse, + GetHttpRequestActionsResponse, GetHttpRequestByIdResponse, GetTemplateFunctionsResponse, + InternalEvent, InternalEventPayload, RenderHttpRequestResponse, SendHttpRequestResponse, }; use yaak_templates::{Parser, Tokens}; @@ -1496,7 +1496,7 @@ async fn cmd_list_http_responses( limit: Option, w: WebviewWindow, ) -> Result, String> { - list_responses(&w, request_id, limit) + list_http_responses(&w, request_id, limit) .await .map_err(|e| e.to_string()) } @@ -1960,12 +1960,6 @@ async fn handle_plugin_event( event: &InternalEvent, ) -> Option { let event = match event.clone().payload { - InternalEventPayload::GetHttpRequestByIdRequest(req) => { - let http_request = get_http_request(app_handle, req.id.as_str()).await.ok(); - Some(InternalEventPayload::GetHttpRequestByIdResponse( - GetHttpRequestByIdResponse { http_request }, - )) - } InternalEventPayload::CopyTextRequest(req) => { app_handle .clipboard() @@ -1979,6 +1973,21 @@ async fn handle_plugin_event( .expect("Failed to emit show_toast"); None } + InternalEventPayload::FindHttpResponsesRequest(req) => { + let http_responses = + list_http_responses(app_handle, req.request_id.as_str(), req.limit) + .await + .unwrap_or_default(); + Some(InternalEventPayload::FindHttpResponsesResponse( + FindHttpResponsesResponse { http_responses }, + )) + } + InternalEventPayload::GetHttpRequestByIdRequest(req) => { + let http_request = get_http_request(app_handle, req.id.as_str()).await.ok(); + Some(InternalEventPayload::GetHttpRequestByIdResponse( + GetHttpRequestByIdResponse { http_request }, + )) + } InternalEventPayload::RenderHttpRequestRequest(req) => { let webview_windows = app_handle.get_focused_window()?.webview_windows(); let w = match webview_windows.iter().next() { diff --git a/src-tauri/src/render.rs b/src-tauri/src/render.rs index e2456fc1..2d25ce3a 100644 --- a/src-tauri/src/render.rs +++ b/src-tauri/src/render.rs @@ -1,8 +1,11 @@ use crate::template_callback::PluginTemplateCallback; -use serde_json::Value; +use serde_json::{json, Map, Value}; use std::collections::HashMap; use tauri::{AppHandle, Manager, Runtime}; -use yaak_models::models::{Environment, EnvironmentVariable, GrpcMetadataEntry, GrpcRequest, HttpRequest, HttpRequestHeader, HttpUrlParameter, Workspace}; +use yaak_models::models::{ + Environment, EnvironmentVariable, GrpcMetadataEntry, GrpcRequest, HttpRequest, + HttpRequestHeader, HttpUrlParameter, Workspace, +}; use yaak_templates::{parse_and_render, TemplateCallback}; pub async fn render_template( @@ -36,17 +39,12 @@ pub async fn render_grpc_request( let mut authentication = HashMap::new(); for (k, v) in r.authentication.clone() { - let v = if v.is_string() { - render(v.as_str().unwrap(), vars, cb).await - } else { - v.to_string() - }; - authentication.insert(render(k.as_str(), vars, cb).await, Value::from(v)); + authentication.insert(k, render_json_value(v, vars, cb).await); } let url = render(r.url.as_str(), vars, cb).await; - GrpcRequest{ + GrpcRequest { url, metadata, authentication, @@ -83,22 +81,12 @@ pub async fn render_http_request( let mut body = HashMap::new(); for (k, v) in r.body.clone() { - let v = if v.is_string() { - render(v.as_str().unwrap(), vars, cb).await - } else { - v.to_string() - }; - body.insert(render(k.as_str(), vars, cb).await, Value::from(v)); + body.insert(k, render_json_value(v, vars, cb).await); } let mut authentication = HashMap::new(); for (k, v) in r.authentication.clone() { - let v = if v.is_string() { - render(v.as_str().unwrap(), vars, cb).await - } else { - v.to_string() - }; - authentication.insert(render(k.as_str(), vars, cb).await, Value::from(v)); + authentication.insert(k, render_json_value(v, vars, cb).await); } let url = render(r.url.clone().as_str(), vars, cb).await; @@ -173,3 +161,103 @@ fn add_variable_to_map( map } + +pub async fn render_json_value( + v: Value, + vars: &HashMap, + cb: &T, +) -> Value { + match v { + Value::String(s) => json!(render(s.as_str(), vars, cb).await), + Value::Array(a) => { + let mut new_a = Vec::new(); + for v in a { + new_a.push(Box::pin(render_json_value(v, vars, cb)).await) + } + json!(new_a) + } + Value::Object(o) => { + let mut new_o = Map::new(); + for (k, v) in o { + let key = Box::pin(render(k.as_str(), vars, cb)).await; + let value = Box::pin(render_json_value(v, vars, cb)).await; + new_o.insert(key, value); + } + json!(new_o) + } + v => v, + } +} + +#[cfg(test)] +mod tests { + use serde_json::json; + use std::collections::HashMap; + use yaak_templates::TemplateCallback; + + struct EmptyCB {} + + impl TemplateCallback for EmptyCB { + async fn run( + &self, + _fn_name: &str, + _args: HashMap, + ) -> Result { + todo!() + } + } + + #[tokio::test] + async fn render_json_value_string() { + let v = json!("${[a]}"); + let mut vars = HashMap::new(); + vars.insert("a".to_string(), "aaa".to_string()); + + let result = super::render_json_value(v, &vars, &EmptyCB {}).await; + assert_eq!(result, json!("aaa")) + } + + #[tokio::test] + async fn render_json_value_array() { + let v = json!(["${[a]}", "${[a]}"]); + let mut vars = HashMap::new(); + vars.insert("a".to_string(), "aaa".to_string()); + + let result = super::render_json_value(v, &vars, &EmptyCB {}).await; + assert_eq!(result, json!(["aaa", "aaa"])) + } + + #[tokio::test] + async fn render_json_value_object() { + let v = json!({"${[a]}": "${[a]}"}); + let mut vars = HashMap::new(); + vars.insert("a".to_string(), "aaa".to_string()); + + let result = super::render_json_value(v, &vars, &EmptyCB {}).await; + assert_eq!(result, json!({"aaa": "aaa"})) + } + + #[tokio::test] + async fn render_json_value_nested() { + let v = json!([ + 123, + {"${[a]}": "${[a]}"}, + null, + "${[a]}", + false, + {"x": ["${[a]}"]} + ]); + let mut vars = HashMap::new(); + vars.insert("a".to_string(), "aaa".to_string()); + + let result = super::render_json_value(v, &vars, &EmptyCB {}).await; + assert_eq!(result, json!([ + 123, + {"aaa": "aaa"}, + null, + "aaa", + false, + {"x": ["aaa"]} + ])) + } +} diff --git a/src-tauri/yaak_models/src/queries.rs b/src-tauri/yaak_models/src/queries.rs index ff628bf5..9f73bc5e 100644 --- a/src-tauri/yaak_models/src/queries.rs +++ b/src-tauri/yaak_models/src/queries.rs @@ -720,6 +720,8 @@ pub async fn delete_environment( emit_deleted_model(window, env) } +const SETTINGS_ID: &str = "default"; + async fn get_settings(mgr: &impl Manager) -> Result { let dbm = &*mgr.state::(); let db = dbm.0.lock().await.get().unwrap(); @@ -727,7 +729,7 @@ async fn get_settings(mgr: &impl Manager) -> Result { let (sql, params) = Query::select() .from(SettingsIden::Table) .column(Asterisk) - .cond_where(Expr::col(SettingsIden::Id).eq("default")) + .cond_where(Expr::col(SettingsIden::Id).eq(SETTINGS_ID)) .build_rusqlite(SqliteQueryBuilder); let mut stmt = db.prepare(sql.as_str())?; Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?) @@ -744,7 +746,7 @@ pub async fn get_or_create_settings(mgr: &impl Manager) -> Settin let (sql, params) = Query::insert() .into_table(SettingsIden::Table) .columns([SettingsIden::Id]) - .values_panic(["default".into()]) + .values_panic([SETTINGS_ID.into()]) .returning_all() .build_rusqlite(SqliteQueryBuilder); @@ -1324,13 +1326,13 @@ pub async fn delete_all_http_responses( window: &WebviewWindow, request_id: &str, ) -> Result<()> { - for r in list_responses(window, request_id, None).await? { + for r in list_http_responses(window, request_id, None).await? { delete_http_response(window, &r.id).await?; } Ok(()) } -pub async fn list_responses( +pub async fn list_http_responses( mgr: &impl Manager, request_id: &str, limit: Option, diff --git a/src-tauri/yaak_plugin_runtime/src/events.rs b/src-tauri/yaak_plugin_runtime/src/events.rs index 0f834346..26691b79 100644 --- a/src-tauri/yaak_plugin_runtime/src/events.rs +++ b/src-tauri/yaak_plugin_runtime/src/events.rs @@ -54,6 +54,9 @@ pub enum InternalEventPayload { GetHttpRequestByIdRequest(GetHttpRequestByIdRequest), GetHttpRequestByIdResponse(GetHttpRequestByIdResponse), + + FindHttpResponsesRequest(FindHttpResponsesRequest), + FindHttpResponsesResponse(FindHttpResponsesResponse), /// Returned when a plugin doesn't get run, just so the server /// has something to listen for @@ -347,6 +350,21 @@ pub struct GetHttpRequestByIdResponse { pub http_request: Option, } +#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] +#[serde(default, rename_all = "camelCase")] +#[ts(export)] +pub struct FindHttpResponsesRequest { + pub request_id: String, + pub limit: Option, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] +#[serde(default, rename_all = "camelCase")] +#[ts(export)] +pub struct FindHttpResponsesResponse { + pub http_responses: Vec, +} + #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[serde(default, rename_all = "camelCase")] #[ts(export)] diff --git a/src-tauri/yaak_plugin_runtime/src/manager.rs b/src-tauri/yaak_plugin_runtime/src/manager.rs index 5245bbcf..b4ba877c 100644 --- a/src-tauri/yaak_plugin_runtime/src/manager.rs +++ b/src-tauri/yaak_plugin_runtime/src/manager.rs @@ -169,9 +169,10 @@ impl PluginManager { content: &str, content_type: &str, ) -> Result { - let plugin_name = match content_type { - "application/json" => "filter-jsonpath", - _ => "filter-xpath", + let plugin_name = if content_type.to_lowercase().contains("json") { + "filter-jsonpath" + } else { + "filter-xpath" }; let event = self diff --git a/src-web/components/App.tsx b/src-web/components/App.tsx index 1959d2b2..3f8f1544 100644 --- a/src-web/components/App.tsx +++ b/src-web/components/App.tsx @@ -7,6 +7,8 @@ import { HTML5Backend } from 'react-dnd-html5-backend'; import { HelmetProvider } from 'react-helmet-async'; import { AppRouter } from './AppRouter'; +const ENABLE_REACT_QUERY_DEVTOOLS = false; + const queryClient = new QueryClient({ defaultOptions: { queries: { @@ -20,7 +22,7 @@ const queryClient = new QueryClient({ export function App() { return ( - + {ENABLE_REACT_QUERY_DEVTOOLS && } diff --git a/src-web/components/GlobalHooks.tsx b/src-web/components/GlobalHooks.tsx index 13c8a3ae..733b9baa 100644 --- a/src-web/components/GlobalHooks.tsx +++ b/src-web/components/GlobalHooks.tsx @@ -23,7 +23,7 @@ import { useRecentEnvironments } from '../hooks/useRecentEnvironments'; import { useRecentRequests } from '../hooks/useRecentRequests'; import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces'; import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey'; -import { settingsQueryKey, useSettings } from '../hooks/useSettings'; +import { settingsAtom, useSettings } from '../hooks/useSettings'; import { useSyncThemeToDocument } from '../hooks/useSyncThemeToDocument'; import { useToggleCommandPalette } from '../hooks/useToggleCommandPalette'; import { workspacesAtom } from '../hooks/useWorkspaces'; @@ -65,6 +65,7 @@ export function GlobalHooks() { windowLabel: string; } + const setSettings = useSetAtom(settingsAtom); const setWorkspaces = useSetAtom(workspacesAtom); const setHttpRequests = useSetAtom(httpRequestsAtom); const setGrpcRequests = useSetAtom(grpcRequestsAtom); @@ -85,8 +86,6 @@ export function GlobalHooks() { ? keyValueQueryKey(model) : model.model === 'cookie_jar' ? cookieJarsQueryKey(model) - : model.model === 'settings' - ? settingsQueryKey() : null; if (model.model === 'http_request' && windowLabel !== getCurrentWebviewWindow().label) { @@ -107,6 +106,8 @@ export function GlobalHooks() { setGrpcRequests(updateModelList(model, pushToFront)); } else if (model.model === 'environment') { setEnvironments(updateModelList(model, pushToFront)); + } else if (model.model === 'settings') { + setSettings(model); } else if (queryKey != null) { // TODO: Convert all models to use Jotai queryClient.setQueryData(queryKey, (current: unknown) => { @@ -146,8 +147,6 @@ export function GlobalHooks() { queryClient.setQueryData(keyValueQueryKey(model), undefined); } else if (model.model === 'cookie_jar') { queryClient.setQueryData(cookieJarsQueryKey(model), undefined); - } else if (model.model === 'settings') { - queryClient.setQueryData(settingsQueryKey(), undefined); } }); diff --git a/src-web/components/RecentResponsesDropdown.tsx b/src-web/components/RecentResponsesDropdown.tsx index 8ef970fe..5043f323 100644 --- a/src-web/components/RecentResponsesDropdown.tsx +++ b/src-web/components/RecentResponsesDropdown.tsx @@ -57,7 +57,7 @@ export const RecentResponsesDropdown = function ResponsePane({ key: 'clear-all', label: `Delete ${responses.length} ${pluralize('Response', responses.length)}`, onSelect: deleteAllResponses.mutate, - hidden: responses.length <= 1, + hidden: responses.length === 0, disabled: responses.length === 0, }, { type: 'separator' }, diff --git a/src-web/components/TemplateFunctionDialog.tsx b/src-web/components/TemplateFunctionDialog.tsx index 65787292..35b3a228 100644 --- a/src-web/components/TemplateFunctionDialog.tsx +++ b/src-web/components/TemplateFunctionDialog.tsx @@ -1,15 +1,16 @@ -import { useCallback, useMemo, useState } from 'react'; -import type { FnArg } from '../gen/FnArg'; -import type { Tokens } from '../gen/Tokens'; -import { useHttpRequests } from '../hooks/useHttpRequests'; -import { useRenderTemplate } from '../hooks/useRenderTemplate'; import type { TemplateFunction, TemplateFunctionArg, TemplateFunctionHttpRequestArg, TemplateFunctionSelectArg, TemplateFunctionTextArg, -} from '../hooks/useTemplateFunctions'; +} from '@yaakapp/api'; +import { useCallback, useMemo, useState } from 'react'; +import type { FnArg } from '../gen/FnArg'; +import type { Tokens } from '../gen/Tokens'; +import { useDebouncedValue } from '../hooks/useDebouncedValue'; +import { useHttpRequests } from '../hooks/useHttpRequests'; +import { useRenderTemplate } from '../hooks/useRenderTemplate'; import { useTemplateTokensToString } from '../hooks/useTemplateTokensToString'; import { fallbackRequestName } from '../lib/fallbackRequestName'; import { Button } from './core/Button'; @@ -86,7 +87,8 @@ export function TemplateFunctionDialog({ templateFunction, hide, initialTokens, hide(); }; - const rendered = useRenderTemplate(tagText.data ?? ''); + const debouncedTagText = useDebouncedValue(tagText.data ?? '', 200); + const rendered = useRenderTemplate(debouncedTagText); return ( diff --git a/src-web/components/core/Editor/twig/templateTags.ts b/src-web/components/core/Editor/twig/templateTags.ts index 25af96c7..d0fb3832 100644 --- a/src-web/components/core/Editor/twig/templateTags.ts +++ b/src-web/components/core/Editor/twig/templateTags.ts @@ -59,7 +59,7 @@ class TemplateTagWidget extends WidgetType { export function templateTags(options: TwigCompletionOption[]) { const templateTagMatcher = new BetterMatchDecorator({ - regexp: /\$\{\[\s*([^\]]+)\s*]}/g, + regexp: /\$\{\[\s*(.+)(?!]})\s*]}/g, decoration(match, view, matchStartPos) { const matchEndPos = matchStartPos + match[0].length - 1; diff --git a/src-web/hooks/useDebouncedValue.ts b/src-web/hooks/useDebouncedValue.ts index 39cb7d53..c6639aa7 100644 --- a/src-web/hooks/useDebouncedValue.ts +++ b/src-web/hooks/useDebouncedValue.ts @@ -1,7 +1,7 @@ import { useEffect } from 'react'; import { useDebouncedState } from './useDebouncedState'; -export function useDebouncedValue(value: T, delay?: number) { +export function useDebouncedValue(value: T, delay = 500) { const [state, setState] = useDebouncedState(value, delay); useEffect(() => setState(value), [setState, value]); return state; diff --git a/src-web/hooks/useSettings.ts b/src-web/hooks/useSettings.ts index 34e15466..51847d3d 100644 --- a/src-web/hooks/useSettings.ts +++ b/src-web/hooks/useSettings.ts @@ -1,19 +1,11 @@ -import { useQuery } from '@tanstack/react-query'; +import { useAtomValue } from 'jotai'; +import { atom } from 'jotai/index'; import type { Settings } from '../lib/models/Settings'; -import { invokeCmd } from '../lib/tauri'; +import { getSettings } from '../lib/store'; -export function settingsQueryKey() { - return ['settings']; -} +const settings = await getSettings(); +export const settingsAtom = atom(settings); export function useSettings() { - return ( - useQuery({ - queryKey: settingsQueryKey(), - queryFn: async () => { - const settings = (await invokeCmd('cmd_get_settings')) as Settings; - return [settings]; - }, - }).data?.[0] ?? undefined - ); + return useAtomValue(settingsAtom); } diff --git a/src-web/hooks/useUpdateSettings.ts b/src-web/hooks/useUpdateSettings.ts index d3a4888c..e196de84 100644 --- a/src-web/hooks/useUpdateSettings.ts +++ b/src-web/hooks/useUpdateSettings.ts @@ -1,15 +1,13 @@ import { useMutation } from '@tanstack/react-query'; import type { Settings } from '../lib/models'; +import { getSettings } from '../lib/store'; import { invokeCmd } from '../lib/tauri'; -import { useSettings } from './useSettings'; export function useUpdateSettings() { - const settings = useSettings(); - return useMutation>({ mutationKey: ['update_settings'], mutationFn: async (patch) => { - if (settings == null) return; + const settings = await getSettings(); const newSettings: Settings = { ...settings, ...patch }; await invokeCmd('cmd_update_settings', { settings: newSettings }); },