Request Timings UI update
The notification banner has been updated (position and style). so it doesn't overlap on Network Panel drawer.
This commit is contained in:
parent
b9e6bd6e72
commit
cbb930379d
5 changed files with 119 additions and 40 deletions
|
|
@ -137,6 +137,13 @@ function SubHeader(props) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<WarnBadge
|
||||||
|
siteId={projectId!}
|
||||||
|
currentLocation={currentLocation}
|
||||||
|
version={currentSession?.trackerVersion ?? ''}
|
||||||
|
containerStyle={{ position: 'relative', left: 0, top: 0, transform: 'none', zIndex: 10 }}
|
||||||
|
trackerWarnStyle={{ backgroundColor: '#fffbeb' }}
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
className="w-full px-4 flex items-center border-b relative"
|
className="w-full px-4 flex items-center border-b relative"
|
||||||
style={{
|
style={{
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||||
import { Alert } from 'antd';
|
import { Alert } from 'antd';
|
||||||
import { Icon } from 'UI';
|
import { Icon } from 'UI';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { ArrowUpRight, X } from 'lucide-react';
|
||||||
|
|
||||||
const localhostWarn = (project: string) => `${project}_localhost_warn`;
|
const localhostWarn = (project: string) => `${project}_localhost_warn`;
|
||||||
|
|
||||||
|
|
@ -29,11 +30,27 @@ function compareVersions(
|
||||||
return VersionComparison.Same;
|
return VersionComparison.Same;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New optional override props added in WarnBadgeExtraProps
|
||||||
|
interface WarnBadgeExtraProps {
|
||||||
|
containerStyle?: React.CSSProperties;
|
||||||
|
containerClassName?: string;
|
||||||
|
localhostWarnStyle?: React.CSSProperties;
|
||||||
|
localhostWarnClassName?: string;
|
||||||
|
trackerWarnStyle?: React.CSSProperties;
|
||||||
|
trackerWarnClassName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
const WarnBadge = React.memo(
|
const WarnBadge = React.memo(
|
||||||
({
|
({
|
||||||
currentLocation,
|
currentLocation,
|
||||||
version,
|
version,
|
||||||
siteId,
|
siteId,
|
||||||
|
containerStyle,
|
||||||
|
containerClassName,
|
||||||
|
localhostWarnStyle,
|
||||||
|
localhostWarnClassName,
|
||||||
|
trackerWarnStyle,
|
||||||
|
trackerWarnClassName,
|
||||||
virtualElsFailed,
|
virtualElsFailed,
|
||||||
onVMode,
|
onVMode,
|
||||||
}: {
|
}: {
|
||||||
|
|
@ -42,7 +59,7 @@ const WarnBadge = React.memo(
|
||||||
siteId: string;
|
siteId: string;
|
||||||
virtualElsFailed: boolean;
|
virtualElsFailed: boolean;
|
||||||
onVMode: () => void;
|
onVMode: () => void;
|
||||||
}) => {
|
} & WarnBadgeExtraProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const localhostWarnSiteKey = localhostWarn(siteId);
|
const localhostWarnSiteKey = localhostWarn(siteId);
|
||||||
const defaultLocalhostWarn =
|
const defaultLocalhostWarn =
|
||||||
|
|
@ -75,20 +92,37 @@ const WarnBadge = React.memo(
|
||||||
|
|
||||||
if (!warnings.some(el => el === true)) return null;
|
if (!warnings.some(el => el === true)) return null;
|
||||||
|
|
||||||
|
// Default container styles and classes
|
||||||
|
const defaultContainerStyle: React.CSSProperties = {
|
||||||
|
zIndex: 999,
|
||||||
|
position: 'absolute',
|
||||||
|
left: '50%',
|
||||||
|
bottom: '0',
|
||||||
|
transform: 'translate(-50%, 80%)',
|
||||||
|
fontWeight: 500,
|
||||||
|
};
|
||||||
|
const defaultContainerClass = "flex flex-col gap-2";
|
||||||
|
const defaultWarnClass = "px-3 py-.5 border border-gray-lighter shadow-sm rounded bg-active-blue flex items-center justify-between";
|
||||||
|
|
||||||
|
// Merge defaults with any overrides
|
||||||
|
const mergedContainerStyle = { ...defaultContainerStyle, ...containerStyle };
|
||||||
|
const mergedContainerClassName = containerClassName
|
||||||
|
? defaultContainerClass + ' ' + containerClassName
|
||||||
|
: defaultContainerClass;
|
||||||
|
const mergedLocalhostWarnClassName = localhostWarnClassName
|
||||||
|
? defaultWarnClass + ' ' + localhostWarnClassName
|
||||||
|
: defaultWarnClass;
|
||||||
|
const mergedTrackerWarnClassName = trackerWarnClassName
|
||||||
|
? defaultWarnClass + ' ' + trackerWarnClassName
|
||||||
|
: defaultWarnClass;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex flex-col gap-2"
|
className={mergedContainerClassName}
|
||||||
style={{
|
style={mergedContainerStyle}
|
||||||
zIndex: 999,
|
|
||||||
position: 'absolute',
|
|
||||||
left: '50%',
|
|
||||||
bottom: '0',
|
|
||||||
transform: 'translate(-50%, 80%)',
|
|
||||||
fontWeight: 500,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{warnings[0] ? (
|
{warnings[0] ? (
|
||||||
<div className="px-3 py-1 border border-gray-lighter drop-shadow-md rounded bg-active-blue flex items-center justify-between">
|
<div className={mergedLocalhostWarnClassName} style={localhostWarnStyle}>
|
||||||
<div>
|
<div>
|
||||||
<span>{t('Some assets may load incorrectly on localhost.')}</span>
|
<span>{t('Some assets may load incorrectly on localhost.')}</span>
|
||||||
<a
|
<a
|
||||||
|
|
@ -110,25 +144,25 @@ const WarnBadge = React.memo(
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{warnings[1] ? (
|
{warnings[1] ? (
|
||||||
<div className="px-3 py-1 border border-gray-lighter drop-shadow-md rounded bg-active-blue flex items-center justify-between">
|
<div className={mergedTrackerWarnClassName} style={trackerWarnStyle}>
|
||||||
<div>
|
<div className='flex gap-x-2 flex-wrap'>
|
||||||
<div>
|
<div className='font-normal'>
|
||||||
{t('Tracker version')} ({version})
|
{t('Tracker version')} <span className='mx-1 font-semibold'>{version}</span>
|
||||||
{t('for this recording is')}{' '}
|
{t('for this recording is')}{' '}
|
||||||
{trackerVerDiff === VersionComparison.Lower
|
{trackerVerDiff === VersionComparison.Lower
|
||||||
? 'lower '
|
? 'lower '
|
||||||
: 'ahead of '}
|
: 'ahead of '}
|
||||||
{t('the current')} ({trackerVersion}) {t('version')}.
|
{t('the current')}<span className='mx-1 font-semibold'>{trackerVersion}</span>{t('version')}.
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className='flex gap-1 items-center font-normal'>
|
||||||
<span>{t('Some recording might display incorrectly.')}</span>
|
<span>{t('Some recording might display incorrectly.')}</span>
|
||||||
<a
|
<a
|
||||||
href="https://docs.openreplay.com/en/deployment/upgrade/#tracker-compatibility"
|
href="https://docs.openreplay.com/en/deployment/upgrade/#tracker-compatibility"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
className="link ml-1"
|
className="link ml-1 flex gap-1 items-center"
|
||||||
>
|
>
|
||||||
{t('Learn More')}
|
{t('Learn More')} <ArrowUpRight size={12} />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -148,11 +182,26 @@ const WarnBadge = React.memo(
|
||||||
<div className='link' onClick={onVMode}>{t('Enable')}</div>
|
<div className='link' onClick={onVMode}>{t('Enable')}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="py-1 ml-3 cursor-pointer"
|
||||||
|
onClick={() => closeWarning(1)}
|
||||||
|
>
|
||||||
|
<X size={18} strokeWidth={1.5} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{warnings[2] ? (
|
||||||
|
<div className="px-3 py-1 border border-gray-lighter drop-shadow-md rounded bg-active-blue flex items-center justify-between">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<div>{t('If you have issues displaying custom HTML elements (i.e when using LWC), consider turning on Virtual Mode.')}</div>
|
||||||
|
<div className='link' onClick={onVMode}>{t('Enable')}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="py-1 ml-3 cursor-pointer"
|
className="py-1 ml-3 cursor-pointer"
|
||||||
onClick={() => closeWarning(2)}
|
onClick={() => closeWarning(2)}
|
||||||
>
|
>
|
||||||
<Icon name="close" size={16} color="black" />
|
<X size={18} strokeWidth={1.5} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
|
||||||
|
|
@ -32,36 +32,36 @@ function FetchTimings({ timings }: { timings: Record<string, number> }) {
|
||||||
{
|
{
|
||||||
key: 'queueing',
|
key: 'queueing',
|
||||||
name: 'Queueing',
|
name: 'Queueing',
|
||||||
color: 'bg-[#99a1af]',
|
color: 'bg-transparent border border-[#666]',
|
||||||
description: 'Time spent in browser queue before connection start',
|
description: 'Time spent in browser queue before connection start',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 'stalled',
|
|
||||||
name: 'Stalled',
|
|
||||||
color: 'bg-[#c3c3c3]',
|
|
||||||
description: 'Time request was stalled after connection start',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
category: 'Connection Start',
|
category: 'Connection Start',
|
||||||
children: [
|
children: [
|
||||||
|
{
|
||||||
|
key: 'stalled',
|
||||||
|
name: 'Stalled',
|
||||||
|
color: 'bg-[#c3c3c3]',
|
||||||
|
description: 'Time request was stalled after connection start',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'dnsLookup',
|
key: 'dnsLookup',
|
||||||
name: 'DNS Lookup',
|
name: 'DNS Lookup',
|
||||||
color: 'bg-[#00c951]',
|
color: 'bg-[#12546C]',
|
||||||
description: 'Time spent resolving the DNS',
|
description: 'Time spent resolving the DNS',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'initialConnection',
|
key: 'initialConnection',
|
||||||
name: 'Initial Connection',
|
name: 'Initial Connection',
|
||||||
color: 'bg-[#efb100]',
|
color: 'bg-[#DD4F18]',
|
||||||
description: 'Time establishing connection (TCP handshakes/retries)',
|
description: 'Time establishing connection (TCP handshakes/retries)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'ssl',
|
key: 'ssl',
|
||||||
name: 'SSL',
|
name: 'SSL',
|
||||||
color: 'bg-[#ad46ff]',
|
color: 'bg-[#C079FF]',
|
||||||
description: 'Time spent completing SSL/TLS handshake',
|
description: 'Time spent completing SSL/TLS handshake',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
@ -72,13 +72,13 @@ function FetchTimings({ timings }: { timings: Record<string, number> }) {
|
||||||
{
|
{
|
||||||
key: 'ttfb',
|
key: 'ttfb',
|
||||||
name: 'Request & TTFB',
|
name: 'Request & TTFB',
|
||||||
color: 'bg-[#2b7fff]',
|
color: 'bg-[#3CB347]',
|
||||||
description: 'Time waiting for first byte (server response time)',
|
description: 'Time waiting for first byte (server response time)',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'contentDownload',
|
key: 'contentDownload',
|
||||||
name: 'Content Download',
|
name: 'Content Download',
|
||||||
color: 'bg-[#00a63e]',
|
color: 'bg-[#3E78F7]',
|
||||||
description: 'Time spent receiving the response data',
|
description: 'Time spent receiving the response data',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
@ -116,27 +116,27 @@ function FetchTimings({ timings }: { timings: Record<string, number> }) {
|
||||||
const timelineData = React.useMemo(() => calculateTimelines(), [total]);
|
const timelineData = React.useMemo(() => calculateTimelines(), [total]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full bg-white rounded-lg shadow-sm p-4 font-sans">
|
<div className="w-full py-4 font-sans">
|
||||||
<div>
|
<div>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{timelineData.map((cat, index) => (
|
{timelineData.map((cat, index) => (
|
||||||
<div>
|
<div>
|
||||||
<div>{cat.category}</div>
|
<div className='text-neutral-500'>{cat.category}</div>
|
||||||
<div>
|
<div>
|
||||||
{cat.children.map((phase, index) => (
|
{cat.children.map((phase, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className="grid grid-cols-12 items-center gap-2 space-y-2"
|
className="grid grid-cols-12 items-center gap-2 space-y-2"
|
||||||
>
|
>
|
||||||
<div className="col-span-4 text-sm text-gray-dark font-medium flex items-center gap-2">
|
<div className="col-span-4 text-sm text-neutral-950 font-medium flex items-center gap-2">
|
||||||
<Tooltip title={phase.description}>
|
<Tooltip title={phase.description}>
|
||||||
<HelpCircle size={12} />
|
<HelpCircle size={12} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<span>{phase.name}:</span>
|
<span>{phase.name}:</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-span-6 relative">
|
<div className="col-span-7 relative">
|
||||||
<div className="h-5 bg-gray-lightest rounded overflow-hidden">
|
<div className="h-4 bg-neutral-50 overflow-hidden">
|
||||||
{phase.width > 0 && (
|
{phase.width > 0 && (
|
||||||
<div
|
<div
|
||||||
className={`absolute top-0 h-full ${phase.color} hover:opacity-80 transition-opacity`}
|
className={`absolute top-0 h-full ${phase.color} hover:opacity-80 transition-opacity`}
|
||||||
|
|
@ -150,7 +150,7 @@ function FetchTimings({ timings }: { timings: Record<string, number> }) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="col-span-2 text-right font-mono text-sm text-gray-dark">
|
<div className="col-span-1 text-right font-mono text-sm text-gray-dark">
|
||||||
{formatTime(phase.duration)}
|
{formatTime(phase.duration)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -160,11 +160,11 @@ function FetchTimings({ timings }: { timings: Record<string, number> }) {
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<div className="grid grid-cols-12 items-center gap-2 pt-2 border-t border-t-gray-light mt-2">
|
<div className="grid grid-cols-12 items-center gap-2 pt-2 border-t border-t-gray-light mt-2">
|
||||||
<div className="col-span-3 text-sm text-gray-dark font-semibold">
|
<div className="col-span-3 text-sm text-neutral-950 font-semibold">
|
||||||
Total:
|
Total:
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-7"></div>
|
<div className="col-span-7"></div>
|
||||||
<div className="col-span-2 text-right font-mono text-sm text-gray-dark font-semibold">
|
<div className="col-span-2 text-right font-mono text-sm text-neutral-950 font-semibold">
|
||||||
{formatTime(total)}{' '}
|
{formatTime(total)}{' '}
|
||||||
{isAdjusted ? (
|
{isAdjusted ? (
|
||||||
<span className="ml-1 text-xs text-yellow">
|
<span className="ml-1 text-xs text-yellow">
|
||||||
|
|
|
||||||
15
frontend/devbox.json
Normal file
15
frontend/devbox.json
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.14.0/.schema/devbox.schema.json",
|
||||||
|
"packages": [],
|
||||||
|
"shell": {
|
||||||
|
"init_hook": [
|
||||||
|
"echo 'Welcome to devbox!' > /dev/null"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"test": [
|
||||||
|
"echo \"Error: no test specified\" && exit 1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
8
frontend/devbox.lock
Normal file
8
frontend/devbox.lock
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"lockfile_version": "1",
|
||||||
|
"packages": {
|
||||||
|
"github:NixOS/nixpkgs/nixpkgs-unstable": {
|
||||||
|
"resolved": "github:NixOS/nixpkgs/18dd725c29603f582cf1900e0d25f9f1063dbf11?lastModified=1744536153&narHash=sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg%2FN38%3D"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue