Everything in messages now

This commit is contained in:
Gregory Schier
2024-02-22 19:51:30 -08:00
parent e3016f7100
commit 16506d1ddd
10 changed files with 231 additions and 205 deletions

View File

@@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "\n SELECT\n id, model, workspace_id, request_id, connection_id, created_at, content,\n event_type AS \"event_type!: GrpcEventType\",\n metadata AS \"metadata!: sqlx::types::Json<HashMap<String, String>>\"\n FROM grpc_events\n WHERE connection_id = ?\n ", "query": "\n SELECT\n id, model, workspace_id, request_id, connection_id, created_at, content, status, error,\n event_type AS \"event_type!: GrpcEventType\",\n metadata AS \"metadata!: sqlx::types::Json<HashMap<String, String>>\"\n FROM grpc_events\n WHERE connection_id = ?\n ",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@@ -39,13 +39,23 @@
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "event_type!: GrpcEventType", "name": "status",
"ordinal": 7, "ordinal": 7,
"type_info": "Int64"
},
{
"name": "error",
"ordinal": 8,
"type_info": "Text"
},
{
"name": "event_type!: GrpcEventType",
"ordinal": 9,
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "metadata!: sqlx::types::Json<HashMap<String, String>>", "name": "metadata!: sqlx::types::Json<HashMap<String, String>>",
"ordinal": 8, "ordinal": 10,
"type_info": "Text" "type_info": "Text"
} }
], ],
@@ -60,9 +70,11 @@
false, false,
false, false,
false, false,
true,
true,
false, false,
false false
] ]
}, },
"hash": "737045ddd5f8ba3454425e82b9d3943f93649742d8f78613e01d322745e47ebd" "hash": "18ada3bb42c29f1940ab2e61961d79cdd69210f3dc2076aedcadeba8e34dcb6e"
} }

View File

@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "\n INSERT INTO grpc_events (\n id, workspace_id, request_id, connection_id, content, event_type, metadata\n )\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n content = excluded.content,\n event_type = excluded.event_type,\n metadata = excluded.metadata\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 7
},
"nullable": []
},
"hash": "3dce053aef78e831db2369f3c49e891cb8a9e1ba6e7a60fe9e24292a3f97dca3"
}

View File

@@ -1,6 +1,6 @@
{ {
"db_name": "SQLite", "db_name": "SQLite",
"query": "\n SELECT\n id, model, workspace_id, request_id, connection_id, created_at, content,\n event_type AS \"event_type!: GrpcEventType\",\n metadata AS \"metadata!: sqlx::types::Json<HashMap<String, String>>\"\n FROM grpc_events\n WHERE id = ?\n ", "query": "\n SELECT\n id, model, workspace_id, request_id, connection_id, created_at, content, status, error,\n event_type AS \"event_type!: GrpcEventType\",\n metadata AS \"metadata!: sqlx::types::Json<HashMap<String, String>>\"\n FROM grpc_events\n WHERE id = ?\n ",
"describe": { "describe": {
"columns": [ "columns": [
{ {
@@ -39,13 +39,23 @@
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "event_type!: GrpcEventType", "name": "status",
"ordinal": 7, "ordinal": 7,
"type_info": "Int64"
},
{
"name": "error",
"ordinal": 8,
"type_info": "Text"
},
{
"name": "event_type!: GrpcEventType",
"ordinal": 9,
"type_info": "Text" "type_info": "Text"
}, },
{ {
"name": "metadata!: sqlx::types::Json<HashMap<String, String>>", "name": "metadata!: sqlx::types::Json<HashMap<String, String>>",
"ordinal": 8, "ordinal": 10,
"type_info": "Text" "type_info": "Text"
} }
], ],
@@ -60,9 +70,11 @@
false, false,
false, false,
false, false,
true,
true,
false, false,
false false
] ]
}, },
"hash": "20d6b878bb8d16bde3e78e22cf801b5b191905d867091bb54a210256a0145a17" "hash": "92d8f003a8f7df692345f2d2fd2504c9222645976e3433e32e190f4ee4bf100d"
} }

View File

@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n INSERT INTO grpc_events (\n id, workspace_id, request_id, connection_id, content, event_type, metadata, \n status, error\n )\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n content = excluded.content,\n event_type = excluded.event_type,\n metadata = excluded.metadata,\n status = excluded.status,\n error = excluded.error\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 9
},
"nullable": []
},
"hash": "df70bef8eac244eeedd03f5e42573b4c9dbd19cd764e97817c7de30209d21af9"
}

