Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: global env #3222

Merged
merged 8 commits into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/bruno-app/src/components/CodeEditor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ if (!SERVER_RENDERED) {
'bru.setNextRequest(requestName)',
'req.disableParsingResponseJson()',
'bru.getRequestVar(key)',
'bru.sleep(ms)'
'bru.sleep(ms)',
'bru.getGlobalEnvVar(key)',
'bru.setGlobalEnvVar(key, value)'
];
CodeMirror.registerHelper('hint', 'brunoJS', (editor, options) => {
const cursor = editor.getCursor();
Expand Down
7 changes: 7 additions & 0 deletions packages/bruno-app/src/components/Dropdown/StyledWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ const Wrapper = styled.div`
padding: 0.35rem 0.6rem;
cursor: pointer;

&.active {
color: ${(props) => props.theme.colors.text.yellow} !important;
.icon {
color: ${(props) => props.theme.colors.text.yellow} !important;
}
}

.icon {
color: ${(props) => props.theme.dropdown.iconColor};
}
Expand Down
4 changes: 2 additions & 2 deletions packages/bruno-app/src/components/Dropdown/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import React from 'react';
import Tippy from '@tippyjs/react';
import StyledWrapper from './StyledWrapper';

const Dropdown = ({ icon, children, onCreate, placement }) => {
const Dropdown = ({ icon, children, onCreate, placement, transparent }) => {
return (
<StyledWrapper className="dropdown">
<StyledWrapper className="dropdown" transparent={transparent}>
<Tippy
content={children}
placement={placement || 'bottom-end'}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,11 @@ const EnvironmentSelector = ({ collection }) => {
<StyledWrapper>
<div className="flex items-center cursor-pointer environment-selector">
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
<div className="label-item font-medium">Collection Environments</div>
{environments && environments.length
? environments.map((e) => (
<div
className="dropdown-item"
className={`dropdown-item ${e?.uid === activeEnvironmentUid ? 'active' : ''}`}
key={e.uid}
onClick={() => {
onSelect(e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const EnvironmentVariables = ({ environment, collection, setIsModified, original

const ErrorMessage = ({ name }) => {
const meta = formik.getFieldMeta(name);
if (!meta.error) {
if (!meta.error || !meta.touched) {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import styled from 'styled-components';

const Wrapper = styled.div`
.current-environment {
}
.environment-active {
padding: 0.3rem 0.4rem;
color: ${(props) => props.theme.colors.text.yellow};
border: solid 1px ${(props) => props.theme.colors.text.yellow} !important;
}
.environment-selector {
.active: {
color: ${(props) => props.theme.colors.text.yellow};
}
}
`;

export default Wrapper;
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import React, { useRef, forwardRef, useState } from 'react';
import find from 'lodash/find';
import Dropdown from 'components/Dropdown';
import { IconSettings, IconWorld, IconDatabase, IconDatabaseOff, IconCheck } from '@tabler/icons';
import EnvironmentSettings from '../EnvironmentSettings';
import toast from 'react-hot-toast';
import { useDispatch, useSelector } from 'react-redux';
import StyledWrapper from './StyledWrapper';
import { selectGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';

const EnvironmentSelector = () => {
const dispatch = useDispatch();
const dropdownTippyRef = useRef();
const globalEnvironments = useSelector((state) => state.globalEnvironments.globalEnvironments);
const activeGlobalEnvironmentUid = useSelector((state) => state.globalEnvironments.activeGlobalEnvironmentUid);
const [openSettingsModal, setOpenSettingsModal] = useState(false);
const activeEnvironment = activeGlobalEnvironmentUid ? find(globalEnvironments, (e) => e.uid === activeGlobalEnvironmentUid) : null;

const Icon = forwardRef((props, ref) => {
return (
<div ref={ref} className={`current-environment flex flex-row gap-1 rounded-xl text-xs cursor-pointer items-center justify-center select-none ${activeGlobalEnvironmentUid? 'environment-active': ''}`}>
<IconWorld className="globe" size={16} strokeWidth={1.5} />
{
activeEnvironment ? <div>{activeEnvironment?.name}</div> : null
}
</div>
);
});

const handleSettingsIconClick = () => {
setOpenSettingsModal(true);
};

const handleModalClose = () => {
setOpenSettingsModal(false);
};

const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);

const onSelect = (environment) => {
dispatch(selectGlobalEnvironment({ environmentUid: environment ? environment.uid : null }))
.then(() => {
if (environment) {
toast.success(`Environment changed to ${environment.name}`);
} else {
toast.success(`No Environments are active now`);
}
})
.catch((err) => console.log(err) && toast.error('An error occurred while selecting the environment'));
};

return (
<StyledWrapper>
<div className="flex items-center cursor-pointer environment-selector mr-3">
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end" transparent={true}>
<div className="label-item font-medium">Global Environments</div>
{globalEnvironments && globalEnvironments.length
? globalEnvironments.map((e) => (
<div
className={`dropdown-item ${e?.uid === activeGlobalEnvironmentUid ? 'active' : ''}`}
key={e.uid}
onClick={() => {
onSelect(e);
dropdownTippyRef.current.hide();
}}
>
<IconDatabase size={18} strokeWidth={1.5} /> <span className="ml-2 break-all">{e.name}</span>
</div>
))
: null}
<div
className="dropdown-item"
onClick={() => {
dropdownTippyRef.current.hide();
onSelect(null);
}}
>
<IconDatabaseOff size={18} strokeWidth={1.5} />
<span className="ml-2">No Environment</span>
</div>
<div className="dropdown-item border-top" onClick={handleSettingsIconClick}>
<div className="pr-2 text-gray-600">
<IconSettings size={18} strokeWidth={1.5} />
</div>
<span>Configure</span>
</div>
</Dropdown>
</div>
{openSettingsModal && <EnvironmentSettings globalEnvironments={globalEnvironments} activeGlobalEnvironmentUid={activeGlobalEnvironmentUid} onClose={handleModalClose} />}
</StyledWrapper>
);
};

export default EnvironmentSelector;
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import Modal from 'components/Modal/index';
import Portal from 'components/Portal/index';
import { useFormik } from 'formik';
import { copyGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';
import { useEffect, useRef } from 'react';
import toast from 'react-hot-toast';
import { useDispatch } from 'react-redux';
import * as Yup from 'yup';

const CopyEnvironment = ({ environment, onClose }) => {
const dispatch = useDispatch();
const inputRef = useRef();
const formik = useFormik({
enableReinitialize: true,
initialValues: {
name: environment.name + ' - Copy'
},
validationSchema: Yup.object({
name: Yup.string()
.min(1, 'must be at least 1 character')
.max(50, 'must be 50 characters or less')
.required('name is required')
}),
onSubmit: (values) => {
dispatch(copyGlobalEnvironment({ name: values.name, environmentUid: environment.uid }))
.then(() => {
toast.success('Global environment created!');
onClose();
})
.catch((error) => {
toast.error('An error occurred while created the environment');
console.error(error);
});
}
});

useEffect(() => {
if (inputRef && inputRef.current) {
inputRef.current.focus();
}
}, [inputRef]);

const onSubmit = () => {
formik.handleSubmit();
};

return (
<Portal>
<Modal size="sm" title={'Copy Global Environment'} confirmText="Copy" handleConfirm={onSubmit} handleCancel={onClose}>
<form className="bruno-form" onSubmit={e => e.preventDefault()}>
<div>
<label htmlFor="name" className="block font-semibold">
New Environment Name
</label>
<input
id="environment-name"
type="text"
name="name"
ref={inputRef}
className="block textbox mt-2 w-full"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.name || ''}
/>
{formik.touched.name && formik.errors.name ? (
<div className="text-red-500">{formik.errors.name}</div>
) : null}
</div>
</form>
</Modal>
</Portal>
);
};

export default CopyEnvironment;
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React, { useEffect, useRef } from 'react';
import toast from 'react-hot-toast';
import { useFormik } from 'formik';
import * as Yup from 'yup';
import { useDispatch } from 'react-redux';
import Portal from 'components/Portal';
import Modal from 'components/Modal';
import { addGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';

const CreateEnvironment = ({ onClose }) => {
const dispatch = useDispatch();
const inputRef = useRef();
const formik = useFormik({
enableReinitialize: true,
initialValues: {
name: ''
},
validationSchema: Yup.object({
name: Yup.string()
.min(1, 'must be at least 1 character')
.max(50, 'must be 50 characters or less')
.required('name is required')
}),
onSubmit: (values) => {
dispatch(addGlobalEnvironment({ name: values.name }))
.then(() => {
toast.success('Global environment created!');
onClose();
})
.catch(() => toast.error('An error occurred while creating the environment'));
}
});

useEffect(() => {
if (inputRef && inputRef.current) {
inputRef.current.focus();
}
}, [inputRef]);

const onSubmit = () => {
formik.handleSubmit();
};

return (
<Portal>
<Modal
size="sm"
title={'Create Global Environment'}
confirmText="Create"
handleConfirm={onSubmit}
handleCancel={onClose}
>
<form className="bruno-form" onSubmit={e => e.preventDefault()}>
<div>
<label htmlFor="name" className="block font-semibold">
Environment Name
</label>
<div className="flex items-center mt-2">
<input
id="environment-name"
type="text"
name="name"
ref={inputRef}
className="block textbox w-full"
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
spellCheck="false"
onChange={formik.handleChange}
value={formik.values.name || ''}
/>
</div>
{formik.touched.name && formik.errors.name ? (
<div className="text-red-500">{formik.errors.name}</div>
) : null}
</div>
</form>
</Modal>
</Portal>
);
};

export default CreateEnvironment;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import styled from 'styled-components';

const Wrapper = styled.div`
button.submit {
color: white;
background-color: var(--color-background-danger) !important;
border: inherit !important;

&:hover {
border: inherit !important;
}
}
`;

export default Wrapper;
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';
import Portal from 'components/Portal/index';
import toast from 'react-hot-toast';
import Modal from 'components/Modal/index';
import { useDispatch } from 'react-redux';
import StyledWrapper from './StyledWrapper';
import { deleteGlobalEnvironment } from 'providers/ReduxStore/slices/global-environments';

const DeleteEnvironment = ({ onClose, environment }) => {
const dispatch = useDispatch();
const onConfirm = () => {
dispatch(deleteGlobalEnvironment({ environmentUid: environment.uid }))
.then(() => {
toast.success('Global Environment deleted successfully');
onClose();
})
.catch(() => toast.error('An error occurred while deleting the environment'));
};

return (
<Portal>
<StyledWrapper>
<Modal
size="sm"
title={'Delete Global Environment'}
confirmText="Delete"
handleConfirm={onConfirm}
handleCancel={onClose}
>
Are you sure you want to delete <span className="font-semibold">{environment.name}</span> ?
</Modal>
</StyledWrapper>
</Portal>
);
};

export default DeleteEnvironment;
Loading