Real-time response time

Closes https://feedback.yaak.app/p/real-time-display-of-request-execution-timer
This commit is contained in:
Gregory Schier
2025-04-17 14:16:10 -07:00
parent 73554078d1
commit 45fcea1954
4 changed files with 55 additions and 44 deletions

View File

@@ -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<R: Runtime>(
&*response.lock().await,
e.to_string(),
&update_source,
))
));
}
};
@@ -464,8 +464,10 @@ pub async fn send_http_request<R: Runtime>(
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<R: Runtime>(
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")
},

View File

@@ -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' && <LoadingIcon size="sm" />}
<HttpStatusTag showReason response={activeResponse} />
<span>&bull;</span>
<DurationTag
headers={activeResponse.elapsedHeaders}
total={activeResponse.elapsed}
<HttpResponseDurationTag
response={activeResponse}
/>
<span>&bull;</span>
<SizeTag contentLength={activeResponse.contentLength ?? 0} />

View File

@@ -1,33 +0,0 @@
interface Props {
total: number;
headers: number;
}
export function DurationTag({ total, headers }: Props) {
return (
<span
className="font-mono"
title={`HEADER: ${formatMillis(headers)}\nTOTAL: ${formatMillis(total)}`}
>
{formatMillis(total)}
</span>
);
}
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}`;
}

View File

@@ -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<number>(0);
const timeout = useRef<NodeJS.Timeout>();
// 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 (
<span className="font-mono" title={title}>
{formatMillis(response.elapsed || fallbackDuration)}
</span>
);
}
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`;
}
}