mirror of
https://github.com/usebruno/bruno.git
synced 2024-11-25 01:14:23 +01:00
feature: Asserts table resizable and orderable
This commit is contained in:
parent
dce96e0f13
commit
c670ae579e
@ -1,218 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { IconTrash } from '@tabler/icons';
|
|
||||||
import SingleLineEditor from 'components/SingleLineEditor';
|
|
||||||
import AssertionOperator from '../AssertionOperator';
|
|
||||||
import { useTheme } from 'providers/Theme';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assertion operators
|
|
||||||
*
|
|
||||||
* eq : equal to
|
|
||||||
* neq : not equal to
|
|
||||||
* gt : greater than
|
|
||||||
* gte : greater than or equal to
|
|
||||||
* lt : less than
|
|
||||||
* lte : less than or equal to
|
|
||||||
* in : in
|
|
||||||
* notIn : not in
|
|
||||||
* contains : contains
|
|
||||||
* notContains : not contains
|
|
||||||
* length : length
|
|
||||||
* matches : matches
|
|
||||||
* notMatches : not matches
|
|
||||||
* startsWith : starts with
|
|
||||||
* endsWith : ends with
|
|
||||||
* between : between
|
|
||||||
* isEmpty : is empty
|
|
||||||
* isNull : is null
|
|
||||||
* isUndefined : is undefined
|
|
||||||
* isDefined : is defined
|
|
||||||
* isTruthy : is truthy
|
|
||||||
* isFalsy : is falsy
|
|
||||||
* isJson : is json
|
|
||||||
* isNumber : is number
|
|
||||||
* isString : is string
|
|
||||||
* isBoolean : is boolean
|
|
||||||
* isArray : is array
|
|
||||||
*/
|
|
||||||
const parseAssertionOperator = (str = '') => {
|
|
||||||
if (!str || typeof str !== 'string' || !str.length) {
|
|
||||||
return {
|
|
||||||
operator: 'eq',
|
|
||||||
value: str
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const operators = [
|
|
||||||
'eq',
|
|
||||||
'neq',
|
|
||||||
'gt',
|
|
||||||
'gte',
|
|
||||||
'lt',
|
|
||||||
'lte',
|
|
||||||
'in',
|
|
||||||
'notIn',
|
|
||||||
'contains',
|
|
||||||
'notContains',
|
|
||||||
'length',
|
|
||||||
'matches',
|
|
||||||
'notMatches',
|
|
||||||
'startsWith',
|
|
||||||
'endsWith',
|
|
||||||
'between',
|
|
||||||
'isEmpty',
|
|
||||||
'isNull',
|
|
||||||
'isUndefined',
|
|
||||||
'isDefined',
|
|
||||||
'isTruthy',
|
|
||||||
'isFalsy',
|
|
||||||
'isJson',
|
|
||||||
'isNumber',
|
|
||||||
'isString',
|
|
||||||
'isBoolean',
|
|
||||||
'isArray'
|
|
||||||
];
|
|
||||||
|
|
||||||
const unaryOperators = [
|
|
||||||
'isEmpty',
|
|
||||||
'isNull',
|
|
||||||
'isUndefined',
|
|
||||||
'isDefined',
|
|
||||||
'isTruthy',
|
|
||||||
'isFalsy',
|
|
||||||
'isJson',
|
|
||||||
'isNumber',
|
|
||||||
'isString',
|
|
||||||
'isBoolean',
|
|
||||||
'isArray'
|
|
||||||
];
|
|
||||||
|
|
||||||
const [operator, ...rest] = str.trim().split(' ');
|
|
||||||
const value = rest.join(' ');
|
|
||||||
|
|
||||||
if (unaryOperators.includes(operator)) {
|
|
||||||
return {
|
|
||||||
operator,
|
|
||||||
value: ''
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (operators.includes(operator)) {
|
|
||||||
return {
|
|
||||||
operator,
|
|
||||||
value
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
operator: 'eq',
|
|
||||||
value: str
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const isUnaryOperator = (operator) => {
|
|
||||||
const unaryOperators = [
|
|
||||||
'isEmpty',
|
|
||||||
'isNull',
|
|
||||||
'isUndefined',
|
|
||||||
'isDefined',
|
|
||||||
'isTruthy',
|
|
||||||
'isFalsy',
|
|
||||||
'isJson',
|
|
||||||
'isNumber',
|
|
||||||
'isString',
|
|
||||||
'isBoolean',
|
|
||||||
'isArray'
|
|
||||||
];
|
|
||||||
|
|
||||||
return unaryOperators.includes(operator);
|
|
||||||
};
|
|
||||||
|
|
||||||
const AssertionRow = ({
|
|
||||||
item,
|
|
||||||
collection,
|
|
||||||
assertion,
|
|
||||||
handleAssertionChange,
|
|
||||||
handleRemoveAssertion,
|
|
||||||
onSave,
|
|
||||||
handleRun
|
|
||||||
}) => {
|
|
||||||
const { storedTheme } = useTheme();
|
|
||||||
|
|
||||||
const { operator, value } = parseAssertionOperator(assertion.value);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<tr key={assertion.uid}>
|
|
||||||
<td>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
autoComplete="off"
|
|
||||||
autoCorrect="off"
|
|
||||||
autoCapitalize="off"
|
|
||||||
spellCheck="false"
|
|
||||||
value={assertion.name}
|
|
||||||
className="mousetrap"
|
|
||||||
onChange={(e) => handleAssertionChange(e, assertion, 'name')}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<AssertionOperator
|
|
||||||
operator={operator}
|
|
||||||
onChange={(op) =>
|
|
||||||
handleAssertionChange(
|
|
||||||
{
|
|
||||||
target: {
|
|
||||||
value: `${op} ${value}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
assertion,
|
|
||||||
'value'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{!isUnaryOperator(operator) ? (
|
|
||||||
<SingleLineEditor
|
|
||||||
value={value}
|
|
||||||
theme={storedTheme}
|
|
||||||
readOnly={true}
|
|
||||||
onSave={onSave}
|
|
||||||
onChange={(newValue) =>
|
|
||||||
handleAssertionChange(
|
|
||||||
{
|
|
||||||
target: {
|
|
||||||
value: `${operator} ${newValue}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
assertion,
|
|
||||||
'value'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onRun={handleRun}
|
|
||||||
collection={collection}
|
|
||||||
item={item}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<input type="text" className="cursor-default" disabled />
|
|
||||||
)}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div className="flex items-center">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={assertion.enabled}
|
|
||||||
tabIndex="-1"
|
|
||||||
className="mr-3 mousetrap"
|
|
||||||
onChange={(e) => handleAssertionChange(e, assertion, 'enabled')}
|
|
||||||
/>
|
|
||||||
<button tabIndex="-1" onClick={() => handleRemoveAssertion(assertion)}>
|
|
||||||
<IconTrash strokeWidth={1.5} size={20} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AssertionRow;
|
|
@ -20,14 +20,6 @@ const Wrapper = styled.div`
|
|||||||
td {
|
td {
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
|
|
||||||
&:nth-child(1) {
|
|
||||||
width: 30%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(4) {
|
|
||||||
width: 70px;
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,142 @@ import React from 'react';
|
|||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import cloneDeep from 'lodash/cloneDeep';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { addAssertion, updateAssertion, deleteAssertion } from 'providers/ReduxStore/slices/collections';
|
import { IconTrash } from '@tabler/icons';
|
||||||
|
import { addAssertion, moveAssertion, updateAssertion, deleteAssertion } from 'providers/ReduxStore/slices/collections';
|
||||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
import AssertionRow from './AssertionRow';
|
import { useTheme } from 'providers/Theme';
|
||||||
|
import AssertionOperator from './AssertionOperator';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import Table from 'components/Table/index';
|
||||||
|
import ReorderTable from 'components/ReorderTable/index';
|
||||||
|
import SingleLineEditor from 'components/SingleLineEditor';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assertion operators
|
||||||
|
*
|
||||||
|
* eq : equal to
|
||||||
|
* neq : not equal to
|
||||||
|
* gt : greater than
|
||||||
|
* gte : greater than or equal to
|
||||||
|
* lt : less than
|
||||||
|
* lte : less than or equal to
|
||||||
|
* in : in
|
||||||
|
* notIn : not in
|
||||||
|
* contains : contains
|
||||||
|
* notContains : not contains
|
||||||
|
* length : length
|
||||||
|
* matches : matches
|
||||||
|
* notMatches : not matches
|
||||||
|
* startsWith : starts with
|
||||||
|
* endsWith : ends with
|
||||||
|
* between : between
|
||||||
|
* isEmpty : is empty
|
||||||
|
* isNull : is null
|
||||||
|
* isUndefined : is undefined
|
||||||
|
* isDefined : is defined
|
||||||
|
* isTruthy : is truthy
|
||||||
|
* isFalsy : is falsy
|
||||||
|
* isJson : is json
|
||||||
|
* isNumber : is number
|
||||||
|
* isString : is string
|
||||||
|
* isBoolean : is boolean
|
||||||
|
* isArray : is array
|
||||||
|
*/
|
||||||
|
const parseAssertionOperator = (str = '') => {
|
||||||
|
if (!str || typeof str !== 'string' || !str.length) {
|
||||||
|
return {
|
||||||
|
operator: 'eq',
|
||||||
|
value: str
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const operators = [
|
||||||
|
'eq',
|
||||||
|
'neq',
|
||||||
|
'gt',
|
||||||
|
'gte',
|
||||||
|
'lt',
|
||||||
|
'lte',
|
||||||
|
'in',
|
||||||
|
'notIn',
|
||||||
|
'contains',
|
||||||
|
'notContains',
|
||||||
|
'length',
|
||||||
|
'matches',
|
||||||
|
'notMatches',
|
||||||
|
'startsWith',
|
||||||
|
'endsWith',
|
||||||
|
'between',
|
||||||
|
'isEmpty',
|
||||||
|
'isNull',
|
||||||
|
'isUndefined',
|
||||||
|
'isDefined',
|
||||||
|
'isTruthy',
|
||||||
|
'isFalsy',
|
||||||
|
'isJson',
|
||||||
|
'isNumber',
|
||||||
|
'isString',
|
||||||
|
'isBoolean',
|
||||||
|
'isArray'
|
||||||
|
];
|
||||||
|
|
||||||
|
const unaryOperators = [
|
||||||
|
'isEmpty',
|
||||||
|
'isNull',
|
||||||
|
'isUndefined',
|
||||||
|
'isDefined',
|
||||||
|
'isTruthy',
|
||||||
|
'isFalsy',
|
||||||
|
'isJson',
|
||||||
|
'isNumber',
|
||||||
|
'isString',
|
||||||
|
'isBoolean',
|
||||||
|
'isArray'
|
||||||
|
];
|
||||||
|
|
||||||
|
const [operator, ...rest] = str.trim().split(' ');
|
||||||
|
const value = rest.join(' ');
|
||||||
|
|
||||||
|
if (unaryOperators.includes(operator)) {
|
||||||
|
return {
|
||||||
|
operator,
|
||||||
|
value: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operators.includes(operator)) {
|
||||||
|
return {
|
||||||
|
operator,
|
||||||
|
value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
operator: 'eq',
|
||||||
|
value: str
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const isUnaryOperator = (operator) => {
|
||||||
|
const unaryOperators = [
|
||||||
|
'isEmpty',
|
||||||
|
'isNull',
|
||||||
|
'isUndefined',
|
||||||
|
'isDefined',
|
||||||
|
'isTruthy',
|
||||||
|
'isFalsy',
|
||||||
|
'isJson',
|
||||||
|
'isNumber',
|
||||||
|
'isString',
|
||||||
|
'isBoolean',
|
||||||
|
'isArray'
|
||||||
|
];
|
||||||
|
|
||||||
|
return unaryOperators.includes(operator);
|
||||||
|
};
|
||||||
|
|
||||||
const Assertions = ({ item, collection }) => {
|
const Assertions = ({ item, collection }) => {
|
||||||
|
const { storedTheme } = useTheme();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const assertions = item.draft ? get(item, 'draft.request.assertions') : get(item, 'request.assertions');
|
const assertions = item.draft ? get(item, 'draft.request.assertions') : get(item, 'request.assertions');
|
||||||
|
|
||||||
@ -57,36 +187,108 @@ const Assertions = ({ item, collection }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAssertionDrag = ({ updateReorderedItem }) => {
|
||||||
|
dispatch(
|
||||||
|
moveAssertion({
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
itemUid: item.uid,
|
||||||
|
updateReorderedItem
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="w-full">
|
<StyledWrapper className="w-full">
|
||||||
<table>
|
<Table
|
||||||
<thead>
|
headers={[
|
||||||
<tr>
|
{ name: 'Expr', width: '37%' },
|
||||||
<td>Expr</td>
|
{ name: 'Operator', width: '30%' },
|
||||||
<td>Operator</td>
|
{ name: 'Value', width: '20%' },
|
||||||
<td>Value</td>
|
{ name: '', width: '13%' }
|
||||||
<td></td>
|
]}
|
||||||
</tr>
|
>
|
||||||
</thead>
|
<ReorderTable updateReorderedItem={handleAssertionDrag}>
|
||||||
<tbody>
|
|
||||||
{assertions && assertions.length
|
{assertions && assertions.length
|
||||||
? assertions.map((assertion) => {
|
? assertions.map((assertion) => {
|
||||||
|
const { operator, value } = parseAssertionOperator(assertion.value);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AssertionRow
|
<tr key={assertion.uid} data-uid={assertion.uid}>
|
||||||
key={assertion.uid}
|
<td className="flex relative">
|
||||||
assertion={assertion}
|
<input
|
||||||
item={item}
|
type="text"
|
||||||
collection={collection}
|
autoComplete="off"
|
||||||
handleAssertionChange={handleAssertionChange}
|
autoCorrect="off"
|
||||||
handleRemoveAssertion={handleRemoveAssertion}
|
autoCapitalize="off"
|
||||||
onSave={onSave}
|
spellCheck="false"
|
||||||
handleRun={handleRun}
|
value={assertion.name}
|
||||||
|
className="mousetrap"
|
||||||
|
onChange={(e) => handleAssertionChange(e, assertion, 'name')}
|
||||||
/>
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<AssertionOperator
|
||||||
|
operator={operator}
|
||||||
|
onChange={(op) =>
|
||||||
|
handleAssertionChange(
|
||||||
|
{
|
||||||
|
target: {
|
||||||
|
value: `${op} ${value}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
assertion,
|
||||||
|
'value'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{!isUnaryOperator(operator) ? (
|
||||||
|
<SingleLineEditor
|
||||||
|
value={value}
|
||||||
|
theme={storedTheme}
|
||||||
|
readOnly={true}
|
||||||
|
onSave={onSave}
|
||||||
|
onChange={(newValue) =>
|
||||||
|
handleAssertionChange(
|
||||||
|
{
|
||||||
|
target: {
|
||||||
|
value: `${operator} ${newValue}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
assertion,
|
||||||
|
'value'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onRun={handleRun}
|
||||||
|
collection={collection}
|
||||||
|
item={item}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<input type="text" className="cursor-default" disabled />
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={assertion.enabled}
|
||||||
|
tabIndex="-1"
|
||||||
|
className="mr-3 mousetrap"
|
||||||
|
onChange={(e) => handleAssertionChange(e, assertion, 'enabled')}
|
||||||
|
/>
|
||||||
|
<button tabIndex="-1" onClick={() => handleRemoveAssertion(assertion)}>
|
||||||
|
<IconTrash strokeWidth={1.5} size={20} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
: null}
|
: null}
|
||||||
</tbody>
|
</ReorderTable>
|
||||||
</table>
|
</Table>
|
||||||
|
|
||||||
<button className="btn-add-assertion text-link pr-2 py-3 mt-2 select-none" onClick={handleAddAssertion}>
|
<button className="btn-add-assertion text-link pr-2 py-3 mt-2 select-none" onClick={handleAddAssertion}>
|
||||||
+ Add Assertion
|
+ Add Assertion
|
||||||
</button>
|
</button>
|
||||||
|
@ -986,6 +986,26 @@ export const collectionsSlice = createSlice({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
moveAssertion: (state, action) => {
|
||||||
|
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||||
|
|
||||||
|
if (collection) {
|
||||||
|
const item = findItemInCollection(collection, action.payload.itemUid);
|
||||||
|
|
||||||
|
if (item && isItemARequest(item)) {
|
||||||
|
if (!item.draft) {
|
||||||
|
item.draft = cloneDeep(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { updateReorderedItem } = action.payload;
|
||||||
|
const assertions = item.draft.request.assertions;
|
||||||
|
|
||||||
|
item.draft.request.assertions = updateReorderedItem.map((uid) => {
|
||||||
|
return assertions.find((assertion) => assertion.uid === uid);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
updateAssertion: (state, action) => {
|
updateAssertion: (state, action) => {
|
||||||
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||||
|
|
||||||
@ -1790,6 +1810,7 @@ export const {
|
|||||||
updateRequestTests,
|
updateRequestTests,
|
||||||
updateRequestMethod,
|
updateRequestMethod,
|
||||||
addAssertion,
|
addAssertion,
|
||||||
|
moveAssertion,
|
||||||
updateAssertion,
|
updateAssertion,
|
||||||
deleteAssertion,
|
deleteAssertion,
|
||||||
addVar,
|
addVar,
|
||||||
|
Loading…
Reference in New Issue
Block a user