fix: refactor response examples to use MenuDropdown and Editable components (#6382)

* feat: use common dropdown component

* fix: update example ui to match v3

* fix: test cases, bugs

* fix: review comments

* fix: review comments

* fix: review

* fix: file body/binary table within response examples

* fix: file name, close btn not visible issue

* fix: unnessary transition for three  dots

* fix: install missing deps in bruno-app

* update example url when param is updated

* empty commit

* chore: update package-lock.json

---------

Co-authored-by: Bijin A B <bijin@usebruno.com>
This commit is contained in:
sanish chirayath
2025-12-14 16:21:06 +05:30
committed by GitHub
parent 2f5537c8db
commit 8cbda5f5cc
30 changed files with 1211 additions and 1349 deletions

View File

@@ -43,13 +43,17 @@ const StyledWrapper = styled.div`
tbody {
tr {
border-bottom: 1px solid ${(props) => props.theme.table.border};
&:hover {
background: ${(props) => props.theme.plainGrid.hoverBg};
}
}
}
}
/* Override styles for EditableTable to prevent uppercase transformation and ensure proper spacing */
/* The .table-container is from EditableTable component */
.table-container table thead td {
text-transform: none !important;
letter-spacing: normal !important;
padding: 8px 10px !important;
}
tr {

View File

@@ -1,14 +1,11 @@
import React, { useState, useMemo } from 'react';
import React, { useState, useMemo, useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { IconTrash } from '@tabler/icons';
import { useTheme } from 'providers/Theme';
import get from 'lodash/get';
import { addResponseExampleParam, updateResponseExampleParam, deleteResponseExampleParam, moveResponseExampleParam, setResponseExampleParams } from 'providers/ReduxStore/slices/collections';
import Table from 'components/Table-v2';
import ReorderTable from 'components/ReorderTable';
import { moveResponseExampleParam, setResponseExampleParams } from 'providers/ReduxStore/slices/collections';
import EditableTable from 'components/EditableTable';
import SingleLineEditor from 'components/SingleLineEditor';
import BulkEditor from 'components/BulkEditor';
import Checkbox from 'components/Checkbox';
import InfoTip from 'components/InfoTip';
import StyledWrapper from './StyledWrapper';
@@ -26,61 +23,22 @@ const ResponseExampleParams = ({ editMode, item, collection, exampleUid }) => {
const queryParams = params.filter((param) => param.type === 'query');
const pathParams = params.filter((param) => param.type === 'path');
const handleAddQueryParam = () => {
const handleQueryParamsChange = useCallback((updatedQueryParams) => {
if (!editMode) {
return;
}
dispatch(addResponseExampleParam({
itemUid: item.uid,
collectionUid: collection.uid,
exampleUid: exampleUid
}));
};
const handleQueryParamChange = (e, data, key) => {
if (!editMode) {
return;
}
const updatedParam = { ...data };
switch (key) {
case 'name': {
updatedParam.name = e.target.value;
break;
}
case 'value': {
updatedParam.value = e.target.value;
break;
}
case 'enabled': {
updatedParam.enabled = e.target.checked;
break;
}
}
dispatch(updateResponseExampleParam({
// Merge updated query params with path params
const allParams = [...updatedQueryParams, ...pathParams];
dispatch(setResponseExampleParams({
itemUid: item.uid,
collectionUid: collection.uid,
exampleUid: exampleUid,
param: updatedParam
params: allParams
}));
};
}, [editMode, dispatch, item.uid, collection.uid, exampleUid, pathParams]);
const handleRemoveQueryParam = (param) => {
if (!editMode) {
return;
}
dispatch(deleteResponseExampleParam({
itemUid: item.uid,
collectionUid: collection.uid,
exampleUid: exampleUid,
paramUid: param.uid
}));
};
const handleQueryParamDrag = ({ updateReorderedItem }) => {
const handleQueryParamDrag = useCallback(({ updateReorderedItem }) => {
if (!editMode) {
return;
}
@@ -91,7 +49,22 @@ const ResponseExampleParams = ({ editMode, item, collection, exampleUid }) => {
exampleUid: exampleUid,
updateReorderedItem
}));
};
}, [editMode, dispatch, item.uid, collection.uid, exampleUid]);
const handlePathParamsChange = useCallback((updatedPathParams) => {
if (!editMode) {
return;
}
// Merge updated path params with query params
const allParams = [...queryParams, ...updatedPathParams];
dispatch(setResponseExampleParams({
itemUid: item.uid,
collectionUid: collection.uid,
exampleUid: exampleUid,
params: allParams
}));
}, [editMode, dispatch, item.uid, collection.uid, exampleUid, queryParams]);
const toggleBulkEditMode = () => {
setIsBulkEditMode(!isBulkEditMode);
@@ -102,27 +75,13 @@ const ResponseExampleParams = ({ editMode, item, collection, exampleUid }) => {
return;
}
// Merge bulk edited query params with path params
const allParams = [...newParams, ...pathParams];
dispatch(setResponseExampleParams({
itemUid: item.uid,
collectionUid: collection.uid,
exampleUid: exampleUid,
params: newParams
}));
};
const handlePathParamChange = (e, data) => {
if (!editMode) {
return;
}
const updatedParam = { ...data };
updatedParam.value = e.target.value;
dispatch(updateResponseExampleParam({
itemUid: item.uid,
collectionUid: collection.uid,
exampleUid: exampleUid,
param: updatedParam
params: allParams
}));
};
@@ -138,6 +97,86 @@ const ResponseExampleParams = ({ editMode, item, collection, exampleUid }) => {
);
}
const queryColumns = [
{
key: 'name',
name: 'Name',
isKeyField: true,
placeholder: 'Name',
width: '40%',
readOnly: !editMode,
render: ({ row, value, onChange, isLastEmptyRow }) => (
<SingleLineEditor
value={value || ''}
theme={storedTheme}
onSave={() => {}}
onChange={onChange}
onRun={() => {}}
collection={collection}
variablesAutocomplete={true}
readOnly={!editMode}
placeholder={isLastEmptyRow ? 'Name' : ''}
/>
)
},
{
key: 'value',
name: 'Value',
placeholder: 'Value',
width: '60%',
readOnly: !editMode,
render: ({ row, value, onChange, isLastEmptyRow }) => (
<SingleLineEditor
value={value || ''}
theme={storedTheme}
onSave={() => {}}
onChange={onChange}
onRun={() => {}}
collection={collection}
variablesAutocomplete={true}
readOnly={!editMode}
placeholder={isLastEmptyRow ? 'Value' : ''}
/>
)
}
];
const pathColumns = [
{
key: 'name',
name: 'Name',
readOnly: true,
width: '40%'
},
{
key: 'value',
name: 'Value',
placeholder: 'Value',
width: '60%',
readOnly: !editMode,
render: ({ row, value, onChange, isLastEmptyRow }) => (
<SingleLineEditor
value={value || ''}
theme={storedTheme}
onSave={() => {}}
onChange={onChange}
onRun={() => {}}
collection={collection}
variablesAutocomplete={true}
readOnly={!editMode}
placeholder={isLastEmptyRow ? 'Value' : ''}
/>
)
}
];
const defaultQueryRow = {
name: '',
value: '',
enabled: true,
type: 'query'
};
if (queryParams.length === 0 && pathParams.length === 0 && !editMode) {
return null;
}
@@ -145,69 +184,17 @@ const ResponseExampleParams = ({ editMode, item, collection, exampleUid }) => {
return (
<StyledWrapper className="w-full mt-4">
<div className="mb-1 title text-xs font-bold">Query parameters</div>
<Table
headers={[
{ name: 'Name', accessor: 'name', width: '40%' },
{ name: 'Value', accessor: 'value', width: '60%' }
]}
>
<ReorderTable updateReorderedItem={handleQueryParamDrag}>
{queryParams && queryParams.length
? queryParams.map((param, index) => (
<tr key={param.uid} data-uid={param.uid}>
<td className="flex relative">
<div className="flex items-center justify-center mr-3">
<Checkbox
checked={param.enabled !== false}
disabled={!editMode}
onChange={(e) => handleQueryParamChange(e, param, 'enabled')}
dataTestId={`query-param-checkbox-${index}`}
/>
</div>
<SingleLineEditor
value={param.name || ''}
theme={storedTheme}
onSave={() => {}}
onChange={(newValue) => handleQueryParamChange({ target: { value: newValue } }, param, 'name')}
onRun={() => {}}
collection={collection}
variablesAutocomplete={true}
readOnly={!editMode}
/>
</td>
<td>
<div className="flex items-center justify-center pl-4">
<SingleLineEditor
value={param.value || ''}
theme={storedTheme}
onSave={() => {}}
onChange={(newValue) => handleQueryParamChange({ target: { value: newValue } }, param, 'value')}
onRun={() => {}}
collection={collection}
variablesAutocomplete={true}
readOnly={!editMode}
/>
{editMode && (
<button tabIndex="-1" onClick={() => handleRemoveQueryParam(param)} className="delete-button">
<IconTrash strokeWidth={1.5} size={16} />
</button>
)}
</div>
</td>
</tr>
))
: null}
</ReorderTable>
</Table>
<EditableTable
columns={queryColumns}
rows={queryParams || []}
onChange={handleQueryParamsChange}
defaultRow={defaultQueryRow}
reorderable={editMode}
onReorder={handleQueryParamDrag}
showAddRow={editMode}
/>
{editMode && (
<div className="flex justify-between mt-2">
<button
className="btn-action text-link pr-2 py-3 select-none"
onClick={handleAddQueryParam}
>
+ Add Param
</button>
<div className="flex justify-end mt-2">
<button
className="btn-action text-link select-none"
onClick={toggleBulkEditMode}
@@ -231,37 +218,16 @@ const ResponseExampleParams = ({ editMode, item, collection, exampleUid }) => {
</div>
</InfoTip>
</div>
<Table
headers={[
{ name: 'Name', accessor: 'name', width: '40%' },
{ name: 'Value', accessor: 'value', width: '60%' }
]}
>
{pathParams && pathParams.length
? pathParams.map((path, index) => {
return (
<tr key={index} data-uid={path.uid}>
<td>
{path.name}
</td>
<td>
<SingleLineEditor
value={path.value}
theme={storedTheme}
onSave={() => {}}
onChange={(newValue) => handlePathParamChange({ target: { value: newValue } }, path)}
onRun={() => {}}
collection={collection}
variablesAutocomplete={true}
readOnly={!editMode}
/>
</td>
</tr>
);
})
: null}
</Table>
{pathParams.length === 0 && <div className="title pr-2 py-3 mt-2 text-xs">No path parameters defined</div>}
<EditableTable
columns={pathColumns}
rows={pathParams}
onChange={handlePathParamsChange}
defaultRow={{}}
showCheckbox={false}
showDelete={false}
showAddRow={false}
reorderable={false}
/>
</>
)}