Skip to content

Commit

Permalink
Updates the style and refactors code to be a little cleaner for
Browse files Browse the repository at this point in the history
data/state view

Expandable fields, ensures they all use the same classes, adds some
left-borders + highlighting
  • Loading branch information
elijahbenizzy committed Apr 1, 2024
1 parent 58a3b8c commit 463186e
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 132 deletions.
7 changes: 4 additions & 3 deletions telemetry/ui/src/components/routes/app/ActionView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ import { base16AteliersulphurpoolLight } from 'react-syntax-highlighter/dist/esm
*/
export const CodeView = (props: { code: string }) => {
return (
<div className="h-full w-full pt-2 gap-2 flex flex-col max-w-full">
<div className="h-full w-full pt-2 gap-2 flex flex-col max-w-full overflow-y-auto">
<SyntaxHighlighter
language="python"
className="bg-dwdarkblue/100"
className="bg-dwdarkblue/100 hide-scrollbar"
wrapLines={true}
wrapLongLines={true}
style={base16AteliersulphurpoolLight}>
style={base16AteliersulphurpoolLight}
>
{props.code}
</SyntaxHighlighter>
</div>
Expand Down
305 changes: 177 additions & 128 deletions telemetry/ui/src/components/routes/app/DataView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import React, { useState } from 'react';
import { Step } from '../../../api';
import JsonView from '@uiw/react-json-view';
import { Button } from '../../common/button';
import { Switch, SwitchField } from '../../common/switch';
import { Label } from '../../common/fieldset';
import { classNames } from '../../../utils/tailwind';
import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/20/solid';

const StateButton = (props: { label: string; selected: boolean; setSelected: () => void }) => {
const color = props.selected ? 'zinc' : 'light';
Expand All @@ -13,46 +17,45 @@ const StateButton = (props: { label: string; selected: boolean; setSelected: ()
};

export const ErrorView = (props: { error: string }) => {
return <pre className="text-dwred rounded-sm p-2 text-wrap text-xs">{props.error}</pre>;
return (
<>
<pre className="text-dwred rounded-sm p-2 text-wrap text-xs">{props.error}</pre>
</>
);
};
export const DataView = (props: { currentStep: Step | undefined; priorStep: Step | undefined }) => {
const [whichState, setWhichState] = useState<'after' | 'before'>('after');
const stepToExamine = whichState === 'after' ? props.currentStep : props.priorStep;
const stateData = stepToExamine?.step_end_log?.state;
const resultData = stepToExamine?.step_end_log?.result;
const resultData = stepToExamine?.step_end_log?.result || undefined;
const inputs = stepToExamine?.step_start_log?.inputs;
const error = props.currentStep?.step_end_log?.exception;
const [viewRawData, setViewRawData] = useState<'raw' | 'render'>('render');

return (
<div className="h-full pl-3 pt-2 flex flex-col gap-2">
<div className="flex flex-row justify-between">
<h1 className="text-2xl text-gray-600 font-semibold">State</h1>
<div className="flex flex-row justify-end gap-2 pr-2">
<div className="pl-0 flex flex-col gap-2 hide-scrollbar">
<div className="flex flex-row justify-between sticky top-0 z-20 bg-white">
<h1 className="text-2xl text-gray-600 font-semibold pt-0">State</h1>
<div className="flex flex-row justify-end gap-2 pr-2 ">
<SwitchField>
<Switch
name="test"
checked={viewRawData === 'raw'}
onChange={(checked) => {
setViewRawData(checked ? 'raw' : 'render');
}}
></Switch>
<Label className="-mx-2">Raw</Label>
</SwitchField>

{stateData !== undefined && (
<>
<StateButton
label="raw"
selected={viewRawData === 'raw'}
setSelected={() => {
setViewRawData('raw');
}}
/>
<StateButton
label="render"
selected={viewRawData === 'render'}
setSelected={() => {
setViewRawData('render');
}}
/>
<StateButton
label="after"
selected={whichState === 'after'}
setSelected={() => {
setWhichState('after');
}}
/>
</>
<StateButton
label="after"
selected={whichState === 'after'}
setSelected={() => {
setWhichState('after');
}}
/>
)}

{
Expand All @@ -67,136 +70,182 @@ export const DataView = (props: { currentStep: Step | undefined; priorStep: Step
</div>
</div>

{stateData !== undefined && viewRawData === 'render' && (
// <JsonView value={stateData} collapsed={2} enableClipboard={false} />
<FormRenderer data={stateData} />
)}
{stateData !== undefined && viewRawData === 'raw' && (
<JsonView value={stateData} collapsed={2} enableClipboard={false} />
)}
<StateView stateData={stateData} viewRawData={viewRawData} />
{error && (
<>
<h1 className="text-2xl text-gray-600 font-semibold">Error</h1>
<ErrorView error={error} />
</>
)}
{resultData && Object.keys(resultData).length > 0 && (
<>
<h1 className="text-2xl text-gray-600 font-semibold sticky top-8 bg-white">Result</h1>
<ResultView resultData={resultData} viewRawData={viewRawData} />
</>
)}
{inputs && Object.keys(inputs).length > 0 && (
<>
<h1 className="text-2xl text-gray-600 font-semibold sticky top-8 bg-white">Input</h1>
<InputsView inputs={inputs || {}} />
</>
)}
</div>
);
};

export const StateView = (props: {
stateData: DataType | undefined;
viewRawData: 'render' | 'raw';
}) => {
const { stateData, viewRawData } = props;
return (
<>
{stateData !== undefined && viewRawData === 'render' && <FormRenderer data={stateData} />}
{stateData !== undefined && viewRawData === 'raw' && (
<JsonView value={stateData} collapsed={2} enableClipboard={false} />
)}
</>
);
};

export const ResultView = (props: {
resultData: DataType | undefined;
viewRawData: 'render' | 'raw';
}) => {
const { resultData, viewRawData } = props;
return (
<>
{resultData && viewRawData === 'render' && (
<>
<h1 className="text-2xl text-gray-600 font-semibold">Result</h1>
<FormRenderer data={resultData} />
</>
)}
{resultData && viewRawData === 'raw' && (
<>
<h1 className="text-2xl text-gray-600 font-semibold">Result</h1>
<JsonView value={resultData} collapsed={2} enableClipboard={false} />
</>
)}
{Object.keys(inputs || {}).length > 0 && (
<>
<h1 className="text-2xl text-gray-600 font-semibold">Inputs</h1>
<JsonView value={inputs} collapsed={2} enableClipboard={false} />
</>
)}
</div>
</>
);
};

interface StringComponentProps {
name: string;
value: string;
level: number;
}
// String component is used to render string data
const StringComponent: React.FC<StringComponentProps> = ({ name, value, level }) => {
const renderField = (name: string, value: string, level: number) => {
return (
<div key={name + '-' + String(level)} className="border">
<label className="border text-xl font-semibold">{name}</label>
<br />
<pre
className=""
style={{ whiteSpace: 'pre-wrap', wordWrap: 'break-word', maxWidth: '1000px' }}
>
{value}
</pre>
</div>
);
};
return <div>{renderField(name, value, level)}</div>;
export const InputsView = (props: { inputs: object }) => {
const { inputs } = props;
return <FormRenderer data={inputs as DataType} />;
};

type DataType = Record<string, string | number | boolean | object>;

interface FormRendererProps {
data: Record<string, string | number | boolean | object>;
}

// This component is used to render the form data in a structured way
const FormRenderer: React.FC<FormRendererProps> = ({ data }) => {
const renderField = (value: string | number | boolean | object, key: string, level: number) => {
// TODO: have max level depth.
if (typeof value === 'string') {
return <StringComponent name={key} value={value} level={level} />;
} else if (Array.isArray(value)) {
return (
<div key={key + '-' + String(level)} className="border">
<label className="border text-xl font-semibold">{key}</label>
<br />
<div>
{value.map((v, i) => {
return (
<div key={key + '-' + i.toString()} className="border-2">
{renderField(v, key + '-' + i.toString(), level + 1)}
</div>
);
})}
const Header = (props: {
name: string;
isExpanded: boolean;
setExpanded: (expanded: boolean) => void;
}) => {
const MinimizeMaximizeIcon = props.isExpanded ? ChevronUpIcon : ChevronDownIcon;

return (
<div className="flex flex-row gap-1 z-10 pb-2 items-center">
<h1 className="text-lg text-gray-500 font-semibold text-under">{props.name}</h1>
<MinimizeMaximizeIcon
className={classNames(
'text-gray-500',
'h-7 w-7 hover:bg-gray-50 rounded-md hover:cursor-pointer hover:scale-105'
)}
aria-hidden="true"
onClick={() => {
props.setExpanded(!props.isExpanded);
}}
/>
</div>
);
};
const RenderedField = (props: {
value: string | number | boolean | object;
keyName: string;
level: number;
}) => {
const [isExpanded, setExpanded] = useState(true);
// TODO: have max level depth.
const { value, keyName: key, level } = props;
const bodyClassNames =
'border-gray-100 border-l-[8px] pl-1 hover:bg-gray-100 text-sm text-gray-700';
if (key.startsWith('__')) {
return null;
}
return (
<>
<Header name={key} isExpanded={isExpanded} setExpanded={setExpanded} />
{isExpanded &&
(typeof value === 'string' ? (
<div key={key + '-' + String(level)}>
<pre
className={bodyClassNames}
style={{ whiteSpace: 'pre-wrap', wordWrap: 'break-word', maxWidth: '1000px' }}
>
{value}
</pre>
</div>
</div>
);
} else if (typeof value === 'object') {
return (
<div key={key} className="border">
<label className="border text-xl font-semibold">{key}</label>
<br />
<div>
{value === null ? (
<span>NULL</span>
) : (
Object.entries(value).map(([k, v]) => {
) : Array.isArray(value) ? (
<div key={key + String(level)}>
<div>
{value.map((v, i) => {
return (
<div key={key + '-' + k} className="border-2">
{renderField(v, k, level + 1)}
<div key={key + '-' + i.toString()} className={bodyClassNames}>
<RenderedField
value={v}
keyName={key + '[' + i.toString() + ']'}
level={level + 1}
/>
</div>
);
})
)}
})}
</div>
</div>
</div>
);
} else if (value === null) {
<div key={key + '-' + String(level)} className="border">
<label className="border text-xl font-semibold">{key}</label>
<br />
<span>NULL</span>
</div>;
} else {
return (
<div key={key + '-' + String(level)} className="border">
<label className="border text-xl font-semibold">{key}</label>
<br />
<span>{value.toString()}</span>
</div>
);
}
};

const renderFields = () => {
if (data !== null) {
return Object.entries(data).map(([key, value]) => renderField(value, key, 0));
}
return null;
};
) : typeof value === 'object' ? (
<div key={key}>
<div>
{value === null ? (
<span>NULL</span>
) : (
Object.entries(value).map(([k, v]) => {
return (
<div key={key + '-' + k} className={bodyClassNames}>
<RenderedField value={v} keyName={k} level={level + 1} />
</div>
);
})
)}
</div>
</div>
) : value === null ? (
<div key={key + '-' + String(level)}>
<pre className={bodyClassNames}>NULL</pre>
</div>
) : (
<div key={key + '-' + String(level)} className="">
<pre>{value.toString()}</pre>
</div>
))}
</>
);
};

return <div>{renderFields()}</div>;
// This component is used to render the form data in a structured way
const FormRenderer: React.FC<FormRendererProps> = ({ data }) => {
if (data !== null) {
return (
<>
{Object.entries(data).map(([key, value]) => {
return <RenderedField keyName={key} value={value} level={0} key={key} />;
})}
</>
);
}
return null;
};

export default FormRenderer;
2 changes: 1 addition & 1 deletion telemetry/ui/src/components/routes/app/StateMachine.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const AppStateView = (props: {
return (
<>
<Tabs tabs={tabs} currentTab={currentTab} setCurrentTab={setCurrentTab} />
<div className="px-10 h-full w-full overflow-y-auto">
<div className="px-4 h-full w-full hide-scrollbar overflow-y-auto">
{currentTab === 'graph' && (
<GraphView
stateMachine={props.stateMachine}
Expand Down

0 comments on commit 463186e

Please sign in to comment.