View File

@@ -1,67 +1,69 @@
CREATE TABLE grpc_requests CREATE TABLE grpc_requests
( (
id TEXT NOT NULL id TEXT NOT NULL
PRIMARY KEY, PRIMARY KEY,
model TEXT DEFAULT 'grpc_request' NOT NULL, model TEXT DEFAULT 'grpc_request' NOT NULL,
workspace_id TEXT NOT NULL workspace_id TEXT NOT NULL
REFERENCES workspaces REFERENCES workspaces
ON DELETE CASCADE, ON DELETE CASCADE,
folder_id TEXT NULL folder_id TEXT NULL
REFERENCES folders REFERENCES folders
ON DELETE CASCADE, ON DELETE CASCADE,
created_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')) NOT NULL, created_at DATETIME DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')) NOT NULL,
updated_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')) NOT NULL, updated_at DATETIME DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')) NOT NULL,
name TEXT NOT NULL, name TEXT NOT NULL,
sort_priority REAL NOT NULL, sort_priority REAL NOT NULL,
url TEXT NOT NULL, url TEXT NOT NULL,
service TEXT NULL, service TEXT NULL,
method TEXT NULL, method TEXT NULL,
message TEXT NOT NULL, message TEXT NOT NULL,
proto_files TEXT DEFAULT '[]' NOT NULL, proto_files TEXT DEFAULT '[]' NOT NULL,
authentication TEXT DEFAULT '{}' NOT NULL, authentication TEXT DEFAULT '{}' NOT NULL,
authentication_type TEXT NULL, authentication_type TEXT NULL,
metadata TEXT DEFAULT '[]' NOT NULL metadata TEXT DEFAULT '[]' NOT NULL
); );
CREATE TABLE grpc_connections CREATE TABLE grpc_connections
( (
id TEXT NOT NULL id TEXT NOT NULL
PRIMARY KEY, PRIMARY KEY,
model TEXT DEFAULT 'grpc_connection' NOT NULL, model TEXT DEFAULT 'grpc_connection' NOT NULL,
workspace_id TEXT NOT NULL workspace_id TEXT NOT NULL
REFERENCES workspaces REFERENCES workspaces
ON DELETE CASCADE, ON DELETE CASCADE,
request_id TEXT NOT NULL request_id TEXT NOT NULL
REFERENCES grpc_requests REFERENCES grpc_requests
ON DELETE CASCADE, ON DELETE CASCADE,
created_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')) NOT NULL, created_at DATETIME DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')) NOT NULL,
updated_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')) NOT NULL, updated_at DATETIME DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')) NOT NULL,
url TEXT NOT NULL, url TEXT NOT NULL,
service TEXT NOT NULL, service TEXT NOT NULL,
method TEXT NOT NULL, method TEXT NOT NULL,
status INTEGER DEFAULT -1 NOT NULL, status INTEGER DEFAULT -1 NOT NULL,
error TEXT NULL, error TEXT NULL,
elapsed INTEGER DEFAULT 0 NOT NULL, elapsed INTEGER DEFAULT 0 NOT NULL,
trailers TEXT DEFAULT '{}' NOT NULL trailers TEXT DEFAULT '{}' NOT NULL
); );
CREATE TABLE grpc_events CREATE TABLE grpc_events
( (
id TEXT NOT NULL id TEXT NOT NULL
PRIMARY KEY, PRIMARY KEY,
model TEXT DEFAULT 'grpc_event' NOT NULL, model TEXT DEFAULT 'grpc_event' NOT NULL,
workspace_id TEXT NOT NULL workspace_id TEXT NOT NULL
REFERENCES workspaces REFERENCES workspaces
ON DELETE CASCADE, ON DELETE CASCADE,
request_id TEXT NOT NULL request_id TEXT NOT NULL
REFERENCES grpc_requests REFERENCES grpc_requests
ON DELETE CASCADE, ON DELETE CASCADE,
connection_id TEXT NOT NULL connection_id TEXT NOT NULL
REFERENCES grpc_connections REFERENCES grpc_connections
ON DELETE CASCADE, ON DELETE CASCADE,
created_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')) NOT NULL, created_at DATETIME DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')) NOT NULL,
updated_at DATETIME DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')) NOT NULL, updated_at DATETIME DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')) NOT NULL,
metadata TEXT DEFAULT '{}' NOT NULL, metadata TEXT DEFAULT '{}' NOT NULL,
event_type TEXT NOT NULL, event_type TEXT NOT NULL,
content TEXT NOT NULL status INTEGER NULL,
error TEXT NULL,
content TEXT NOT NULL
); );

