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

Improve preview feedback in fetch query #1153

Merged
merged 6 commits into from
Oct 14, 2022
Merged
Changes from 1 commit
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
65 changes: 62 additions & 3 deletions packages/toolpad-app/src/toolpadDataSources/rest/client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import {
TextField,
Toolbar,
Typography,
Alert,
} from '@mui/material';
import { Controller, useForm } from 'react-hook-form';
import { TabContext, TabList } from '@mui/lab';
import { isEmpty } from 'lodash-es';
import { ClientDataSource, ConnectionEditorProps, QueryEditorProps } from '../../types';
import {
FetchPrivateQuery,
Expand Down Expand Up @@ -200,6 +202,59 @@ function ConnectionParamsInput({ value, onChange }: ConnectionEditorProps<RestCo
);
}

const isCorrectlyTransformedData = (preview: FetchResult) => {
const { data, untransformedData } = preview;

if (isEmpty(untransformedData)) {
return true;
}

return !isEmpty(data);
Copy link
Member

@Janpot Janpot Oct 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the logic is sound here. It should be perfectly valid to return empty arrays or empty objects from the transform function. Similarly, untransformedData can perfectly return an empty array while the transformed data returns undefined.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry it's not really clear what you are proposing or what's exactly the issue?

Similarly, untransformedData can perfectly return an empty array while the transformed data returns undefined.

Can you provide an example?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you provide an example?

Sure thing, here's a transform function that legitimately returns an empty array:

use url: https://httpbin.org/json
use transform: return data.slideshow.slides.filter(slide => slide.title.includes('foo'));

I believe this should not show the warning.

I also think we should accept null as a legitimate value and only show a warning when the transform is returning undefined.
We should probably also make the JsonView show undefined instead of nothing when the input is undefined

Lastly, I believe we should also consider preventing the overuse of basic lodash methods through no-restricted-imports and only whitelist a few like pick, throttle, get and set. Methods like isEmpty cover a too broad range of values and result in bugs like these. Also, the word "empty" doesn't have a well defined meaning in programming, making the code less intuitive. Better to use native constructs to express the intent clearer to other maintainers

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, that's great feedback, I've addressed it in a follow up PR: #1165

};

interface ResolvedPreviewProps {
preview: FetchResult | null;
}

function ResolvedPreview({ preview }: ResolvedPreviewProps): React.ReactElement | null {
if (!preview) {
return null;
}

const { untransformedData } = preview;

if (!untransformedData || isEmpty(untransformedData)) {
return (
<Alert severity="info" sx={{ m: 2 }}>
The request did not return any data.
</Alert>
);
}

if (!isCorrectlyTransformedData(preview)) {
return (
<Alert severity="warning" sx={{ m: 2 }}>
<Typography variant="body2" sx={{ mb: 1 }}>
Request successfully completed and returned data with following keys:
bytasv marked this conversation as resolved.
Show resolved Hide resolved
</Typography>

{Object.keys(untransformedData).map((key) => (
<Typography variant="caption" sx={{ display: 'block' }} key={key}>
- {key}
</Typography>
))}
<Typography variant="body2" sx={{ mb: 1, mt: 2 }}>
However, it seems that <code>transform</code> function did not return expected value.
bytasv marked this conversation as resolved.
Show resolved Hide resolved
<br />
Please check <code>transform</code> function.
bytasv marked this conversation as resolved.
Show resolved Hide resolved
</Typography>
</Alert>
);
}

return <JsonView sx={{ height: '100%' }} src={preview?.data} />;
}

function QueryEditor({
globalScope,
connectionParams,
Expand Down Expand Up @@ -329,6 +384,8 @@ function QueryEditor({
globalScope: queryScope,
});

const [activeTab, setActiveTab] = React.useState('urlQuery');

const [previewHar, setPreviewHar] = React.useState(() => createHarLog());
const { preview, runPreview: handleRunPreview } = useQueryPreview<FetchPrivateQuery, FetchResult>(
{
Expand All @@ -338,15 +395,17 @@ function QueryEditor({
},
{
onPreview(result) {
if (!isCorrectlyTransformedData(result)) {
setActiveTab('transform');
}

setPreviewHar((existing) => mergeHar(createHarLog(), existing, result.har));
},
},
);

const handleHarClear = React.useCallback(() => setPreviewHar(createHarLog()), []);

const [activeTab, setActiveTab] = React.useState('urlQuery');

const handleActiveTabChange = React.useCallback(
(event: React.SyntheticEvent, newValue: string) => setActiveTab(newValue),
[],
Expand Down Expand Up @@ -469,7 +528,7 @@ function QueryEditor({
{preview?.error ? (
<ErrorAlert error={preview?.error} />
) : (
<JsonView sx={{ height: '100%' }} src={preview?.data} />
<ResolvedPreview preview={preview} />
)}
<Devtools
sx={{ width: '100%', height: '100%' }}
Expand Down