From 45fcea1954f950177bddea7eb0b89cbd06e41070 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Thu, 17 Apr 2025 14:16:10 -0700 Subject: [PATCH] Real-time response time Closes https://feedback.yaak.app/p/real-time-display-of-request-execution-timer --- src-tauri/src/http_request.rs | 18 ++++---- src-web/components/HttpResponsePane.tsx | 7 ++-- src-web/components/core/DurationTag.tsx | 33 --------------- .../core/HttpResponseDurationTag.tsx | 41 +++++++++++++++++++ 4 files changed, 55 insertions(+), 44 deletions(-) delete mode 100644 src-web/components/core/DurationTag.tsx create mode 100644 src-web/components/core/HttpResponseDurationTag.tsx diff --git a/src-tauri/src/http_request.rs b/src-tauri/src/http_request.rs index e059e400..82b84898 100644 --- a/src-tauri/src/http_request.rs +++ b/src-tauri/src/http_request.rs @@ -7,10 +7,10 @@ use http::{HeaderMap, HeaderName, HeaderValue}; use log::{debug, error, warn}; use mime_guess::Mime; use reqwest::redirect::Policy; -use reqwest::{multipart, Proxy, Url}; use reqwest::{Method, Response}; -use rustls::crypto::ring; +use reqwest::{Proxy, Url, multipart}; use rustls::ClientConfig; +use rustls::crypto::ring; use rustls_platform_verifier::BuilderVerifierExt; use serde_json::Value; use std::collections::BTreeMap; @@ -20,10 +20,10 @@ use std::sync::Arc; use std::time::Duration; use tauri::{Manager, Runtime, WebviewWindow}; use tokio::fs; -use tokio::fs::{create_dir_all, File}; +use tokio::fs::{File, create_dir_all}; use tokio::io::AsyncWriteExt; use tokio::sync::watch::Receiver; -use tokio::sync::{oneshot, Mutex}; +use tokio::sync::{Mutex, oneshot}; use yaak_models::models::{ Cookie, CookieJar, Environment, HttpRequest, HttpResponse, HttpResponseHeader, HttpResponseState, ProxySetting, ProxySettingAuth, @@ -80,7 +80,7 @@ pub async fn send_http_request( &*response.lock().await, e.to_string(), &update_source, - )) + )); } }; @@ -464,8 +464,10 @@ pub async fn send_http_request( let raw_response = tokio::select! { Ok(r) = resp_rx => r, _ = cancelled_rx.changed() => { - debug!("Request cancelled"); - return Ok(response_err(&app_handle, &*response.lock().await, "Request was cancelled".to_string(), &update_source)); + let mut r = response.lock().await; + r.elapsed_headers = start.elapsed().as_millis() as i32; + r.elapsed = start.elapsed().as_millis() as i32; + return Ok(response_err(&app_handle, &r, "Request was cancelled".to_string(), &update_source)); } }; @@ -632,6 +634,8 @@ pub async fn send_http_request( match app_handle.with_db(|c| c.get_http_response(&response_id)) { Ok(mut r) => { r.state = HttpResponseState::Closed; + r.elapsed = start.elapsed().as_millis() as i32; + r.elapsed_headers = start.elapsed().as_millis() as i32; app_handle.db().update_http_response_if_id(&r, &UpdateSource::from_window(window)) .expect("Failed to update response") }, diff --git a/src-web/components/HttpResponsePane.tsx b/src-web/components/HttpResponsePane.tsx index ce6e2a6f..ae3c9379 100644 --- a/src-web/components/HttpResponsePane.tsx +++ b/src-web/components/HttpResponsePane.tsx @@ -9,7 +9,7 @@ import { getContentTypeFromHeaders } from '../lib/model_util'; import { ConfirmLargeResponse } from './ConfirmLargeResponse'; import { Banner } from './core/Banner'; import { CountBadge } from './core/CountBadge'; -import { DurationTag } from './core/DurationTag'; +import { HttpResponseDurationTag } from './core/HttpResponseDurationTag'; import { HotKeyList } from './core/HotKeyList'; import { LoadingIcon } from './core/LoadingIcon'; import { SizeTag } from './core/SizeTag'; @@ -123,9 +123,8 @@ export function HttpResponsePane({ style, className, activeRequestId }: Props) { {activeResponse.state !== 'closed' && } - diff --git a/src-web/components/core/DurationTag.tsx b/src-web/components/core/DurationTag.tsx deleted file mode 100644 index 863039aa..00000000 --- a/src-web/components/core/DurationTag.tsx +++ /dev/null @@ -1,33 +0,0 @@ -interface Props { - total: number; - headers: number; -} - -export function DurationTag({ total, headers }: Props) { - return ( - - {formatMillis(total)} - - ); -} - -function formatMillis(millis: number) { - let num; - let unit; - - if (millis > 1000 * 60) { - num = millis / 1000 / 60; - unit = 'min'; - } else if (millis > 1000) { - num = millis / 1000; - unit = 's'; - } else { - num = millis; - unit = 'ms'; - } - - return `${Math.round(num * 10) / 10} ${unit}`; -} diff --git a/src-web/components/core/HttpResponseDurationTag.tsx b/src-web/components/core/HttpResponseDurationTag.tsx new file mode 100644 index 00000000..4328c29f --- /dev/null +++ b/src-web/components/core/HttpResponseDurationTag.tsx @@ -0,0 +1,41 @@ +import type { HttpResponse } from '@yaakapp-internal/models'; +import { useEffect, useRef, useState } from 'react'; + +interface Props { + response: HttpResponse; +} + +export function HttpResponseDurationTag({ response }: Props) { + const [fallbackDuration, setFallbackDuration] = useState(0); + const timeout = useRef(); + + // Calculate the duration of the response for use when the response hasn't finished yet + useEffect(() => { + clearInterval(timeout.current); + timeout.current = setInterval(() => { + setFallbackDuration(Date.now() - new Date(response.createdAt + 'Z').getTime()); + }, 100); + return () => clearInterval(timeout.current); + }, [response.createdAt, response.elapsed, response.state]); + + const title = `HEADER: ${formatMillis(response.elapsedHeaders)}\nTOTAL: ${formatMillis(response.elapsed)}`; + + return ( + + {formatMillis(response.elapsed || fallbackDuration)} + + ); +} + +function formatMillis(ms: number) { + if (ms < 1000) { + return `${ms} ms`; + } else if (ms < 60_000) { + const seconds = (ms / 1000).toFixed(ms < 10_000 ? 1 : 0); + return `${seconds} s`; + } else { + const minutes = Math.floor(ms / 60_000); + const seconds = Math.round((ms % 60_000) / 1000); + return `${minutes}m ${seconds}s`; + } +}