View File

@@ -340,7 +340,7 @@ async fn cmd_grpc_go(
let grpc_listen = { let grpc_listen = {
let w = w.clone(); let w = w.clone();
let base_msg = base_msg.clone(); let base_event = base_msg.clone();
let req = req.clone(); let req = req.clone();
let workspace = workspace.clone(); let workspace = workspace.clone();
let environment = environment.clone(); let environment = environment.clone();
@@ -350,15 +350,14 @@ async fn cmd_grpc_go(
req.message req.message
}; };
let msg = render::render(&raw_msg, &workspace, environment.as_ref()); let msg = render::render(&raw_msg, &workspace, environment.as_ref());
let conn_id = conn_id.clone();
upsert_grpc_event( upsert_grpc_event(
&w, &w,
&GrpcEvent { &GrpcEvent {
content: format!("Connecting to {}", req.url), content: format!("Connecting to {}", req.url),
event_type: GrpcEventType::Info, event_type: GrpcEventType::ConnectionStart,
metadata: Json(metadata.clone()), metadata: Json(metadata.clone()),
..base_msg.clone() ..base_event.clone()
}, },
) )
.await .await
@@ -405,7 +404,7 @@ async fn cmd_grpc_go(
&GrpcEvent { &GrpcEvent {
event_type: GrpcEventType::ClientMessage, event_type: GrpcEventType::ClientMessage,
content: msg, content: msg,
..base_msg.clone() ..base_event.clone()
}, },
) )
.await .await
@@ -419,13 +418,13 @@ async fn cmd_grpc_go(
&GrpcEvent { &GrpcEvent {
metadata: Json(metadata_to_map(msg.metadata().clone())), metadata: Json(metadata_to_map(msg.metadata().clone())),
content: if msg.metadata().len() == 0 { content: if msg.metadata().len() == 0 {
"Connection established" "Received response"
} else { } else {
"Received metadata" "Received response with metadata"
} }
.to_string(), .to_string(),
event_type: GrpcEventType::Info, event_type: GrpcEventType::Info,
..base_msg.clone() ..base_event.clone()
}, },
) )
.await .await
@@ -435,30 +434,32 @@ async fn cmd_grpc_go(
&GrpcEvent { &GrpcEvent {
content: serialize_message(&msg.into_inner()).unwrap(), content: serialize_message(&msg.into_inner()).unwrap(),
event_type: GrpcEventType::ServerMessage, event_type: GrpcEventType::ServerMessage,
..base_msg.clone() ..base_event.clone()
}, },
) )
.await .await
.unwrap(); .unwrap();
upsert_grpc_connection( upsert_grpc_event(
&w, &w,
&GrpcConnection { &GrpcEvent {
elapsed: start.elapsed().as_millis() as i64, content: "Connection complete".to_string(),
status: Code::Ok as i64, event_type: GrpcEventType::ConnectionEnd,
..get_grpc_connection(&w, &conn_id).await.unwrap().clone() status: Some(Code::Ok as i64),
..base_event.clone()
}, },
) )
.await .await
.unwrap(); .unwrap();
} }
Some(Err(e)) => { Some(Err(e)) => {
upsert_grpc_connection( upsert_grpc_event(
&w, &w,
&GrpcConnection { &GrpcEvent {
content: "Failed to connect".to_string(),
event_type: GrpcEventType::ConnectionEnd,
error: Some(e.to_string()), error: Some(e.to_string()),
elapsed: start.elapsed().as_millis() as i64, status: Some(Code::Unknown as i64),
status: Code::Unknown as i64, ..base_event.clone()
..get_grpc_connection(&w, &conn_id).await.unwrap().clone()
}, },
) )
.await .await
@@ -470,33 +471,34 @@ async fn cmd_grpc_go(
} }
let mut stream = match maybe_stream { let mut stream = match maybe_stream {
Some(Ok(Ok(s))) => { Some(Ok(Ok(stream))) => {
upsert_grpc_event( upsert_grpc_event(
&w, &w,
&GrpcEvent { &GrpcEvent {
metadata: Json(metadata_to_map(s.metadata().clone())), metadata: Json(metadata_to_map(stream.metadata().clone())),
content: if s.metadata().len() == 0 { content: if stream.metadata().len() == 0 {
"Connection established" "Received response"
} else { } else {
"Received metadata" "Received response with metadata"
} }
.to_string(), .to_string(),
event_type: GrpcEventType::Info, event_type: GrpcEventType::Info,
..base_msg.clone() ..base_event.clone()
}, },
) )
.await .await
.unwrap(); .unwrap();
s.into_inner() stream.into_inner()
} }
Some(Ok(Err(e))) => { Some(Ok(Err(e))) => {
upsert_grpc_connection( upsert_grpc_event(
&w, &w,
&GrpcConnection { &GrpcEvent {
error: Some(e.message().to_string()), error: Some(e.message().to_string()),
status: e.code() as i64, status: Some(e.code() as i64),
elapsed: start.elapsed().as_millis() as i64, content: e.code().description().to_string(),
..get_grpc_connection(&w, &conn_id).await.unwrap().clone() event_type: GrpcEventType::ConnectionEnd,
..base_event.clone()
}, },
) )
.await .await
@@ -504,13 +506,14 @@ async fn cmd_grpc_go(
return; return;
} }
Some(Err(e)) => { Some(Err(e)) => {
upsert_grpc_connection( upsert_grpc_event(
&w, &w,
&GrpcConnection { &GrpcEvent {
error: Some(e), error: Some(e),
status: Code::Unknown as i64, status: Some(Code::Unknown as i64),
elapsed: start.elapsed().as_millis() as i64, content: "Unknown error".to_string(),
..get_grpc_connection(&w, &conn_id).await.unwrap().clone() event_type: GrpcEventType::ConnectionEnd,
..base_event.clone()
}, },
) )
.await .await
@@ -529,7 +532,7 @@ async fn cmd_grpc_go(
&GrpcEvent { &GrpcEvent {
content: message, content: message,
event_type: GrpcEventType::ServerMessage, event_type: GrpcEventType::ServerMessage,
..base_msg.clone() ..base_event.clone()
}, },
) )
.await .await
@@ -541,13 +544,14 @@ async fn cmd_grpc_go(
.await .await
.unwrap_or_default() .unwrap_or_default()
.unwrap_or_default(); .unwrap_or_default();
upsert_grpc_connection( upsert_grpc_event(
&w, &w,
&GrpcConnection { &GrpcEvent {
elapsed: start.elapsed().as_millis() as i64, content: "Connection complete".to_string(),
status: Code::Unavailable as i64, status: Some(Code::Unavailable as i64),
trailers: Json(metadata_to_map(trailers)), metadata: Json(metadata_to_map(trailers)),
..get_grpc_connection(&w, &conn_id).await.unwrap().clone() event_type: GrpcEventType::ConnectionEnd,
..base_event.clone()
}, },
) )
.await .await
@@ -555,13 +559,14 @@ async fn cmd_grpc_go(
break; break;
} }
Err(status) => { Err(status) => {
upsert_grpc_connection( upsert_grpc_event(
&w, &w,
&GrpcConnection { &GrpcEvent {
elapsed: start.elapsed().as_millis() as i64, content: status.to_string(),
status: Code::Unavailable as i64, status: Some(status.code() as i64),
trailers: Json(metadata_to_map(status.metadata().clone())), metadata: Json(metadata_to_map(status.metadata().clone())),
..get_grpc_connection(&w, &conn_id).await.unwrap().clone() event_type: GrpcEventType::ConnectionEnd,
..base_event.clone()
}, },
) )
.await .await
@@ -578,16 +583,32 @@ async fn cmd_grpc_go(
let w = w.clone(); let w = w.clone();
tokio::select! { tokio::select! {
_ = grpc_listen => { _ = grpc_listen => {
// upsert_grpc_connection( let events = list_grpc_events(&w, &conn_id)
// &w, .await
// &GrpcConnection{ .unwrap();
// elapsed: start.elapsed().as_millis() as i64, let closed_event = events
// status: Code::Ok as i64, .iter()
// ..conn .find(|e| GrpcEventType::ConnectionEnd == e.event_type);
// }, let closed_status = closed_event.and_then(|e| e.status).unwrap_or(Code::Unavailable as i64);
// ).await.unwrap(); upsert_grpc_connection(
&w,
&GrpcConnection{
elapsed: start.elapsed().as_millis() as i64,
status: closed_status,
..get_grpc_connection(&w, &conn_id).await.unwrap().clone()
},
).await.unwrap();
}, },
_ = cancelled_rx.changed() => { _ = cancelled_rx.changed() => {
upsert_grpc_event(
&w,
&GrpcEvent {
content: "Cancelled".to_string(),
event_type: GrpcEventType::ConnectionEnd,
status: Some(Code::Cancelled as i64),
..base_msg.clone()
},
).await.unwrap();
upsert_grpc_connection( upsert_grpc_connection(
&w, &w,
&GrpcConnection { &GrpcConnection {

View File

@@ -237,7 +237,7 @@ pub struct GrpcConnection {
pub trailers: Json<HashMap<String, String>>, pub trailers: Json<HashMap<String, String>>,
} }
#[derive(sqlx::Type, Debug, Clone, Serialize, Deserialize)] #[derive(sqlx::Type, Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
#[sqlx(rename_all = "snake_case")] #[sqlx(rename_all = "snake_case")]
pub enum GrpcEventType { pub enum GrpcEventType {
@@ -245,7 +245,8 @@ pub enum GrpcEventType {
Error, Error,
ClientMessage, ClientMessage,
ServerMessage, ServerMessage,
ConnectionResponse, ConnectionStart,
ConnectionEnd,
} }
impl Default for GrpcEventType { impl Default for GrpcEventType {
@@ -266,6 +267,8 @@ pub struct GrpcEvent {
pub content: String, pub content: String,
pub event_type: GrpcEventType, pub event_type: GrpcEventType,
pub metadata: Json<HashMap<String, String>>, pub metadata: Json<HashMap<String, String>>,
pub status: Option<i64>,
pub error: Option<String>,
} }
#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize, Default)] #[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize, Default)]
@@ -699,32 +702,37 @@ pub async fn list_grpc_connections(
pub async fn upsert_grpc_event( pub async fn upsert_grpc_event(
mgr: &impl Manager<Wry>, mgr: &impl Manager<Wry>,
message: &GrpcEvent, event: &GrpcEvent,
) -> Result<GrpcEvent, sqlx::Error> { ) -> Result<GrpcEvent, sqlx::Error> {
let db = get_db(mgr).await; let db = get_db(mgr).await;
let id = match message.id.as_str() { let id = match event.id.as_str() {
"" => generate_id(Some("ge")), "" => generate_id(Some("ge")),
_ => message.id.to_string(), _ => event.id.to_string(),
}; };
sqlx::query!( sqlx::query!(
r#" r#"
INSERT INTO grpc_events ( INSERT INTO grpc_events (
id, workspace_id, request_id, connection_id, content, event_type, metadata id, workspace_id, request_id, connection_id, content, event_type, metadata,
status, error
) )
VALUES (?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (id) DO UPDATE SET ON CONFLICT (id) DO UPDATE SET
updated_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP,
content = excluded.content, content = excluded.content,
event_type = excluded.event_type, event_type = excluded.event_type,
metadata = excluded.metadata metadata = excluded.metadata,
status = excluded.status,
error = excluded.error
"#, "#,
id, id,
message.workspace_id, event.workspace_id,
message.request_id, event.request_id,
message.connection_id, event.connection_id,
message.content, event.content,
message.event_type, event.event_type,
message.metadata, event.metadata,
event.status,
event.error,
) )
.execute(&db) .execute(&db)
.await?; .await?;
@@ -744,7 +752,7 @@ pub async fn get_grpc_event(
GrpcEvent, GrpcEvent,
r#" r#"
SELECT SELECT
id, model, workspace_id, request_id, connection_id, created_at, content, id, model, workspace_id, request_id, connection_id, created_at, content, status, error,
event_type AS "event_type!: GrpcEventType", event_type AS "event_type!: GrpcEventType",
metadata AS "metadata!: sqlx::types::Json<HashMap<String, String>>" metadata AS "metadata!: sqlx::types::Json<HashMap<String, String>>"
FROM grpc_events FROM grpc_events
@@ -765,7 +773,7 @@ pub async fn list_grpc_events(
GrpcEvent, GrpcEvent,
r#" r#"
SELECT SELECT
id, model, workspace_id, request_id, connection_id, created_at, content, id, model, workspace_id, request_id, connection_id, created_at, content, status, error,
event_type AS "event_type!: GrpcEventType", event_type AS "event_type!: GrpcEventType",
metadata AS "metadata!: sqlx::types::Json<HashMap<String, String>>" metadata AS "metadata!: sqlx::types::Json<HashMap<String, String>>"
FROM grpc_events FROM grpc_events

View File

@@ -1,5 +1,5 @@
import classNames from 'classnames'; import classNames from 'classnames';
import { format, addMilliseconds } from 'date-fns'; import { format } from 'date-fns';
import type { CSSProperties, ReactNode } from 'react'; import type { CSSProperties, ReactNode } from 'react';
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import { useGrpcConnections } from '../hooks/useGrpcConnections'; import { useGrpcConnections } from '../hooks/useGrpcConnections';
@@ -27,48 +27,11 @@ interface Props {
| 'no-method'; | 'no-method';
} }
const CONNECTION_RESPONSE_EVENT_ID = 'connection_response';
export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }: Props) { export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }: Props) {
const [activeEventId, setActiveEventId] = useState<string | null>(null); const [activeEventId, setActiveEventId] = useState<string | null>(null);
const connections = useGrpcConnections(activeRequest.id ?? null); const connections = useGrpcConnections(activeRequest.id ?? null);
const activeConnection = connections[0] ?? null; const activeConnection = connections[0] ?? null;
const ogEvents = useGrpcEvents(activeConnection?.id ?? null); const events = useGrpcEvents(activeConnection?.id ?? null);
const events = useMemo(() => {
const createdAt =
activeConnection != null &&
addMilliseconds(activeConnection.createdAt, activeConnection.elapsed)
.toISOString()
.replace('Z', '');
if (activeConnection == null || activeConnection.elapsed === 0) {
return ogEvents;
} else if (activeConnection.error != null) {
return [
...ogEvents,
{
id: CONNECTION_RESPONSE_EVENT_ID,
eventType: 'error',
content: activeConnection.error,
metadata: activeConnection.trailers,
createdAt,
updatedAt: createdAt,
} as GrpcEvent,
];
} else {
return [
...ogEvents,
{
id: CONNECTION_RESPONSE_EVENT_ID,
eventType: activeConnection.status === 0 ? 'connection_response' : 'error',
content: `Connection ${GRPC_CODES[activeConnection.status] ?? 'closed'}`,
metadata: activeConnection.trailers,
createdAt,
updatedAt: createdAt,
} as GrpcEvent,
];
}
}, [activeConnection, ogEvents]);
const activeEvent = useMemo( const activeEvent = useMemo(
() => events.find((m) => m.id === activeEventId) ?? null, () => events.find((m) => m.id === activeEventId) ?? null,
@@ -110,19 +73,16 @@ export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }:
/> />
</HStack> </HStack>
<div className="overflow-y-auto h-full"> <div className="overflow-y-auto h-full">
{...events.map((m) => ( {...events.map((e) => (
<MessageRow <EventRow
key={m.id} key={e.id}
isActive={m.id === activeEventId} event={e}
eventType={m.eventType} isActive={e.id === activeEventId}
timestamp={m.createdAt}
onClick={() => { onClick={() => {
if (m.id === activeEventId) setActiveEventId(null); if (e.id === activeEventId) setActiveEventId(null);
else setActiveEventId(m.id); else setActiveEventId(e.id);
}} }}
> />
{m.content}
</MessageRow>
))} ))}
</div> </div>
</div> </div>
@@ -147,11 +107,11 @@ export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }:
) : ( ) : (
<div className="h-full grid grid-rows-[auto_minmax(0,1fr)]"> <div className="h-full grid grid-rows-[auto_minmax(0,1fr)]">
<div className="mb-2 select-text cursor-text font-semibold"> <div className="mb-2 select-text cursor-text font-semibold">
{activeEvent.content} {activeEvent.error ?? activeEvent.content}
</div> </div>
{Object.keys(activeEvent.metadata).length === 0 ? ( {Object.keys(activeEvent.metadata).length === 0 ? (
<EmptyStateText> <EmptyStateText>
No {activeEvent.eventType === 'connection_response' ? 'trailers' : 'metadata'} No {activeEvent.eventType === 'connection_end' ? 'trailers' : 'metadata'}
</EmptyStateText> </EmptyStateText>
) : ( ) : (
<KeyValueRows> <KeyValueRows>
@@ -170,19 +130,16 @@ export function GrpcConnectionMessagesPane({ style, methodType, activeRequest }:
); );
} }
function MessageRow({ function EventRow({
onClick, onClick,
isActive, isActive,
eventType, event,
children,
timestamp,
}: { }: {
onClick?: () => void; onClick?: () => void;
isActive?: boolean; isActive?: boolean;
eventType: GrpcEvent['eventType']; event: GrpcEvent;
children: ReactNode;
timestamp: string;
}) { }) {
const { eventType, status, createdAt, content, error } = event;
return ( return (
<button <button
onClick={onClick} onClick={onClick}
@@ -199,9 +156,9 @@ function MessageRow({
? 'text-blue-600' ? 'text-blue-600'
: eventType === 'client_message' : eventType === 'client_message'
? 'text-violet-600' ? 'text-violet-600'
: eventType === 'error' : eventType === 'error' || (status != null && status > 0)
? 'text-orange-600' ? 'text-orange-600'
: eventType === 'connection_response' : eventType === 'connection_end'
? 'text-green-600' ? 'text-green-600'
: 'text-gray-700' : 'text-gray-700'
} }
@@ -210,9 +167,9 @@ function MessageRow({
? 'Server message' ? 'Server message'
: eventType === 'client_message' : eventType === 'client_message'
? 'Client message' ? 'Client message'
: eventType === 'error' : eventType === 'error' || (status != null && status > 0)
? 'Error' ? 'Error'
: eventType === 'connection_response' : eventType === 'connection_end'
? 'Connection response' ? 'Connection response'
: undefined : undefined
} }
@@ -221,16 +178,16 @@ function MessageRow({
? 'arrowBigDownDash' ? 'arrowBigDownDash'
: eventType === 'client_message' : eventType === 'client_message'
? 'arrowBigUpDash' ? 'arrowBigUpDash'
: eventType === 'error' : eventType === 'error' || (status != null && status > 0)
? 'alert' ? 'alert'
: eventType === 'connection_response' : eventType === 'connection_end'
? 'check' ? 'check'
: 'info' : 'info'
} }
/> />
<div className={classNames('w-full truncate text-2xs')}>{children}</div> <div className={classNames('w-full truncate text-2xs')}>{error ?? content}</div>
<div className={classNames('opacity-50 text-2xs')}> <div className={classNames('opacity-50 text-2xs')}>
{format(timestamp + 'Z', 'HH:mm:ss.SSS')} {format(createdAt + 'Z', 'HH:mm:ss.SSS')}
</div> </div>
</button> </button>
); );

View File

@@ -1,8 +1,12 @@
import xmlFormat from 'xml-formatter'; import xmlFormat from 'xml-formatter';
const INDENT = ' ';
export function tryFormatJson(text: string, pretty = true): string { export function tryFormatJson(text: string, pretty = true): string {
if (text === '') return text;
try { try {
if (pretty) return JSON.stringify(JSON.parse(text), null, 2); if (pretty) return JSON.stringify(JSON.parse(text), null, INDENT);
else return JSON.stringify(JSON.parse(text)); else return JSON.stringify(JSON.parse(text));
} catch (_) { } catch (_) {
return text; return text;
@@ -10,8 +14,10 @@ export function tryFormatJson(text: string, pretty = true): string {
} }
export function tryFormatXml(text: string): string { export function tryFormatXml(text: string): string {
if (text === '') return text;
try { try {
return xmlFormat(text, { throwOnFailure: true, strictMode: false }); return xmlFormat(text, { throwOnFailure: true, strictMode: false, indentation: INDENT });
} catch (_) { } catch (_) {
return text; return text;
} }

View File

@@ -133,7 +133,15 @@ export interface GrpcEvent extends BaseModel {
readonly connectionId: string; readonly connectionId: string;
readonly model: 'grpc_event'; readonly model: 'grpc_event';
content: string; content: string;
eventType: 'info' | 'error' | 'client_message' | 'server_message' | 'connection_response'; status: number | null;
error: string | null;
eventType:
| 'info'
| 'error'
| 'client_message'
| 'server_message'
| 'connection_start'
| 'connection_end';
metadata: Record<string, string>; metadata: Record<string, string>;
} }