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

Add collapse all feature #149

Merged
merged 3 commits into from
Apr 8, 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
142 changes: 119 additions & 23 deletions telemetry/ui/src/components/routes/app/DataView.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React, { useState } from 'react';
import React, { useEffect, 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';
import { ChevronDownIcon, ChevronUpIcon, MinusIcon, PlusIcon } from '@heroicons/react/20/solid';

const StateButton = (props: { label: string; selected: boolean; setSelected: () => void }) => {
const color = props.selected ? 'zinc' : 'light';
Expand All @@ -23,6 +23,36 @@ export const ErrorView = (props: { error: string }) => {
</>
);
};
/**
* Section header that allows for exapnsion/contraction of all subcomponents
*/
const SectionHeaderWithExpand = (props: {
name: string;
defaultExpanded?: boolean;
setDefaultExpanded?: (expanded: boolean) => void;
enableExpansion: boolean;
}) => {
const MinimizeMaximizeIcon = props.defaultExpanded ? MinusIcon : PlusIcon;
return (
<div className="flex flex-row items-center gap-1">
<h1 className="text-2xl text-gray-900 font-semibold">{props.name}</h1>
{props.enableExpansion && (
<MinimizeMaximizeIcon
className={classNames(
'text-gray-500',
'h-5 w-5 rounded-md hover:cursor-pointer hover:scale-105'
)}
aria-hidden="true"
onClick={() => {
if (props.setDefaultExpanded) {
props.setDefaultExpanded(!props.defaultExpanded);
}
}}
/>
)}
</div>
);
};
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;
Expand All @@ -32,10 +62,20 @@ export const DataView = (props: { currentStep: Step | undefined; priorStep: Step
const error = props.currentStep?.step_end_log?.exception;
const [viewRawData, setViewRawData] = useState<'raw' | 'render'>('render');

const [allStateExpanded, setAllStateExpanded] = useState(true);
const [allResultExpanded, setAllResultExpanded] = useState(true);
const [allInputExpanded, setAllInputExpanded] = useState(true);

return (
<div className="pl-1 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-900 font-semibold pt-2">State</h1>
{/* <h1 className="text-2xl text-gray-900 font-semibold pt-2">State</h1> */}
<SectionHeaderWithExpand
name="State"
defaultExpanded={allStateExpanded}
setDefaultExpanded={setAllStateExpanded}
enableExpansion={viewRawData === 'render'}
/>
<div className="flex flex-row justify-end gap-2 pr-2">
<SwitchField>
<Switch
Expand Down Expand Up @@ -70,7 +110,7 @@ export const DataView = (props: { currentStep: Step | undefined; priorStep: Step
</div>
</div>

<StateView stateData={stateData} viewRawData={viewRawData} />
<StateView stateData={stateData} viewRawData={viewRawData} isExpanded={allStateExpanded} />
{error && (
<>
<h1 className="text-2xl text-gray-900 font-semibold">Error</h1>
Expand All @@ -79,14 +119,29 @@ export const DataView = (props: { currentStep: Step | undefined; priorStep: Step
)}
{resultData && Object.keys(resultData).length > 0 && (
<>
<h1 className="text-2xl text-gray-900 font-semibold sticky top-8 bg-white">Result</h1>
<ResultView resultData={resultData} viewRawData={viewRawData} />
<SectionHeaderWithExpand
name="Result"
defaultExpanded={allResultExpanded}
setDefaultExpanded={setAllResultExpanded}
enableExpansion={viewRawData === 'render'}
/>
<ResultView
resultData={resultData}
viewRawData={viewRawData}
isExpanded={allResultExpanded}
/>
</>
)}
{inputs && Object.keys(inputs).length > 0 && (
<>
<h1 className="text-2xl text-gray-900 font-semibold sticky top-8 bg-white">Input</h1>
<InputsView inputs={inputs || {}} />
<SectionHeaderWithExpand
name="Inputs"
defaultExpanded={allInputExpanded}
setDefaultExpanded={setAllInputExpanded}
enableExpansion={viewRawData === 'render'}
/>
<InputsView inputs={inputs} isExpanded={allInputExpanded} viewRawData={viewRawData} />
{/* <FormRenderer data={inputs as DataType} isDefaultExpanded={allInputExpanded} /> */}
</>
)}
</div>
Expand All @@ -96,11 +151,14 @@ export const DataView = (props: { currentStep: Step | undefined; priorStep: Step
export const StateView = (props: {
stateData: DataType | undefined;
viewRawData: 'render' | 'raw';
isExpanded: boolean;
}) => {
const { stateData, viewRawData } = props;
const { stateData, viewRawData, isExpanded } = props;
return (
<>
{stateData !== undefined && viewRawData === 'render' && <FormRenderer data={stateData} />}
{stateData !== undefined && viewRawData === 'render' && (
<FormRenderer data={stateData} isDefaultExpanded={isExpanded} />
)}
{stateData !== undefined && viewRawData === 'raw' && (
<JsonView value={stateData} collapsed={2} enableClipboard={false} />
)}
Expand All @@ -111,13 +169,14 @@ export const StateView = (props: {
export const ResultView = (props: {
resultData: DataType | undefined;
viewRawData: 'render' | 'raw';
isExpanded: boolean;
}) => {
const { resultData, viewRawData } = props;
const { resultData, viewRawData, isExpanded } = props;
return (
<>
{resultData && viewRawData === 'render' && (
<>
<FormRenderer data={resultData} />
<FormRenderer data={resultData} isDefaultExpanded={isExpanded} />
</>
)}
{resultData && viewRawData === 'raw' && (
Expand All @@ -129,17 +188,31 @@ export const ResultView = (props: {
);
};

export const InputsView = (props: { inputs: object }) => {
const { inputs } = props;
return <FormRenderer data={inputs as DataType} />;
export const InputsView = (props: {
inputs: object;
isExpanded: boolean;
viewRawData: 'render' | 'raw';
}) => {
const { inputs, viewRawData, isExpanded } = props;
return (
<>
{inputs && viewRawData === 'render' ? (
<>
<FormRenderer data={inputs as DataType} isDefaultExpanded={isExpanded} />
</>
) : (
(inputs && viewRawData) === 'raw' && (
<>
<JsonView value={inputs} collapsed={2} enableClipboard={false} />
</>
)
)}
</>
);
};

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

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

const Header = (props: {
name: string;
isExpanded: boolean;
Expand All @@ -153,7 +226,7 @@ const Header = (props: {
<MinimizeMaximizeIcon
className={classNames(
'text-gray-500',
'h-7 w-7 hover:bg-gray-50 rounded-md hover:cursor-pointer hover:scale-105'
'h-6 w-6 hover:bg-gray-50 rounded-md hover:cursor-pointer hover:scale-105'
)}
aria-hidden="true"
onClick={() => {
Expand All @@ -167,8 +240,12 @@ const RenderedField = (props: {
value: string | number | boolean | object;
keyName: string;
level: number;
defaultExpanded: boolean;
}) => {
const [isExpanded, setExpanded] = useState(true);
useEffect(() => {
setExpanded(props.defaultExpanded);
}, [props.defaultExpanded, props.value, props.keyName]);
// TODO: have max level depth.
const { value, keyName: key, level } = props;
const bodyClassNames =
Expand Down Expand Up @@ -199,6 +276,7 @@ const RenderedField = (props: {
value={v}
keyName={key + '[' + i.toString() + ']'}
level={level + 1}
defaultExpanded={props.defaultExpanded}
/>
</div>
);
Expand All @@ -214,7 +292,12 @@ const RenderedField = (props: {
Object.entries(value).map(([k, v]) => {
return (
<div key={key + '-' + k} className={bodyClassNames}>
<RenderedField value={v} keyName={k} level={level + 1} />
<RenderedField
value={v}
keyName={k}
level={level + 1}
defaultExpanded={props.defaultExpanded}
/>
</div>
);
})
Expand All @@ -234,13 +317,26 @@ const RenderedField = (props: {
);
};

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

// This component is used to render the form data in a structured way
const FormRenderer: React.FC<FormRendererProps> = ({ data }) => {
const FormRenderer: React.FC<FormRendererProps> = ({ data, isDefaultExpanded: isExpanded }) => {
if (data !== null) {
return (
<>
{Object.entries(data).map(([key, value]) => {
return <RenderedField keyName={key} value={value} level={0} key={key} />;
return (
<RenderedField
keyName={key}
value={value}
level={0}
key={key}
defaultExpanded={isExpanded}
/>
);
})}
</>
);
Expand Down
5 changes: 4 additions & 1 deletion telemetry/ui/src/examples/EmailAssistant.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,10 @@ export const EmailAssistantAppSelector = (props: {
);
const createAndUpdateMutation = useMutation(
(app_id: string) =>
DefaultService.createNewApplicationApiV0ChatbotCreateProjectIdAppIdPost(projectId, app_id),
DefaultService.createNewApplicationApiV0EmailAssistantCreateNewProjectIdAppIdPost(
projectId,
app_id
),
{
onSuccess: (appID) => {
refetch().then((data) => {
Expand Down
Loading