Skip to content

Commit

Permalink
Update trace list style and theme (#148)
Browse files Browse the repository at this point in the history
  • Loading branch information
carbonrobot authored Oct 24, 2023
1 parent a880840 commit c7aa8b0
Show file tree
Hide file tree
Showing 11 changed files with 122 additions and 140 deletions.
2 changes: 1 addition & 1 deletion packages/webui/src/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function Button({ onClick, className, children, ...props }: ButtonProps, ref: Re
<button
ref={finalRef}
className={tw(
'flex gap-2 items-center p-2 text-sm text-secondary border border-solid border-primary rounded-md shadow-sm hover:bg-gray-50',
'flex gap-2 items-center py-1.5 px-3 text-secondary bg-primary border border-solid border-primary rounded-md shadow-sm hover:bg-gray-50',
className,
)}
onClick={handleClick}
Expand Down
9 changes: 1 addition & 8 deletions packages/webui/src/components/DarkModeToggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,8 @@ export default function DarkModeToggle() {

return (
<div className="flex flex-col justify-center">
<input
type="checkbox"
name="light-switch"
className="light-switch sr-only"
checked={useDarkMode}
onChange={handleCheckboxChange}
/>
<label
className="relative cursor-pointer p-2.5 ring-1 ring-inset ring-primary text-secondary rounded-md shadow-sm"
className="relative cursor-pointer p-2.5 ring-1 ring-inset ring-primary text-secondary rounded-md shadow-sm bg-primary"
htmlFor="light-switch"
onClick={handleCheckboxChange}
role="toggle"
Expand Down
2 changes: 1 addition & 1 deletion packages/webui/src/components/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ function Menu({ Icon, label, items, className, focusKey, ...props }: MenuProps,
<div ref={finalRef} className={tw('relative', className)} {...props}>
<IconButton
role="menu"
className={tw('w-32', isOpen && 'bg-neutral hover:shadow-none')}
className={tw('', isOpen && 'bg-neutral hover:shadow-none')}
Icon={Icon}
onClick={() => setIsOpen(curr => !curr)}
>
Expand Down
2 changes: 1 addition & 1 deletion packages/webui/src/components/ui/ConnectionStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default function ConnectionStatus() {
return <Loading size={4} className="flex items-center" />;
}

const containerClasses = 'flex items-center mt-1 text-gray-600 text-xs uppercase';
const containerClasses = 'flex items-center text-gray-600 text-sm uppercase font-semibold';

if (connected) {
return (
Expand Down
4 changes: 2 additions & 2 deletions packages/webui/src/components/ui/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ export default function Header() {
const isDebugMode = process.env.NODE_ENV !== 'production';

return (
<header className="shadow p-3">
<header className="shadow p-3 bg-secondary">
<div className="flex justify-between">
<div className="flex items-center gap-2">
<div>
<Logo />
</div>
<div className="text-xl font-extrabold mr-4">ENVY</div>
<div className="hidden lg:block">
<div className="hidden md:block">
<ConnectionStatus />
</div>
</div>
Expand Down
54 changes: 14 additions & 40 deletions packages/webui/src/components/ui/TraceList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,13 @@ describe('TraceList', () => {

describe('trace row colours', () => {
const scenarios = [
{ statusCode: 500, bgColor: 'purple-500', borderColor: 'purple-500' },
{ statusCode: 404, bgColor: 'red-500', borderColor: 'red-500' },
{ statusCode: 300, bgColor: 'yellow-500', borderColor: 'yellow-500' },
{ statusCode: 200, bgColor: null, borderColor: 'green-500' },
{ statusCode: 500, bgColor: 'purple-500' },
{ statusCode: 404, bgColor: 'red-500' },
{ statusCode: 300, bgColor: 'yellow-500' },
{ statusCode: 200, bgColor: 'green-500' },
];

it.each(scenarios)('should have $bgColor background for HTTP $statusCode responses', ({ statusCode, bgColor }) => {
it.each(scenarios)('should have $bgColor left border for HTTP $statusCode responses', ({ statusCode, bgColor }) => {
setUpTraces([
{
id: '1',
Expand All @@ -157,42 +157,16 @@ describe('TraceList', () => {

const { getByTestId } = render(<TraceList />);
const traceRow = getByTestId('trace');
const methodData = within(traceRow).getByTestId('column-data-method-cell');

if (bgColor) {
expect(traceRow).toHaveClass(`bg-${bgColor}`);
expect(traceRow).not.toHaveClass('bg-slate-100');
expect(methodData).toHaveClass(`bg-${bgColor}`);
expect(methodData).not.toHaveClass(`border-neutral`);
} else {
expect(traceRow).toHaveClass('bg-slate-100');
expect(methodData).toHaveClass('bg-gray-100');
}
});

it.each(scenarios)(
'should have $borderColor left border for HTTP $statusCode responses',
({ statusCode, borderColor }) => {
setUpTraces([
{
id: '1',
timestamp: 0,
http: {
method: 'GET',
statusCode,
} as Trace['http'],
},
]);

const { getByTestId } = render(<TraceList />);
const traceRow = getByTestId('trace');
const methodData = within(traceRow).getByTestId('column-data-method');

if (borderColor) {
expect(methodData).toHaveClass(`border-${borderColor}`);
expect(methodData).not.toHaveClass(`border-neutral`);
} else {
expect(methodData).toHaveClass('bg-slate-100');
}
},
);

it('should render selected trace row correctly', () => {
const trace = {
id: '1',
Expand All @@ -209,7 +183,7 @@ describe('TraceList', () => {
const { getByTestId } = render(<TraceList />);
const traceRow = getByTestId('trace');

expect(traceRow).toHaveClass('bg-orange-300');
expect(traceRow).toHaveClass('bg-green-100');
});
});

Expand Down Expand Up @@ -308,7 +282,7 @@ describe('TraceList', () => {

const { getByTestId } = render(<TraceList />);
const traceRow = getByTestId('trace');
const methodData = within(traceRow).getByTestId('column-data-time');
const methodData = within(traceRow).getByTestId('column-data-duration');

expect(methodData).toHaveTextContent('Mock Loading component');
});
Expand All @@ -327,7 +301,7 @@ describe('TraceList', () => {

const { getByTestId } = render(<TraceList />);
const traceRow = getByTestId('trace');
const methodData = within(traceRow).getByTestId('column-data-time');
const methodData = within(traceRow).getByTestId('column-data-duration');

expect(methodData).toHaveTextContent('1.23s');
});
Expand All @@ -346,7 +320,7 @@ describe('TraceList', () => {

const { getByTestId } = render(<TraceList />);
const traceRow = getByTestId('trace');
const methodData = within(traceRow).getByTestId('column-data-time');
const methodData = within(traceRow).getByTestId('column-data-duration');

expect(methodData).toHaveTextContent('1.24s');
});
Expand Down Expand Up @@ -403,7 +377,7 @@ describe('TraceList', () => {

const { getByTestId } = render(<TraceList />);
const traceRow = getByTestId('trace');
const methodData = within(traceRow).getByTestId('column-data-method');
const methodData = within(traceRow).getByTestId('column-data-method-cell');

expect(methodData).toBeEmptyDOMElement();
});
Expand Down
105 changes: 44 additions & 61 deletions packages/webui/src/components/ui/TraceList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,44 @@ type MethodAndStatusProps = {
function MethodAndStatus({ method, statusCode }: MethodAndStatusProps) {
return (
<>
<span className="block">{method.toUpperCase()}</span>
<span className="block text-xs">{statusCode && statusCode > -1 ? statusCode : '-'}</span>
<span className="hidden md:inline-block">
{method}&nbsp;
{statusCode && statusCode > -1 ? statusCode : ''}
</span>
</>
);
}

function getMethodAndStatus(trace: Trace) {
return trace.http ? <MethodAndStatus method={trace.http.method} statusCode={trace.http.statusCode} /> : null;
}

function pillStyle(trace: Trace) {
const statusCode = trace.http?.statusCode;

let color = '';
if (!statusCode) color = 'ring-1 ring-gray-400';
else if (statusCode >= 500) color = 'bg-purple-500';
else if (statusCode >= 400) color = 'bg-red-500';
else if (statusCode >= 300) color = 'bg-yellow-500';
else if (statusCode >= 200) color = 'bg-green-500';
return `w-full text-sm rounded-full px-2 py-1.5 font-semibold ${color}`;
}

function getRequestURI(trace: Trace) {
return <ListDataComponent trace={trace} />;
}

function getRequestDuration(trace: Trace) {
return trace.http?.duration ? `${(trace.http.duration / 1000).toFixed(2)}s` : <Loading size={2} />;
}

const columns: [string, (x: Trace) => string | number | React.ReactNode, string, (x: Trace) => string][] = [
['Method', getMethodAndStatus, 'w-[40px] md:w-[125px] overflow-hidden text-center', pillStyle],
['Request', getRequestURI, '', () => 'whitespace-nowrap overflow-hidden overflow-ellipsis'],
['Duration', getRequestDuration, 'w-[100px] text-right', () => 'text-sm'],
];

type TraceListProps = React.HTMLAttributes<HTMLElement> & {
autoScroll?: boolean;
};
Expand Down Expand Up @@ -57,49 +89,6 @@ export default function TraceList({ autoScroll: initialAutoScroll = true }: Trac
setAutoScroll(scrollTop >= maxScrollTop);
}

function getMethodAndStatus(trace: Trace) {
return trace.http ? <MethodAndStatus method={trace.http.method} statusCode={trace.http.statusCode} /> : null;
}

function rowStyle(trace: Trace) {
const statusCode = trace.http?.statusCode;

let color = '';
if (!statusCode) color = '';
else if (statusCode >= 500) color = 'bg-purple-500';
else if (statusCode >= 400) color = 'bg-red-500';
else if (statusCode >= 300) color = 'bg-yellow-500';
else if (statusCode >= 200) color = '';

return color ? `bg-opacity-20 ${color}` : '';
}

function cellStyle(trace: Trace) {
const statusCode = trace.http?.statusCode;

let color = 'border-transparent';
if (!statusCode) color = 'border-transparent';
else if (statusCode >= 500) color = 'border-purple-500';
else if (statusCode >= 400) color = 'border-red-500';
else if (statusCode >= 300) color = 'border-yellow-500';
else if (statusCode >= 200) color = 'border-green-500';
return `border-0 border-l-8 ${color}`;
}

function getRequestURI(trace: Trace) {
return <ListDataComponent trace={trace} />;
}

function getRequestDuration(trace: Trace) {
return trace.http?.duration ? `${(trace.http.duration / 1000).toFixed(2)}s` : <Loading size={2} />;
}

const columns: [string, (x: Trace) => string | number | React.ReactNode, string, (x: Trace) => string][] = [
['Method', getMethodAndStatus, 'w-[50px] md:w-[100px]', cellStyle],
['Request', getRequestURI, '', () => ''],
['Time', getRequestDuration, 'w-[50px] md:w-[100px] text-center', () => ''],
];

const [Icon, message] = connected
? [HiStatusOnline, `Listening for traces...`]
: connecting
Expand All @@ -125,12 +114,12 @@ export default function TraceList({ autoScroll: initialAutoScroll = true }: Trac
</div>
) : (
<div data-test-id="trace-list" className="table table-fixed w-full relative">
<div className="flex-0 table-header-group gap-4 font-semibold sticky top-0 bg-secondary uppercase shadow-lg z-10">
<div className="table-header-group font-bold bg-primary sticky top-0 uppercase">
{columns.map(([label, , baseStyle]) => (
<div
key={label}
data-test-id={`column-heading-${label.toLowerCase()}`}
className={`table-cell p-cell border-b border-primary overflow-hidden ${baseStyle}`}
className={`table-cell p-cell border-b border-primary ${baseStyle}`}
>
{label}
</div>
Expand All @@ -143,24 +132,23 @@ export default function TraceList({ autoScroll: initialAutoScroll = true }: Trac
key={trace.id}
onClick={() => setSelectedTrace(trace.id)}
className={tw(
'gap-4 table-row',
trace.id === selectedTraceId
? 'bg-orange-300 shadow-lg'
: rowStyle(trace) || (idx % 2 === 0 ? 'bg-slate-100' : 'bg-slate-50'),
'hover:bg-orange-200 hover:cursor-pointer hover:shadow',
'table-row h-16',
trace.id === selectedTraceId ? 'bg-green-100' : idx % 2 === 0 ? 'bg-gray-200' : 'bg-gray-200',
'hover:bg-green-100 hover:cursor-pointer hover:shadow',
)}
>
{columns.map(([label, prop, baseStyle, cellStyle]) => (
<div
key={`${trace.id}_${prop}`}
data-test-id={`column-data-${label.toLowerCase()}`}
className={tw(
'table-cell p-cell align-middle overflow-hidden whitespace-nowrap text-ellipsis',
'table-cell p-cell align-middle border-b border-solid border-gray-300',
baseStyle || '',
cellStyle(trace) || '',
)}
>
{typeof prop === 'function' && prop(trace)}
<div className={cellStyle(trace) || ''} data-test-id={`column-data-${label.toLowerCase()}-cell`}>
{typeof prop === 'function' && prop(trace)}
</div>
</div>
))}
</div>
Expand All @@ -170,12 +158,7 @@ export default function TraceList({ autoScroll: initialAutoScroll = true }: Trac
)}
</div>
{hasTraces && (
<div
className={tw(
'flex flex-row items-center border-t border-primary p-2',
selectedTraceId && 'border-r border-primary',
)}
>
<div className="flex flex-row items-center border-t border-primary p-2">
<div data-test-id="trace-count" className="flex-1 font-semibold uppercase">
Traces: {data.length}
</div>
Expand Down
13 changes: 0 additions & 13 deletions packages/webui/src/components/ui/TraceRequestData.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,17 +121,4 @@ describe('TraceRequestData', () => {
expect(data.previousSibling).toBe(request);
});
});

describe('when a trace is selected', () => {
beforeEach(() => {
setUseApplicationData({ selectedTraceId: '1' });
});

it('should truncate the path to the last part given the reduced screen space for the item', () => {
const { getByTestId } = render(<TraceRequestData iconPath="icon.jpg" path="/foo/bar" />);

const path = getByTestId('item-path');
expect(path).toHaveTextContent('.../bar');
});
});
});
11 changes: 4 additions & 7 deletions packages/webui/src/components/ui/TraceRequestData.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import useApplication from '@/hooks/useApplication';

export type TraceRequestDataProps = {
iconPath: string;
hostName?: string;
Expand All @@ -8,19 +6,18 @@ export type TraceRequestDataProps = {
};

export default function TraceRequestData({ iconPath, hostName, path, data }: TraceRequestDataProps) {
const { selectedTraceId } = useApplication();
const pathValue = !!selectedTraceId ? `.../${path.split('/').splice(-1, 1).join('/')}` : path;

return (
<>
<span data-test-id="item-request" className="flex flex-row items-center">
<img data-test-id="item-image" src={iconPath} alt="" className="flex-0 inline-block w-4 h-4 mr-1.5" />
<img data-test-id="item-image" src={iconPath} alt="" className="inline-block w-4 h-4 mr-1.5" />
{hostName && (
<span data-test-id="item-hostname" className="font-semibold pr-1">
{hostName}
</span>
)}
<span data-test-id="item-path">{pathValue}</span>
<span data-test-id="item-path" className="flex-1">
{path}
</span>
</span>
{data && (
<span data-test-id="item-data" className="block text-xs font-mono text-secondary">
Expand Down
Loading

0 comments on commit c7aa8b0

Please sign in to comment.