Merge pull request #993 from automatisch/searchable-json
feat: add searchable json viewer component
This commit is contained in:
@@ -10,7 +10,7 @@ import Box from '@mui/material/Box';
|
|||||||
import type { IApp, IExecutionStep, IStep } from '@automatisch/types';
|
import type { IApp, IExecutionStep, IStep } from '@automatisch/types';
|
||||||
|
|
||||||
import TabPanel from 'components/TabPanel';
|
import TabPanel from 'components/TabPanel';
|
||||||
import JSONViewer from 'components/JSONViewer';
|
import SearchableJSONViewer from 'components/SearchableJSONViewer';
|
||||||
import AppIcon from 'components/AppIcon';
|
import AppIcon from 'components/AppIcon';
|
||||||
import { GET_APPS } from 'graphql/queries/get-apps';
|
import { GET_APPS } from 'graphql/queries/get-apps';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
@@ -92,16 +92,16 @@ export default function ExecutionStep(
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<TabPanel value={activeTabIndex} index={0}>
|
<TabPanel value={activeTabIndex} index={0}>
|
||||||
<JSONViewer data={executionStep.dataIn} />
|
<SearchableJSONViewer data={executionStep.dataIn} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel value={activeTabIndex} index={1}>
|
<TabPanel value={activeTabIndex} index={1}>
|
||||||
<JSONViewer data={executionStep.dataOut} />
|
<SearchableJSONViewer data={executionStep.dataOut} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
{hasError && (
|
{hasError && (
|
||||||
<TabPanel value={activeTabIndex} index={2}>
|
<TabPanel value={activeTabIndex} index={2}>
|
||||||
<JSONViewer data={executionStep.errorDetails} />
|
<SearchableJSONViewer data={executionStep.errorDetails} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
)}
|
)}
|
||||||
</Content>
|
</Content>
|
||||||
|
108
packages/web/src/components/SearchableJSONViewer/index.tsx
Normal file
108
packages/web/src/components/SearchableJSONViewer/index.tsx
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import set from 'lodash/set';
|
||||||
|
import throttle from 'lodash/throttle';
|
||||||
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
import forIn from 'lodash/forIn';
|
||||||
|
import isPlainObject from 'lodash/isPlainObject';
|
||||||
|
import { Box, Typography } from '@mui/material';
|
||||||
|
|
||||||
|
import { IJSONObject } from '@automatisch/types';
|
||||||
|
import JSONViewer from 'components/JSONViewer';
|
||||||
|
import SearchInput from 'components/SearchInput';
|
||||||
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
|
|
||||||
|
type JSONViewerProps = {
|
||||||
|
data: IJSONObject;
|
||||||
|
};
|
||||||
|
|
||||||
|
function aggregate(
|
||||||
|
data: any,
|
||||||
|
searchTerm: string,
|
||||||
|
result = {},
|
||||||
|
prefix: string[] = [],
|
||||||
|
withinArray = false
|
||||||
|
) {
|
||||||
|
if (withinArray) {
|
||||||
|
const containerValue = get(result, prefix, []);
|
||||||
|
|
||||||
|
result = aggregate(
|
||||||
|
data,
|
||||||
|
searchTerm,
|
||||||
|
result,
|
||||||
|
prefix.concat(containerValue.length.toString())
|
||||||
|
);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPlainObject(data)) {
|
||||||
|
forIn(data, (value, key) => {
|
||||||
|
const fullKey = [...prefix, key];
|
||||||
|
|
||||||
|
if (key.toLowerCase().includes(searchTerm)) {
|
||||||
|
set(result, fullKey, value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = aggregate(value, searchTerm, result, fullKey);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
forIn(data, (value) => {
|
||||||
|
result = aggregate(value, searchTerm, result, prefix, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
['string', 'number'].includes(typeof data) &&
|
||||||
|
String(data).toLowerCase().includes(searchTerm)
|
||||||
|
) {
|
||||||
|
set(result, prefix, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SearchableJSONViewer = ({ data }: JSONViewerProps) => {
|
||||||
|
const [filteredData, setFilteredData] = React.useState<IJSONObject | null>(
|
||||||
|
data
|
||||||
|
);
|
||||||
|
const formatMessage = useFormatMessage();
|
||||||
|
|
||||||
|
const onSearchChange = React.useMemo(
|
||||||
|
() =>
|
||||||
|
throttle((event: React.ChangeEvent) => {
|
||||||
|
const search = (event.target as HTMLInputElement).value.toLowerCase();
|
||||||
|
|
||||||
|
if (!search) {
|
||||||
|
setFilteredData(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newFilteredData = aggregate(data, search);
|
||||||
|
|
||||||
|
if (isEmpty(newFilteredData)) {
|
||||||
|
setFilteredData(null);
|
||||||
|
} else {
|
||||||
|
setFilteredData(newFilteredData);
|
||||||
|
}
|
||||||
|
}, 400),
|
||||||
|
[data]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box my={2}>
|
||||||
|
<SearchInput onChange={onSearchChange} />
|
||||||
|
</Box>
|
||||||
|
{filteredData && <JSONViewer data={filteredData} />}
|
||||||
|
{!filteredData && (
|
||||||
|
<Typography>{formatMessage('jsonViewer.noDataFound')}</Typography>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SearchableJSONViewer;
|
@@ -138,5 +138,6 @@
|
|||||||
"resetPasswordForm.confirmPasswordFieldLabel": "Confirm password",
|
"resetPasswordForm.confirmPasswordFieldLabel": "Confirm password",
|
||||||
"resetPasswordForm.passwordUpdated": "The password has been updated. Now, you can login.",
|
"resetPasswordForm.passwordUpdated": "The password has been updated. Now, you can login.",
|
||||||
"usageAlert.informationText": "Tasks: {consumedTaskCount}/{allowedTaskCount} (Resets {relativeResetDate})",
|
"usageAlert.informationText": "Tasks: {consumedTaskCount}/{allowedTaskCount} (Resets {relativeResetDate})",
|
||||||
"usageAlert.viewPlans": "View plans"
|
"usageAlert.viewPlans": "View plans",
|
||||||
|
"jsonViewer.noDataFound": "We couldn't find anything matching your search"
|
||||||
}
|
}
|
Reference in New Issue
Block a user