mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-12 02:26:30 -05:00
Separate model for GQL introspection data (#222)
This commit is contained in:
@@ -29,7 +29,7 @@ export type CallHttpRequestActionArgs = { httpRequest: HttpRequest, };
|
||||
|
||||
export type CallHttpRequestActionRequest = { index: number, pluginRefId: string, args: CallHttpRequestActionArgs, };
|
||||
|
||||
export type CallTemplateFunctionArgs = { purpose: RenderPurpose, values: { [key in string]?: string }, };
|
||||
export type CallTemplateFunctionArgs = { purpose: RenderPurpose, values: { [key in string]?: JsonValue }, };
|
||||
|
||||
export type CallTemplateFunctionRequest = { name: string, args: CallTemplateFunctionArgs, };
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
-- Clean up old key/values that are no longer used
|
||||
DELETE
|
||||
FROM key_values
|
||||
WHERE key LIKE 'graphql_introspection::%';
|
||||
|
||||
CREATE TABLE graphql_introspections
|
||||
(
|
||||
|
||||
id TEXT NOT NULL
|
||||
PRIMARY KEY,
|
||||
model TEXT DEFAULT 'graphql_introspection' NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
workspace_id TEXT NOT NULL
|
||||
REFERENCES workspaces
|
||||
ON DELETE CASCADE,
|
||||
request_id TEXT NULL
|
||||
REFERENCES http_requests
|
||||
ON DELETE CASCADE,
|
||||
content TEXT NULL
|
||||
);
|
||||
@@ -1,6 +1,6 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type AnyModel = CookieJar | Environment | Folder | GrpcConnection | GrpcEvent | GrpcRequest | HttpRequest | HttpResponse | KeyValue | Plugin | Settings | SyncState | WebsocketConnection | WebsocketEvent | WebsocketRequest | Workspace | WorkspaceMeta;
|
||||
export type AnyModel = CookieJar | Environment | Folder | GraphQlIntrospection | GrpcConnection | GrpcEvent | GrpcRequest | HttpRequest | HttpResponse | KeyValue | Plugin | Settings | SyncState | WebsocketConnection | WebsocketEvent | WebsocketRequest | Workspace | WorkspaceMeta;
|
||||
|
||||
export type Cookie = { raw_cookie: string, domain: CookieDomain, expires: CookieExpires, path: [string, boolean], };
|
||||
|
||||
@@ -20,6 +20,8 @@ export type EnvironmentVariable = { enabled?: boolean, name: string, value: stri
|
||||
|
||||
export type Folder = { model: "folder", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record<string, any>, authenticationType: string | null, description: string, headers: Array<HttpRequestHeader>, name: string, sortPriority: number, };
|
||||
|
||||
export type GraphQlIntrospection = { model: "graphql_introspection", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, content: string | null, };
|
||||
|
||||
export type GrpcConnection = { model: "grpc_connection", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, elapsed: number, error: string | null, method: string, service: string, status: number, state: GrpcConnectionState, trailers: { [key in string]?: string }, url: string, };
|
||||
|
||||
export type GrpcConnectionState = "initialized" | "connected" | "closed";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Environment } from "./gen_models.js";
|
||||
import type { Folder } from "./gen_models.js";
|
||||
import type { GrpcRequest } from "./gen_models.js";
|
||||
import type { HttpRequest } from "./gen_models.js";
|
||||
import type { WebsocketRequest } from "./gen_models.js";
|
||||
import type { Workspace } from "./gen_models.js";
|
||||
import type { Environment } from "./gen_models";
|
||||
import type { Folder } from "./gen_models";
|
||||
import type { GrpcRequest } from "./gen_models";
|
||||
import type { HttpRequest } from "./gen_models";
|
||||
import type { WebsocketRequest } from "./gen_models";
|
||||
import type { Workspace } from "./gen_models";
|
||||
|
||||
export type BatchUpsertResult = { workspaces: Array<Workspace>, environments: Array<Environment>, folders: Array<Folder>, httpRequests: Array<HttpRequest>, grpcRequests: Array<GrpcRequest>, websocketRequests: Array<WebsocketRequest>, };
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
const COMMANDS: &[&str] = &[
|
||||
"delete",
|
||||
"duplicate",
|
||||
"get_graphql_introspection",
|
||||
"get_settings",
|
||||
"grpc_events",
|
||||
"upsert",
|
||||
"upsert_graphql_introspection",
|
||||
"websocket_events",
|
||||
"workspace_models",
|
||||
];
|
||||
|
||||
@@ -5,6 +5,7 @@ export function newStoreData(): ModelStoreData {
|
||||
cookie_jar: {},
|
||||
environment: {},
|
||||
folder: {},
|
||||
graphql_introspection: {},
|
||||
grpc_connection: {},
|
||||
grpc_event: {},
|
||||
grpc_request: {},
|
||||
|
||||
@@ -4,8 +4,10 @@ permissions = [
|
||||
"allow-delete",
|
||||
"allow-duplicate",
|
||||
"allow-get-settings",
|
||||
"allow-get-graphql-introspection",
|
||||
"allow-grpc-events",
|
||||
"allow-upsert",
|
||||
"allow-upsert-graphql-introspection",
|
||||
"allow-websocket-events",
|
||||
"allow-workspace-models",
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::error::Error::GenericError;
|
||||
use crate::error::Result;
|
||||
use crate::models::{AnyModel, GrpcEvent, Settings, WebsocketEvent};
|
||||
use crate::models::{AnyModel, GraphQlIntrospection, GrpcEvent, Settings, WebsocketEvent};
|
||||
use crate::query_manager::QueryManagerExt;
|
||||
use crate::util::UpdateSource;
|
||||
use tauri::{AppHandle, Runtime, WebviewWindow};
|
||||
@@ -90,6 +90,26 @@ pub(crate) fn get_settings<R: Runtime>(app_handle: AppHandle<R>) -> Result<Setti
|
||||
Ok(app_handle.db().get_settings())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn get_graphql_introspection<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
request_id: &str,
|
||||
) -> Result<Option<GraphQlIntrospection>> {
|
||||
Ok(app_handle.db().get_graphql_introspection(request_id))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn upsert_graphql_introspection<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
request_id: &str,
|
||||
workspace_id: &str,
|
||||
content: Option<String>,
|
||||
window: WebviewWindow<R>,
|
||||
) -> Result<GraphQlIntrospection> {
|
||||
let source = UpdateSource::from_window(&window);
|
||||
Ok(app_handle.db().upsert_graphql_introspection(workspace_id, request_id, content, &source)?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn workspace_models<R: Runtime>(
|
||||
window: WebviewWindow<R>,
|
||||
@@ -121,11 +141,11 @@ pub(crate) fn workspace_models<R: Runtime>(
|
||||
}
|
||||
|
||||
let j = serde_json::to_string(&l)?;
|
||||
|
||||
|
||||
// NOTE: There's something weird that happens on Linux. If we send Cyrillic (or maybe other)
|
||||
// unicode characters in this response (doesn't matter where) then the following bug happens:
|
||||
// https://feedback.yaak.app/p/editing-the-url-sometimes-freezes-the-app
|
||||
//
|
||||
//
|
||||
// It's as if every string resulting from the JSON.parse of the models gets encoded slightly
|
||||
// wrong or something, causing the above bug where Codemirror can't calculate the cursor
|
||||
// position anymore (even when none of the characters are included directly in the input).
|
||||
@@ -137,19 +157,22 @@ pub(crate) fn workspace_models<R: Runtime>(
|
||||
}
|
||||
|
||||
fn escape_str_for_webview(input: &str) -> String {
|
||||
input.chars().map(|c| {
|
||||
let code = c as u32;
|
||||
// ASCII
|
||||
if code <= 0x7F {
|
||||
c.to_string()
|
||||
// BMP characters encoded normally
|
||||
} else if code < 0xFFFF {
|
||||
format!("\\u{:04X}", code)
|
||||
// Beyond BMP encoded a surrogate pairs
|
||||
} else {
|
||||
let high = ((code - 0x10000) >> 10) + 0xD800;
|
||||
let low = ((code - 0x10000) & 0x3FF) + 0xDC00;
|
||||
format!("\\u{:04X}\\u{:04X}", high, low)
|
||||
}
|
||||
}).collect()
|
||||
}
|
||||
input
|
||||
.chars()
|
||||
.map(|c| {
|
||||
let code = c as u32;
|
||||
// ASCII
|
||||
if code <= 0x7F {
|
||||
c.to_string()
|
||||
// BMP characters encoded normally
|
||||
} else if code < 0xFFFF {
|
||||
format!("\\u{:04X}", code)
|
||||
// Beyond BMP encoded a surrogate pairs
|
||||
} else {
|
||||
let high = ((code - 0x10000) >> 10) + 0xD800;
|
||||
let low = ((code - 0x10000) & 0x3FF) + 0xDC00;
|
||||
format!("\\u{:04X}\\u{:04X}", high, low)
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ use crate::util::ModelChangeEvent;
|
||||
use log::info;
|
||||
use r2d2::Pool;
|
||||
use r2d2_sqlite::SqliteConnectionManager;
|
||||
use sqlx::SqlitePool;
|
||||
use sqlx::migrate::Migrator;
|
||||
use sqlx::sqlite::SqliteConnectOptions;
|
||||
use sqlx::SqlitePool;
|
||||
use std::fs::create_dir_all;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
@@ -14,7 +14,7 @@ use std::time::Duration;
|
||||
use tauri::async_runtime::Mutex;
|
||||
use tauri::path::BaseDirectory;
|
||||
use tauri::plugin::TauriPlugin;
|
||||
use tauri::{generate_handler, AppHandle, Emitter, Manager, Runtime};
|
||||
use tauri::{AppHandle, Emitter, Manager, Runtime, generate_handler};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
mod commands;
|
||||
@@ -39,13 +39,15 @@ impl SqliteConnection {
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("yaak-models")
|
||||
.invoke_handler(generate_handler![
|
||||
upsert,
|
||||
delete,
|
||||
duplicate,
|
||||
workspace_models,
|
||||
grpc_events,
|
||||
websocket_events,
|
||||
get_graphql_introspection,
|
||||
get_settings,
|
||||
grpc_events,
|
||||
upsert,
|
||||
upsert_graphql_introspection,
|
||||
websocket_events,
|
||||
workspace_models,
|
||||
])
|
||||
.setup(|app_handle, _api| {
|
||||
let app_path = app_handle.path().app_data_dir().unwrap();
|
||||
|
||||
@@ -1342,6 +1342,79 @@ impl UpsertModelInfo for HttpResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
#[enum_def(table_name = "graphql_introspections")]
|
||||
pub struct GraphQlIntrospection {
|
||||
#[ts(type = "\"graphql_introspection\"")]
|
||||
pub model: String,
|
||||
pub id: String,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub updated_at: NaiveDateTime,
|
||||
pub workspace_id: String,
|
||||
pub request_id: String,
|
||||
pub content: Option<String>,
|
||||
}
|
||||
|
||||
impl UpsertModelInfo for GraphQlIntrospection {
|
||||
fn table_name() -> impl IntoTableRef {
|
||||
GraphQlIntrospectionIden::Table
|
||||
}
|
||||
|
||||
fn id_column() -> impl IntoIden + Eq + Clone {
|
||||
GraphQlIntrospectionIden::Id
|
||||
}
|
||||
|
||||
fn generate_id() -> String {
|
||||
generate_prefixed_id("gi")
|
||||
}
|
||||
|
||||
fn order_by() -> (impl IntoColumnRef, Order) {
|
||||
(GraphQlIntrospectionIden::CreatedAt, Desc)
|
||||
}
|
||||
|
||||
fn get_id(&self) -> String {
|
||||
self.id.clone()
|
||||
}
|
||||
|
||||
fn insert_values(
|
||||
self,
|
||||
source: &UpdateSource,
|
||||
) -> Result<Vec<(impl IntoIden + Eq, impl Into<SimpleExpr>)>> {
|
||||
use GraphQlIntrospectionIden::*;
|
||||
Ok(vec![
|
||||
(CreatedAt, upsert_date(source, self.created_at)),
|
||||
(UpdatedAt, upsert_date(source, self.updated_at)),
|
||||
(WorkspaceId, self.workspace_id.into()),
|
||||
(RequestId, self.request_id.into()),
|
||||
(Content, self.content.into()),
|
||||
])
|
||||
}
|
||||
|
||||
fn update_columns() -> Vec<impl IntoIden> {
|
||||
vec![
|
||||
GraphQlIntrospectionIden::UpdatedAt,
|
||||
GraphQlIntrospectionIden::Content,
|
||||
]
|
||||
}
|
||||
|
||||
fn from_row(r: &Row) -> rusqlite::Result<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Ok(Self {
|
||||
id: r.get("id")?,
|
||||
model: r.get("model")?,
|
||||
created_at: r.get("created_at")?,
|
||||
updated_at: r.get("updated_at")?,
|
||||
workspace_id: r.get("workspace_id")?,
|
||||
request_id: r.get("request_id")?,
|
||||
content: r.get("content")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
@@ -2002,6 +2075,7 @@ define_any_model! {
|
||||
CookieJar,
|
||||
Environment,
|
||||
Folder,
|
||||
GraphQlIntrospection,
|
||||
GrpcConnection,
|
||||
GrpcEvent,
|
||||
GrpcRequest,
|
||||
@@ -2031,6 +2105,9 @@ impl<'de> Deserialize<'de> for AnyModel {
|
||||
Some(m) if m == "cookie_jar" => AnyModel::CookieJar(fv(value).unwrap()),
|
||||
Some(m) if m == "environment" => AnyModel::Environment(fv(value).unwrap()),
|
||||
Some(m) if m == "folder" => AnyModel::Folder(fv(value).unwrap()),
|
||||
Some(m) if m == "graphql_introspection" => {
|
||||
AnyModel::GraphQlIntrospection(fv(value).unwrap())
|
||||
}
|
||||
Some(m) if m == "grpc_connection" => AnyModel::GrpcConnection(fv(value).unwrap()),
|
||||
Some(m) if m == "grpc_event" => AnyModel::GrpcEvent(fv(value).unwrap()),
|
||||
Some(m) if m == "grpc_request" => AnyModel::GrpcRequest(fv(value).unwrap()),
|
||||
|
||||
55
src-tauri/yaak-models/src/queries/graphql_introspections.rs
Normal file
55
src-tauri/yaak-models/src/queries/graphql_introspections.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use crate::db_context::DbContext;
|
||||
use crate::error::Result;
|
||||
use crate::models::{GraphQlIntrospection, GraphQlIntrospectionIden};
|
||||
use crate::util::UpdateSource;
|
||||
use chrono::{Duration, Utc};
|
||||
use sea_query::{Expr, Query, SqliteQueryBuilder};
|
||||
use sea_query_rusqlite::RusqliteBinder;
|
||||
|
||||
impl<'a> DbContext<'a> {
|
||||
pub fn get_graphql_introspection(&self, request_id: &str) -> Option<GraphQlIntrospection> {
|
||||
self.find_optional(GraphQlIntrospectionIden::RequestId, request_id)
|
||||
}
|
||||
|
||||
pub fn upsert_graphql_introspection(
|
||||
&self,
|
||||
workspace_id: &str,
|
||||
request_id: &str,
|
||||
content: Option<String>,
|
||||
source: &UpdateSource,
|
||||
) -> Result<GraphQlIntrospection> {
|
||||
// Clean up old ones every time a new one is upserted
|
||||
self.delete_expired_graphql_introspections()?;
|
||||
|
||||
match self.get_graphql_introspection(request_id) {
|
||||
None => self.upsert(
|
||||
&GraphQlIntrospection {
|
||||
content,
|
||||
request_id: request_id.to_string(),
|
||||
workspace_id: workspace_id.to_string(),
|
||||
..Default::default()
|
||||
},
|
||||
source,
|
||||
),
|
||||
Some(introspection) => self.upsert(
|
||||
&GraphQlIntrospection {
|
||||
content,
|
||||
..introspection
|
||||
},
|
||||
source,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete_expired_graphql_introspections(&self) -> Result<()> {
|
||||
let cutoff = Utc::now().naive_utc() - Duration::days(7);
|
||||
let (sql, params) = Query::delete()
|
||||
.from_table(GraphQlIntrospectionIden::Table)
|
||||
.cond_where(Expr::col(GraphQlIntrospectionIden::UpdatedAt).lt(cutoff))
|
||||
.build_rusqlite(SqliteQueryBuilder);
|
||||
|
||||
let mut stmt = self.conn.resolve().prepare(sql.as_str())?;
|
||||
stmt.execute(&*params.as_params())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ mod batch;
|
||||
mod cookie_jars;
|
||||
mod environments;
|
||||
mod folders;
|
||||
mod graphql_introspections;
|
||||
mod grpc_connections;
|
||||
mod grpc_events;
|
||||
mod grpc_requests;
|
||||
|
||||
@@ -29,7 +29,7 @@ export type CallHttpRequestActionArgs = { httpRequest: HttpRequest, };
|
||||
|
||||
export type CallHttpRequestActionRequest = { index: number, pluginRefId: string, args: CallHttpRequestActionArgs, };
|
||||
|
||||
export type CallTemplateFunctionArgs = { purpose: RenderPurpose, values: { [key in string]?: string }, };
|
||||
export type CallTemplateFunctionArgs = { purpose: RenderPurpose, values: { [key in string]?: JsonValue }, };
|
||||
|
||||
export type CallTemplateFunctionRequest = { name: string, args: CallTemplateFunctionArgs, };
|
||||
|
||||
|
||||
@@ -117,6 +117,7 @@ impl TryFrom<AnyModel> for SyncModel {
|
||||
|
||||
// Non-sync models
|
||||
AnyModel::CookieJar(m) => return Err(UnknownModel(m.model)),
|
||||
AnyModel::GraphQlIntrospection(m) => return Err(UnknownModel(m.model)),
|
||||
AnyModel::GrpcConnection(m) => return Err(UnknownModel(m.model)),
|
||||
AnyModel::GrpcEvent(m) => return Err(UnknownModel(m.model)),
|
||||
AnyModel::HttpResponse(m) => return Err(UnknownModel(m.model)),
|
||||
|
||||
@@ -242,7 +242,7 @@ mod parse_and_render_tests {
|
||||
async fn render_valid_fn() -> Result<()> {
|
||||
let vars = HashMap::new();
|
||||
let template = r#"${[ say_hello(a='John', b='Kate') ]}"#;
|
||||
let result = r#"say_hello: 2, Some("John") Some("Kate")"#;
|
||||
let result = r#"say_hello: 2, Some(String("John")) Some(String("Kate"))"#;
|
||||
|
||||
struct CB {}
|
||||
impl TemplateCallback for CB {
|
||||
@@ -271,7 +271,7 @@ mod parse_and_render_tests {
|
||||
async fn render_fn_arg() -> Result<()> {
|
||||
let vars = HashMap::new();
|
||||
let template = r#"${[ upper(foo='bar') ]}"#;
|
||||
let result = r#"BAR"#;
|
||||
let result = r#""BAR""#;
|
||||
struct CB {}
|
||||
impl TemplateCallback for CB {
|
||||
async fn run(
|
||||
@@ -305,7 +305,7 @@ mod parse_and_render_tests {
|
||||
let mut vars = HashMap::new();
|
||||
vars.insert("foo".to_string(), "bar".to_string());
|
||||
let template = r#"${[ upper(foo=b64'Zm9vICdiYXInIGJheg') ]}"#;
|
||||
let result = r#"FOO 'BAR' BAZ"#;
|
||||
let result = r#""FOO 'BAR' BAZ""#;
|
||||
struct CB {}
|
||||
impl TemplateCallback for CB {
|
||||
async fn run(&self, fn_name: &str, args: HashMap<String, serde_json::Value>) -> Result<String> {
|
||||
@@ -334,7 +334,7 @@ mod parse_and_render_tests {
|
||||
let mut vars = HashMap::new();
|
||||
vars.insert("foo".to_string(), "bar".to_string());
|
||||
let template = r#"${[ upper(foo='${[ foo ]}') ]}"#;
|
||||
let result = r#"BAR"#;
|
||||
let result = r#""BAR""#;
|
||||
struct CB {}
|
||||
impl TemplateCallback for CB {
|
||||
async fn run(&self, fn_name: &str, args: HashMap<String, serde_json::Value>) -> Result<String> {
|
||||
@@ -364,7 +364,7 @@ mod parse_and_render_tests {
|
||||
let mut vars = HashMap::new();
|
||||
vars.insert("foo".to_string(), "bar".to_string());
|
||||
let template = r#"${[ no_op(inner='${[ foo ]}') ]}"#;
|
||||
let result = r#"bar"#;
|
||||
let result = r#""bar""#;
|
||||
struct CB {}
|
||||
impl TemplateCallback for CB {
|
||||
async fn run(&self, fn_name: &str, args: HashMap<String, serde_json::Value>) -> Result<String> {
|
||||
@@ -392,7 +392,7 @@ mod parse_and_render_tests {
|
||||
async fn render_nested_fn() -> Result<()> {
|
||||
let vars = HashMap::new();
|
||||
let template = r#"${[ upper(foo=secret()) ]}"#;
|
||||
let result = r#"ABC"#;
|
||||
let result = r#""ABC""#;
|
||||
struct CB {}
|
||||
impl TemplateCallback for CB {
|
||||
async fn run(&self, fn_name: &str, args: HashMap<String, serde_json::Value>) -> Result<String> {
|
||||
|
||||
@@ -343,7 +343,7 @@ function SetupSyncDropdown({ workspaceMeta }: { workspaceMeta: WorkspaceMeta })
|
||||
color: 'success',
|
||||
label: 'Open Workspace Settings',
|
||||
leftSlot: <Icon icon="settings" />,
|
||||
onSelect: openWorkspaceSettings,
|
||||
onSelect: () => openWorkspaceSettings('general'),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ import { activeWorkspaceIdAtom } from '../hooks/useActiveWorkspace';
|
||||
import { useHotKey } from '../hooks/useHotKey';
|
||||
import { useKeyboardEvent } from '../hooks/useKeyboardEvent';
|
||||
import { useRecentRequests } from '../hooks/useRecentRequests';
|
||||
import {allRequestsAtom} from "../hooks/useAllRequests";
|
||||
import { allRequestsAtom } from '../hooks/useAllRequests';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { resolvedModelName } from '../lib/resolvedModelName';
|
||||
import { router } from '../lib/router';
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { HttpRequest } from '@yaakapp-internal/models';
|
||||
import type { GraphQLSchema, IntrospectionQuery } from 'graphql';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import type { GraphQlIntrospection, HttpRequest } from '@yaakapp-internal/models';
|
||||
import type { GraphQLSchema } from 'graphql';
|
||||
import { buildClientSchema, getIntrospectionQuery } from 'graphql';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { minPromiseMillis } from '../lib/minPromiseMillis';
|
||||
@@ -7,7 +9,6 @@ import { getResponseBodyText } from '../lib/responseBody';
|
||||
import { sendEphemeralRequest } from '../lib/sendEphemeralRequest';
|
||||
import { useActiveEnvironment } from './useActiveEnvironment';
|
||||
import { useDebouncedValue } from './useDebouncedValue';
|
||||
import { useKeyValue } from './useKeyValue';
|
||||
|
||||
const introspectionRequestBody = JSON.stringify({
|
||||
query: getIntrospectionQuery(),
|
||||
@@ -20,18 +21,38 @@ export function useIntrospectGraphQL(
|
||||
) {
|
||||
// Debounce the request because it can change rapidly, and we don't
|
||||
// want to send so too many requests.
|
||||
const request = useDebouncedValue(baseRequest);
|
||||
const debouncedRequest = useDebouncedValue(baseRequest);
|
||||
const activeEnvironment = useActiveEnvironment();
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string>();
|
||||
const [schema, setSchema] = useState<GraphQLSchema | null>(null);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { value: introspection, set: setIntrospection } = useKeyValue<IntrospectionQuery | null>({
|
||||
key: ['graphql_introspection', baseRequest.id],
|
||||
fallback: null,
|
||||
namespace: 'global',
|
||||
const introspection = useQuery({
|
||||
queryKey: ['introspection', baseRequest.id],
|
||||
queryFn: async () =>
|
||||
invoke<GraphQlIntrospection | null>('plugin:yaak-models|get_graphql_introspection', {
|
||||
requestId: baseRequest.id,
|
||||
}),
|
||||
});
|
||||
|
||||
const upsertIntrospection = useCallback(
|
||||
async (content: string | null) => {
|
||||
const v = await invoke<GraphQlIntrospection>(
|
||||
'plugin:yaak-models|upsert_graphql_introspection',
|
||||
{
|
||||
requestId: baseRequest.id,
|
||||
workspaceId: baseRequest.workspaceId,
|
||||
content: content ?? '',
|
||||
},
|
||||
);
|
||||
|
||||
// Update local introspection
|
||||
queryClient.setQueryData(['introspection', baseRequest.id], v);
|
||||
},
|
||||
[baseRequest.id, baseRequest.workspaceId, queryClient],
|
||||
);
|
||||
|
||||
const refetch = useCallback(async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
@@ -62,15 +83,14 @@ export function useIntrospectGraphQL(
|
||||
return setError('Empty body returned in response');
|
||||
}
|
||||
|
||||
const { data } = JSON.parse(bodyText);
|
||||
console.log(`Got introspection response for ${baseRequest.url}`, data);
|
||||
await setIntrospection(data);
|
||||
console.log(`Got introspection response for ${baseRequest.url}`, bodyText);
|
||||
await upsertIntrospection(bodyText);
|
||||
} catch (err) {
|
||||
setError(String(err));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [activeEnvironment?.id, baseRequest, setIntrospection]);
|
||||
}, [activeEnvironment?.id, baseRequest, upsertIntrospection]);
|
||||
|
||||
useEffect(() => {
|
||||
// Skip introspection if automatic is disabled and we already have one
|
||||
@@ -81,27 +101,27 @@ export function useIntrospectGraphQL(
|
||||
refetch().catch(console.error);
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [request.id, request.url, request.method, activeEnvironment?.id]);
|
||||
}, [baseRequest.id, debouncedRequest.url, debouncedRequest.method, activeEnvironment?.id]);
|
||||
|
||||
const clear = useCallback(async () => {
|
||||
setError('');
|
||||
setSchema(null);
|
||||
await setIntrospection(null);
|
||||
}, [setIntrospection]);
|
||||
await upsertIntrospection(null);
|
||||
}, [upsertIntrospection]);
|
||||
|
||||
useEffect(() => {
|
||||
if (introspection == null) {
|
||||
if (introspection.data?.content == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const schema = buildClientSchema(introspection);
|
||||
const schema = buildClientSchema(JSON.parse(introspection.data.content).data);
|
||||
setSchema(schema);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (e: any) {
|
||||
setError('message' in e ? e.message : String(e));
|
||||
}
|
||||
}, [introspection]);
|
||||
}, [introspection.data?.content]);
|
||||
|
||||
return { schema, isLoading, error, refetch, clear };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user