Merge pull request #993 from automatisch/searchable-json

feat: add searchable json viewer component
This commit is contained in:
Ömer Faruk Aydın
2023-03-20 13:29:40 +03:00
committed by GitHub
3 changed files with 115 additions and 6 deletions

View File

@@ -10,7 +10,7 @@ import Box from '@mui/material/Box';
import type { IApp, IExecutionStep, IStep } from '@automatisch/types';
import TabPanel from 'components/TabPanel';
import JSONViewer from 'components/JSONViewer';
import SearchableJSONViewer from 'components/SearchableJSONViewer';
import AppIcon from 'components/AppIcon';
import { GET_APPS } from 'graphql/queries/get-apps';
import useFormatMessage from 'hooks/useFormatMessage';
@@ -92,16 +92,16 @@ export default function ExecutionStep(
</Box>
<TabPanel value={activeTabIndex} index={0}>
<JSONViewer data={executionStep.dataIn} />
<SearchableJSONViewer data={executionStep.dataIn} />
</TabPanel>
<TabPanel value={activeTabIndex} index={1}>
<JSONViewer data={executionStep.dataOut} />
<SearchableJSONViewer data={executionStep.dataOut} />
</TabPanel>
{hasError && (
<TabPanel value={activeTabIndex} index={2}>
<JSONViewer data={executionStep.errorDetails} />
<SearchableJSONViewer data={executionStep.errorDetails} />
</TabPanel>
)}
</Content>

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

View File

@@ -138,5 +138,6 @@
"resetPasswordForm.confirmPasswordFieldLabel": "Confirm password",
"resetPasswordForm.passwordUpdated": "The password has been updated. Now, you can login.",
"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"
}