Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds POC "render" view of state #117

Merged
merged 4 commits into from
Apr 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
207 changes: 191 additions & 16 deletions telemetry/ui/src/components/routes/app/DataView.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import React, { useState } from 'react';
import { Step } from '../../../api';
import JsonView from '@uiw/react-json-view';
import { Button } from '../../common/button';
import { useState } from 'react';
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,21 +17,37 @@ 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="after"
Expand All @@ -50,27 +70,182 @@ export const DataView = (props: { currentStep: Step | undefined; priorStep: Step
</div>
</div>

{stateData !== undefined && (
<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 && (
{resultData && Object.keys(resultData).length > 0 && (
<>
<h1 className="text-2xl text-gray-600 font-semibold">Result</h1>
<JsonView value={resultData} collapsed={2} enableClipboard={false} />
<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' && (
<>
<FormRenderer data={resultData} />
</>
)}
{Object.keys(inputs || {}).length > 0 && (
{resultData && viewRawData === 'raw' && (
<>
<h1 className="text-2xl text-gray-600 font-semibold">Inputs</h1>
<JsonView value={inputs} collapsed={2} enableClipboard={false} />
<JsonView value={resultData} collapsed={2} enableClipboard={false} />
</>
)}
</>
);
};

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>;
}

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>
) : Array.isArray(value) ? (
<div key={key + String(level)}>
<div>
{value.map((v, i) => {
return (
<div key={key + '-' + i.toString()} className={bodyClassNames}>
<RenderedField
value={v}
keyName={key + '[' + i.toString() + ']'}
level={level + 1}
/>
</div>
);
})}
</div>
</div>
) : 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>
))}
</>
);
};

// 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
Loading