feat(#682): Enforce environment variable strictness

This commit is contained in:
Its-treason 2023-10-22 21:54:17 +02:00
parent 564825868e
commit 7267ba6485
3 changed files with 113 additions and 155 deletions

View File

@ -13,10 +13,12 @@ const Wrapper = styled.div`
padding: 4px 10px; padding: 4px 10px;
&:nth-child(1), &:nth-child(1),
&:nth-child(4), &:nth-child(4) {
&:nth-child(5) {
width: 70px; width: 70px;
} }
&:nth-child(5) {
width: 40px;
}
&:nth-child(2) { &:nth-child(2) {
width: 25%; width: 25%;

View File

@ -1,68 +1,79 @@
import React, { useReducer } from 'react'; import React from 'react';
import toast from 'react-hot-toast'; import toast from 'react-hot-toast';
import cloneDeep from 'lodash/cloneDeep'; import cloneDeep from 'lodash/cloneDeep';
import { IconTrash } from '@tabler/icons'; import { IconTrash } from '@tabler/icons';
import { useTheme } from 'providers/Theme'; import { useTheme } from 'providers/Theme';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { saveEnvironment } from 'providers/ReduxStore/slices/collections/actions'; import { saveEnvironment } from 'providers/ReduxStore/slices/collections/actions';
import reducer from './reducer';
import SingleLineEditor from 'components/SingleLineEditor'; import SingleLineEditor from 'components/SingleLineEditor';
import StyledWrapper from './StyledWrapper'; import StyledWrapper from './StyledWrapper';
import { useFormik } from 'formik';
import * as Yup from 'yup';
import { uuid } from 'utils/common';
const EnvironmentVariables = ({ environment, collection }) => { const EnvironmentVariables = ({ environment, collection }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { storedTheme } = useTheme(); const { storedTheme } = useTheme();
const [state, reducerDispatch] = useReducer(reducer, { hasChanges: false, variables: environment.variables || [] });
const { variables, hasChanges } = state;
const saveChanges = () => { const formik = useFormik({
dispatch(saveEnvironment(cloneDeep(variables), environment.uid, collection.uid)) enableReinitialize: true,
.then(() => { initialValues: environment.variables || [],
toast.success('Changes saved successfully'); validationSchema: Yup.array().of(
reducerDispatch({ Yup.object({
type: 'CHANGES_SAVED' 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 = () => { const addVariable = () => {
reducerDispatch({ const newVariable = {
type: 'ADD_VAR' uid: uuid(),
}); name: '',
value: '',
type: 'text',
secret: false,
enabled: true
};
formik.setFieldValue(formik.values.length, newVariable, false);
}; };
const handleVarChange = (e, _variable, type) => { const handleRemoveVar = (id) => {
const variable = cloneDeep(_variable); formik.setValues(formik.values.filter((variable) => variable.uid !== id));
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
});
}; };
return ( return (
@ -78,55 +89,57 @@ const EnvironmentVariables = ({ environment, collection }) => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{variables && variables.length {formik.values.map((variable, index) => (
? variables.map((variable, index) => { <tr key={variable.uid}>
return ( <td className="text-center">
<tr key={variable.uid}> <input
<td className="text-center"> type="checkbox"
<input className="mr-3 mousetrap"
type="checkbox" name={`${index}.enabled`}
checked={variable.enabled} checked={variable.enabled}
className="mr-3 mousetrap" onChange={formik.handleChange}
onChange={(e) => handleVarChange(e, variable, 'enabled')} />
/> </td>
</td> <td>
<td> <input
<input type="text"
type="text" autoComplete="off"
autoComplete="off" autoCorrect="off"
autoCorrect="off" autoCapitalize="off"
autoCapitalize="off" spellCheck="false"
spellCheck="false" className="mousetrap"
value={variable.name} id={`${index}.name`}
className="mousetrap" name={`${index}.name`}
onChange={(e) => handleVarChange(e, variable, 'name')} value={formik.values[index].name}
/> onChange={formik.handleChange}
</td> />
<td> <ErrorMessage name={`${index}.name`} />
<SingleLineEditor </td>
value={variable.value} <td>
theme={storedTheme} <SingleLineEditor
onChange={(newValue) => handleVarChange({ target: { value: newValue } }, variable, 'value')} theme={storedTheme}
collection={collection} collection={collection}
/> name={`${index}.value`}
</td> value={variable.value}
<td> onChange={(newValue) => formik.setFieldValue(`${index}.value`, newValue, true)}
<input />
type="checkbox" </td>
checked={variable.secret} <td>
className="mr-3 mousetrap" <input
onChange={(e) => handleVarChange(e, variable, 'secret')} type="checkbox"
/> className="mr-3 mousetrap"
</td> name={`${index}.secret`}
<td> checked={variable.secret}
<button onClick={() => handleRemoveVars(variable)}> onChange={formik.handleChange}
<IconTrash strokeWidth={1.5} size={20} /> />
</button> </td>
</td> <td>
</tr> <button onClick={() => handleRemoveVar(variable.uid)}>
); <IconTrash strokeWidth={1.5} size={20} />
}) </button>
: null} </td>
</tr>
))}
</tbody> </tbody>
</table> </table>
@ -137,12 +150,7 @@ const EnvironmentVariables = ({ environment, collection }) => {
</div> </div>
<div> <div>
<button <button type="submit" className="submit btn btn-md btn-secondary mt-2" onClick={formik.handleSubmit}>
type="submit"
className="submit btn btn-md btn-secondary mt-2"
disabled={!hasChanges}
onClick={saveChanges}
>
Save Save
</button> </button>
</div> </div>

View File

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