mirror of
https://github.com/usebruno/bruno.git
synced 2025-06-27 07:21:51 +02:00
Add jsonpath response filtering
This commit is contained in:
parent
8fb8eee5ef
commit
04aa921099
17
package-lock.json
generated
17
package-lock.json
generated
@ -10903,6 +10903,14 @@
|
|||||||
"graceful-fs": "^4.1.6"
|
"graceful-fs": "^4.1.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jsonpath-plus": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-zBfiUPM5nD0YZSBT/o/fbCUlCcepMIdP0CJZxM1+KgA4f2T206f6VAg9e7mX35+KlMaIc5qXW34f3BnwJ3w+RA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/jsprim": {
|
"node_modules/jsprim": {
|
||||||
"version": "1.4.2",
|
"version": "1.4.2",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@ -16653,6 +16661,7 @@
|
|||||||
"httpsnippet": "^3.0.1",
|
"httpsnippet": "^3.0.1",
|
||||||
"idb": "^7.0.0",
|
"idb": "^7.0.0",
|
||||||
"immer": "^9.0.15",
|
"immer": "^9.0.15",
|
||||||
|
"jsonpath-plus": "^7.2.0",
|
||||||
"know-your-http-well": "^0.5.0",
|
"know-your-http-well": "^0.5.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"markdown-it": "^13.0.2",
|
"markdown-it": "^13.0.2",
|
||||||
@ -16820,7 +16829,7 @@
|
|||||||
},
|
},
|
||||||
"packages/bruno-electron": {
|
"packages/bruno-electron": {
|
||||||
"name": "bruno",
|
"name": "bruno",
|
||||||
"version": "v1.0.1",
|
"version": "v1.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/credential-providers": "^3.425.0",
|
"@aws-sdk/credential-providers": "^3.425.0",
|
||||||
"@usebruno/js": "0.9.1",
|
"@usebruno/js": "0.9.1",
|
||||||
@ -20760,6 +20769,7 @@
|
|||||||
"httpsnippet": "^3.0.1",
|
"httpsnippet": "^3.0.1",
|
||||||
"idb": "^7.0.0",
|
"idb": "^7.0.0",
|
||||||
"immer": "^9.0.15",
|
"immer": "^9.0.15",
|
||||||
|
"jsonpath-plus": "^7.2.0",
|
||||||
"know-your-http-well": "^0.5.0",
|
"know-your-http-well": "^0.5.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"markdown-it": "^13.0.2",
|
"markdown-it": "^13.0.2",
|
||||||
@ -25003,6 +25013,11 @@
|
|||||||
"universalify": "^2.0.0"
|
"universalify": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"jsonpath-plus": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-zBfiUPM5nD0YZSBT/o/fbCUlCcepMIdP0CJZxM1+KgA4f2T206f6VAg9e7mX35+KlMaIc5qXW34f3BnwJ3w+RA=="
|
||||||
|
},
|
||||||
"jsprim": {
|
"jsprim": {
|
||||||
"version": "1.4.2",
|
"version": "1.4.2",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
"httpsnippet": "^3.0.1",
|
"httpsnippet": "^3.0.1",
|
||||||
"idb": "^7.0.0",
|
"idb": "^7.0.0",
|
||||||
"immer": "^9.0.15",
|
"immer": "^9.0.15",
|
||||||
|
"jsonpath-plus": "^7.2.0",
|
||||||
"know-your-http-well": "^0.5.0",
|
"know-your-http-well": "^0.5.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"markdown-it": "^13.0.2",
|
"markdown-it": "^13.0.2",
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
import { IconFilter } from '@tabler/icons';
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { Tooltip as ReactTooltip } from 'react-tooltip';
|
||||||
|
|
||||||
|
const QueryResultFilter = ({ onChange, mode }) => {
|
||||||
|
const tooltipText = useMemo(() => {
|
||||||
|
if (mode.includes('json')) {
|
||||||
|
return 'Filter with JSONPath';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode.includes('xml')) {
|
||||||
|
return 'Filter with XPath';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}, [mode]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={'response-filter relative'}>
|
||||||
|
<div className="absolute inset-y-0 left-0 pl-4 flex items-center">
|
||||||
|
<div className="text-gray-500 sm:text-sm" id="request-filter-icon">
|
||||||
|
<IconFilter size={16} strokeWidth={1.5} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{tooltipText && typeof tooltipText === 'string' && (
|
||||||
|
<ReactTooltip anchorId={'request-filter-icon'} html={tooltipText} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="response-filter"
|
||||||
|
id="response-filter"
|
||||||
|
autoComplete="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
autoCapitalize="off"
|
||||||
|
spellCheck="false"
|
||||||
|
className="block w-full pl-10 py-1 sm:text-sm"
|
||||||
|
onChange={onChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default QueryResultFilter;
|
@ -3,7 +3,8 @@ import styled from 'styled-components';
|
|||||||
const StyledWrapper = styled.div`
|
const StyledWrapper = styled.div`
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 100%;
|
grid-template-columns: 100%;
|
||||||
grid-template-rows: 1.25rem calc(100% - 1.25rem);
|
grid-template-rows: ${(props) =>
|
||||||
|
props.queryFilterEnabled ? '1.25rem calc(100% - 3.5rem)' : '1.25rem calc(100% - 1.25rem)'};
|
||||||
|
|
||||||
/* This is a hack to force Codemirror to use all available space */
|
/* This is a hack to force Codemirror to use all available space */
|
||||||
> div {
|
> div {
|
||||||
@ -27,6 +28,22 @@ const StyledWrapper = styled.div`
|
|||||||
.muted {
|
.muted {
|
||||||
color: ${(props) => props.theme.colors.text.muted};
|
color: ${(props) => props.theme.colors.text.muted};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.response-filter {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
input {
|
||||||
|
border: ${(props) => props.theme.sidebar.search.border};
|
||||||
|
border-radius: 2px;
|
||||||
|
background-color: ${(props) => props.theme.sidebar.search.bg};
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default StyledWrapper;
|
export default StyledWrapper;
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
import { debounce } from 'lodash';
|
||||||
|
import QueryResultFilter from './QueryResultFilter';
|
||||||
|
import { JSONPath } from 'jsonpath-plus';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { getContentType, safeStringifyJSON, safeParseXML } from 'utils/common';
|
import { getContentType, safeStringifyJSON, safeParseXML } from 'utils/common';
|
||||||
@ -10,12 +13,20 @@ import { useMemo } from 'react';
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useTheme } from 'providers/Theme/index';
|
import { useTheme } from 'providers/Theme/index';
|
||||||
|
|
||||||
const formatResponse = (data, mode) => {
|
const formatResponse = (data, mode, filter) => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode.includes('json')) {
|
if (mode.includes('json')) {
|
||||||
|
if (filter) {
|
||||||
|
try {
|
||||||
|
data = JSONPath({ path: filter, json: data });
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Could not filter with JSONPath.', e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return safeStringifyJSON(data, true);
|
return safeStringifyJSON(data, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,9 +49,14 @@ const formatResponse = (data, mode) => {
|
|||||||
const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEventListener, headers, error }) => {
|
const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEventListener, headers, error }) => {
|
||||||
const contentType = getContentType(headers);
|
const contentType = getContentType(headers);
|
||||||
const mode = getCodeMirrorModeBasedOnContentType(contentType);
|
const mode = getCodeMirrorModeBasedOnContentType(contentType);
|
||||||
const formattedData = formatResponse(data, mode);
|
const [filter, setFilter] = useState(null);
|
||||||
|
const formattedData = formatResponse(data, mode, filter);
|
||||||
const { storedTheme } = useTheme();
|
const { storedTheme } = useTheme();
|
||||||
|
|
||||||
|
const debouncedResultFilterOnChange = debounce((e) => {
|
||||||
|
setFilter(e.target.value);
|
||||||
|
}, 250);
|
||||||
|
|
||||||
const allowedPreviewModes = useMemo(() => {
|
const allowedPreviewModes = useMemo(() => {
|
||||||
// Always show raw
|
// Always show raw
|
||||||
const allowedPreviewModes = ['raw'];
|
const allowedPreviewModes = ['raw'];
|
||||||
@ -79,8 +95,14 @@ const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEven
|
|||||||
));
|
));
|
||||||
}, [allowedPreviewModes, previewTab]);
|
}, [allowedPreviewModes, previewTab]);
|
||||||
|
|
||||||
|
const queryFilterEnabled = useMemo(() => mode.includes('json'), [mode]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="w-full h-full" style={{ maxWidth: width }}>
|
<StyledWrapper
|
||||||
|
className="w-full h-full relative"
|
||||||
|
style={{ maxWidth: width }}
|
||||||
|
queryFilterEnabled={queryFilterEnabled}
|
||||||
|
>
|
||||||
<div className="flex justify-end gap-2 text-xs" role="tablist">
|
<div className="flex justify-end gap-2 text-xs" role="tablist">
|
||||||
{tabs}
|
{tabs}
|
||||||
</div>
|
</div>
|
||||||
@ -96,19 +118,22 @@ const QueryResult = ({ item, collection, data, dataBuffer, width, disableRunEven
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<QueryResultPreview
|
<>
|
||||||
previewTab={previewTab}
|
<QueryResultPreview
|
||||||
data={data}
|
previewTab={previewTab}
|
||||||
dataBuffer={dataBuffer}
|
data={data}
|
||||||
formattedData={formattedData}
|
dataBuffer={dataBuffer}
|
||||||
item={item}
|
formattedData={formattedData}
|
||||||
contentType={contentType}
|
item={item}
|
||||||
mode={mode}
|
contentType={contentType}
|
||||||
collection={collection}
|
mode={mode}
|
||||||
allowedPreviewModes={allowedPreviewModes}
|
collection={collection}
|
||||||
disableRunEventListener={disableRunEventListener}
|
allowedPreviewModes={allowedPreviewModes}
|
||||||
storedTheme={storedTheme}
|
disableRunEventListener={disableRunEventListener}
|
||||||
/>
|
storedTheme={storedTheme}
|
||||||
|
/>
|
||||||
|
{queryFilterEnabled && <QueryResultFilter onChange={debouncedResultFilterOnChange} mode={mode} />}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user