mirror of
https://github.com/usebruno/bruno.git
synced 2024-11-07 16:44:27 +01:00
Merge pull request #791 from Its-treason/feature/variable-name-validation
Enforce variable strictness & validation
This commit is contained in:
commit
1e435b8e10
@ -13,10 +13,12 @@ const Wrapper = styled.div`
|
||||
padding: 4px 10px;
|
||||
|
||||
&:nth-child(1),
|
||||
&:nth-child(4),
|
||||
&:nth-child(5) {
|
||||
&:nth-child(4) {
|
||||
width: 70px;
|
||||
}
|
||||
&:nth-child(5) {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
width: 25%;
|
||||
|
@ -1,68 +1,79 @@
|
||||
import React, { useReducer } from 'react';
|
||||
import React from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { IconTrash } from '@tabler/icons';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { saveEnvironment } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import reducer from './reducer';
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import { useFormik } from 'formik';
|
||||
import * as Yup from 'yup';
|
||||
import { uuid } from 'utils/common';
|
||||
|
||||
const EnvironmentVariables = ({ environment, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { storedTheme } = useTheme();
|
||||
const [state, reducerDispatch] = useReducer(reducer, { hasChanges: false, variables: environment.variables || [] });
|
||||
const { variables, hasChanges } = state;
|
||||
|
||||
const saveChanges = () => {
|
||||
dispatch(saveEnvironment(cloneDeep(variables), environment.uid, collection.uid))
|
||||
.then(() => {
|
||||
toast.success('Changes saved successfully');
|
||||
reducerDispatch({
|
||||
type: 'CHANGES_SAVED'
|
||||
});
|
||||
const formik = useFormik({
|
||||
enableReinitialize: true,
|
||||
initialValues: environment.variables || [],
|
||||
validationSchema: Yup.array().of(
|
||||
Yup.object({
|
||||
enabled: Yup.boolean(),
|
||||
name: Yup.string()
|
||||
.required('Name cannot be empty')
|
||||
.matches(/^(?!\d)\w*$/, 'Name contains invalid characters')
|
||||
.trim(),
|
||||
secret: Yup.boolean(),
|
||||
type: Yup.string(),
|
||||
uid: Yup.string(),
|
||||
value: Yup.string().trim()
|
||||
})
|
||||
.catch(() => toast.error('An error occurred while saving the changes'));
|
||||
),
|
||||
onSubmit: (values) => {
|
||||
if (!formik.dirty) {
|
||||
toast.error('Nothing to save');
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(saveEnvironment(cloneDeep(values), environment.uid, collection.uid))
|
||||
.then(() => {
|
||||
toast.success('Changes saved successfully');
|
||||
formik.resetForm({ values });
|
||||
})
|
||||
.catch(() => toast.error('An error occurred while saving the changes'));
|
||||
}
|
||||
});
|
||||
|
||||
const ErrorMessage = ({ name }) => {
|
||||
const meta = formik.getFieldMeta(name);
|
||||
console.log(name, meta);
|
||||
if (!meta.error) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<label htmlFor={name} className="text-red-500">
|
||||
{meta.error}
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
||||
const addVariable = () => {
|
||||
reducerDispatch({
|
||||
type: 'ADD_VAR'
|
||||
});
|
||||
const newVariable = {
|
||||
uid: uuid(),
|
||||
name: '',
|
||||
value: '',
|
||||
type: 'text',
|
||||
secret: false,
|
||||
enabled: true
|
||||
};
|
||||
formik.setFieldValue(formik.values.length, newVariable, false);
|
||||
};
|
||||
|
||||
const handleVarChange = (e, _variable, type) => {
|
||||
const variable = cloneDeep(_variable);
|
||||
switch (type) {
|
||||
case 'name': {
|
||||
variable.name = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'value': {
|
||||
variable.value = e.target.value;
|
||||
break;
|
||||
}
|
||||
case 'enabled': {
|
||||
variable.enabled = e.target.checked;
|
||||
break;
|
||||
}
|
||||
case 'secret': {
|
||||
variable.secret = e.target.checked;
|
||||
break;
|
||||
}
|
||||
}
|
||||
reducerDispatch({
|
||||
type: 'UPDATE_VAR',
|
||||
variable
|
||||
});
|
||||
};
|
||||
|
||||
const handleRemoveVars = (variable) => {
|
||||
reducerDispatch({
|
||||
type: 'DELETE_VAR',
|
||||
variable
|
||||
});
|
||||
const handleRemoveVar = (id) => {
|
||||
formik.setValues(formik.values.filter((variable) => variable.uid !== id));
|
||||
};
|
||||
|
||||
return (
|
||||
@ -78,55 +89,57 @@ const EnvironmentVariables = ({ environment, collection }) => {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{variables && variables.length
|
||||
? variables.map((variable, index) => {
|
||||
return (
|
||||
<tr key={variable.uid}>
|
||||
<td className="text-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={variable.enabled}
|
||||
className="mr-3 mousetrap"
|
||||
onChange={(e) => handleVarChange(e, variable, 'enabled')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
value={variable.name}
|
||||
className="mousetrap"
|
||||
onChange={(e) => handleVarChange(e, variable, 'name')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<SingleLineEditor
|
||||
value={variable.value}
|
||||
theme={storedTheme}
|
||||
onChange={(newValue) => handleVarChange({ target: { value: newValue } }, variable, 'value')}
|
||||
collection={collection}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={variable.secret}
|
||||
className="mr-3 mousetrap"
|
||||
onChange={(e) => handleVarChange(e, variable, 'secret')}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<button onClick={() => handleRemoveVars(variable)}>
|
||||
<IconTrash strokeWidth={1.5} size={20} />
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
{formik.values.map((variable, index) => (
|
||||
<tr key={variable.uid}>
|
||||
<td className="text-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="mr-3 mousetrap"
|
||||
name={`${index}.enabled`}
|
||||
checked={variable.enabled}
|
||||
onChange={formik.handleChange}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
autoCorrect="off"
|
||||
autoCapitalize="off"
|
||||
spellCheck="false"
|
||||
className="mousetrap"
|
||||
id={`${index}.name`}
|
||||
name={`${index}.name`}
|
||||
value={formik.values[index].name}
|
||||
onChange={formik.handleChange}
|
||||
/>
|
||||
<ErrorMessage name={`${index}.name`} />
|
||||
</td>
|
||||
<td>
|
||||
<SingleLineEditor
|
||||
theme={storedTheme}
|
||||
collection={collection}
|
||||
name={`${index}.value`}
|
||||
value={variable.value}
|
||||
onChange={(newValue) => formik.setFieldValue(`${index}.value`, newValue, true)}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="mr-3 mousetrap"
|
||||
name={`${index}.secret`}
|
||||
checked={variable.secret}
|
||||
onChange={formik.handleChange}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<button onClick={() => handleRemoveVar(variable.uid)}>
|
||||
<IconTrash strokeWidth={1.5} size={20} />
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@ -137,12 +150,7 @@ const EnvironmentVariables = ({ environment, collection }) => {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="submit"
|
||||
className="submit btn btn-md btn-secondary mt-2"
|
||||
disabled={!hasChanges}
|
||||
onClick={saveChanges}
|
||||
>
|
||||
<button type="submit" className="submit btn btn-md btn-secondary mt-2" onClick={formik.handleSubmit}>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
|
@ -1,52 +0,0 @@
|
||||
import produce from 'immer';
|
||||
import find from 'lodash/find';
|
||||
import filter from 'lodash/filter';
|
||||
import { uuid } from 'utils/common';
|
||||
|
||||
const reducer = (state, action) => {
|
||||
switch (action.type) {
|
||||
case 'ADD_VAR': {
|
||||
return produce(state, (draft) => {
|
||||
draft.variables.push({
|
||||
uid: uuid(),
|
||||
name: '',
|
||||
value: '',
|
||||
type: 'text',
|
||||
secret: false,
|
||||
enabled: true
|
||||
});
|
||||
draft.hasChanges = true;
|
||||
});
|
||||
}
|
||||
|
||||
case 'UPDATE_VAR': {
|
||||
return produce(state, (draft) => {
|
||||
const variable = find(draft.variables, (v) => v.uid === action.variable.uid);
|
||||
variable.name = action.variable.name;
|
||||
variable.value = action.variable.value;
|
||||
variable.enabled = action.variable.enabled;
|
||||
variable.secret = action.variable.secret;
|
||||
draft.hasChanges = true;
|
||||
});
|
||||
}
|
||||
|
||||
case 'DELETE_VAR': {
|
||||
return produce(state, (draft) => {
|
||||
draft.variables = filter(draft.variables, (v) => v.uid !== action.variable.uid);
|
||||
draft.hasChanges = true;
|
||||
});
|
||||
}
|
||||
|
||||
case 'CHANGES_SAVED': {
|
||||
return produce(state, (draft) => {
|
||||
draft.hasChanges = false;
|
||||
});
|
||||
}
|
||||
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default reducer;
|
@ -8,6 +8,7 @@ import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collection
|
||||
import SingleLineEditor from 'components/SingleLineEditor';
|
||||
import Tooltip from 'components/Tooltip';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
const VarsTable = ({ item, collection, vars, varType }) => {
|
||||
const dispatch = useDispatch();
|
||||
@ -29,7 +30,19 @@ const VarsTable = ({ item, collection, vars, varType }) => {
|
||||
const _var = cloneDeep(v);
|
||||
switch (type) {
|
||||
case 'name': {
|
||||
_var.name = e.target.value;
|
||||
const value = e.target.value;
|
||||
|
||||
if (/^(?!\d).*$/.test(value) === false) {
|
||||
toast.error('Variable names must not start with a number!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (/^\w*$/.test(value) === false) {
|
||||
toast.error('Variable contains invalid character! Variables must only contain alpha-numeric characters.');
|
||||
return;
|
||||
}
|
||||
|
||||
_var.name = value;
|
||||
break;
|
||||
}
|
||||
case 'value': {
|
||||
@ -88,7 +101,7 @@ const VarsTable = ({ item, collection, vars, varType }) => {
|
||||
</thead>
|
||||
<tbody>
|
||||
{vars && vars.length
|
||||
? vars.map((_var, index) => {
|
||||
? vars.map((_var) => {
|
||||
return (
|
||||
<tr key={_var.uid}>
|
||||
<td>
|
||||
|
@ -1,42 +1,12 @@
|
||||
import React from 'react';
|
||||
import get from 'lodash/get';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { updateRequestScript, updateResponseScript } from 'providers/ReduxStore/slices/collections';
|
||||
import { sendRequest, saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||
import { useTheme } from 'providers/Theme';
|
||||
import VarsTable from './VarsTable';
|
||||
import StyledWrapper from './StyledWrapper';
|
||||
|
||||
const Vars = ({ item, collection }) => {
|
||||
const dispatch = useDispatch();
|
||||
const requestVars = item.draft ? get(item, 'draft.request.vars.req') : get(item, 'request.vars.req');
|
||||
const responseVars = item.draft ? get(item, 'draft.request.vars.res') : get(item, 'request.vars.res');
|
||||
|
||||
const { storedTheme } = useTheme();
|
||||
|
||||
const onRequestScriptEdit = (value) => {
|
||||
dispatch(
|
||||
updateRequestScript({
|
||||
script: value,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const onResponseScriptEdit = (value) => {
|
||||
dispatch(
|
||||
updateResponseScript({
|
||||
script: value,
|
||||
itemUid: item.uid,
|
||||
collectionUid: collection.uid
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const onRun = () => dispatch(sendRequest(item, collection.uid));
|
||||
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||
|
||||
return (
|
||||
<StyledWrapper className="w-full flex flex-col">
|
||||
<div className="flex-1 mt-2">
|
||||
|
@ -59,10 +59,24 @@ class Bru {
|
||||
throw new Error('Key is required');
|
||||
}
|
||||
|
||||
if (/^(?!\d)\w*$/.test(key) === false) {
|
||||
throw new Error(
|
||||
`Variable name: "${key}" contains invalid characters!` +
|
||||
' Names must only contain alpha-numeric characters and cannot start with a digit.'
|
||||
);
|
||||
}
|
||||
|
||||
this.collectionVariables[key] = value;
|
||||
}
|
||||
|
||||
getVar(key) {
|
||||
if (/^(?!\d)\w*$/.test(key) === false) {
|
||||
throw new Error(
|
||||
`Variable name: "${key}" contains invalid characters!` +
|
||||
' Names must only contain alpha-numeric characters and cannot start with a digit.'
|
||||
);
|
||||
}
|
||||
|
||||
return this.collectionVariables[key];
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ body:sparql {
|
||||
|
||||
body:form-urlencoded {
|
||||
apikey: secret
|
||||
numbers: +91998877665
|
||||
numbers: %2B91998877665
|
||||
~message: hello
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user