mirror of
https://github.com/Kong/insomnia.git
synced 2026-04-22 23:28:33 -04:00
Environments data routing (#6127)
* update environments to use data routing * upgrade react-router for json api in actions * check for the proper route in the modal * remove ? * fix git url construction * fix lint issues * logs
This commit is contained in:
23
packages/insomnia/package-lock.json
generated
23
packages/insomnia/package-lock.json
generated
@@ -140,7 +140,7 @@
|
||||
"react-dnd-html5-backend": "^7.4.4",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-redux": "^7.2.6",
|
||||
"react-router-dom": "^6.4.2",
|
||||
"react-router-dom": "^6.14.1",
|
||||
"react-stately": "3.21.0",
|
||||
"react-use": "^17.4.0",
|
||||
"redux": "^4.1.2",
|
||||
@@ -4471,9 +4471,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@remix-run/router": {
|
||||
"version": "1.0.2",
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.7.1.tgz",
|
||||
"integrity": "sha512-bgVQM4ZJ2u2CM8k1ey70o1ePFXsEzYVZoWghh6WjM8p59jQ7HxzbHW4SbnWFG7V9ig9chLawQxDTZ3xzOF8MkQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
@@ -15958,11 +15959,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "6.4.2",
|
||||
"version": "6.14.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.14.1.tgz",
|
||||
"integrity": "sha512-U4PfgvG55LdvbQjg5Y9QRWyVxIdO1LlpYT7x+tMAxd9/vmiPuJhIwdxZuIQLN/9e3O4KFDHYfR9gzGeYMasW8g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.0.2"
|
||||
"@remix-run/router": "1.7.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
@@ -15972,12 +15974,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "6.4.2",
|
||||
"version": "6.14.1",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.14.1.tgz",
|
||||
"integrity": "sha512-ssF6M5UkQjHK70fgukCJyjlda0Dgono2QGwqGvuk7D+EDGHdacEN3Yke2LTMjkrpHuFwBfDFsEjGVXBDmL+bWw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.0.2",
|
||||
"react-router": "6.4.2"
|
||||
"@remix-run/router": "1.7.1",
|
||||
"react-router": "6.14.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
|
||||
@@ -189,7 +189,7 @@
|
||||
"react-dnd-html5-backend": "^7.4.4",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-redux": "^7.2.6",
|
||||
"react-router-dom": "^6.4.2",
|
||||
"react-router-dom": "^6.14.1",
|
||||
"react-stately": "3.21.0",
|
||||
"react-use": "^17.4.0",
|
||||
"redux": "^4.1.2",
|
||||
|
||||
@@ -30,8 +30,8 @@ export const httpClient = {
|
||||
}
|
||||
|
||||
return {
|
||||
url: response.request.res.responseUrl,
|
||||
method: response.request.method,
|
||||
url: config.url,
|
||||
method: config.method,
|
||||
headers: response.headers,
|
||||
body: [response.data],
|
||||
statusCode: response.status,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React, { FC, useCallback, useRef } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useFetcher, useParams, useRouteLoaderData } from 'react-router-dom';
|
||||
|
||||
import * as models from '../../../models';
|
||||
import type { Environment } from '../../../models/environment';
|
||||
import { selectActiveWorkspaceMeta, selectEnvironments, selectHotKeyRegistry } from '../../redux/selectors';
|
||||
import { selectHotKeyRegistry } from '../../redux/selectors';
|
||||
import { WorkspaceLoaderData } from '../../routes/workspace';
|
||||
import { Dropdown, DropdownButton, type DropdownHandle, DropdownItem, DropdownSection, ItemContent } from '../base/dropdown';
|
||||
import { useDocBodyKeyboardShortcuts } from '../keydown-binder';
|
||||
import { showModal } from '../modals/index';
|
||||
@@ -15,13 +16,17 @@ interface Props {
|
||||
workspaceId: string;
|
||||
}
|
||||
|
||||
export const EnvironmentsDropdown: FC<Props> = ({
|
||||
activeEnvironment,
|
||||
workspaceId,
|
||||
}) => {
|
||||
const environments = useSelector(selectEnvironments);
|
||||
export const EnvironmentsDropdown: FC<Props> = () => {
|
||||
const { organizationId, projectId, workspaceId } = useParams<{ organizationId: string; projectId: string; workspaceId: string}>();
|
||||
const {
|
||||
baseEnvironment,
|
||||
activeEnvironment,
|
||||
subEnvironments,
|
||||
} = useRouteLoaderData(
|
||||
':workspaceId'
|
||||
) as WorkspaceLoaderData;
|
||||
const hotKeyRegistry = useSelector(selectHotKeyRegistry);
|
||||
const activeWorkspaceMeta = useSelector(selectActiveWorkspaceMeta);
|
||||
const setActiveEnvironmentFetcher = useFetcher();
|
||||
const dropdownRef = useRef<DropdownHandle>(null);
|
||||
|
||||
const toggleSwitchMenu = useCallback(() => {
|
||||
@@ -33,10 +38,6 @@ export const EnvironmentsDropdown: FC<Props> = ({
|
||||
});
|
||||
|
||||
// NOTE: Base environment might not exist if the users hasn't managed environments yet.
|
||||
const baseEnvironment = environments.find(environment => environment.parentId === workspaceId);
|
||||
const subEnvironments = environments
|
||||
.filter(environment => environment.parentId === (baseEnvironment && baseEnvironment._id))
|
||||
.sort((e1, e2) => e1.metaSortKey - e2.metaSortKey);
|
||||
const description = (!activeEnvironment || activeEnvironment === baseEnvironment) ? 'No Environment' : activeEnvironment.name;
|
||||
|
||||
return (
|
||||
@@ -83,9 +84,13 @@ export const EnvironmentsDropdown: FC<Props> = ({
|
||||
...(environment.color ? { color: environment.color } : {}),
|
||||
}}
|
||||
onClick={() => {
|
||||
if (activeWorkspaceMeta) {
|
||||
models.workspaceMeta.update(activeWorkspaceMeta, { activeEnvironmentId: environment._id });
|
||||
}
|
||||
setActiveEnvironmentFetcher.submit({
|
||||
environmentId: environment._id,
|
||||
},
|
||||
{
|
||||
method: 'post',
|
||||
action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/environment/set-active`,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</DropdownItem>
|
||||
@@ -97,9 +102,13 @@ export const EnvironmentsDropdown: FC<Props> = ({
|
||||
icon="empty"
|
||||
label="No Environment"
|
||||
onClick={() => {
|
||||
if (activeWorkspaceMeta) {
|
||||
models.workspaceMeta.update(activeWorkspaceMeta, { activeEnvironmentId: null });
|
||||
}
|
||||
setActiveEnvironmentFetcher.submit({
|
||||
environmentId: baseEnvironment._id,
|
||||
},
|
||||
{
|
||||
method: 'post',
|
||||
action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/environment/set-active`,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</DropdownItem>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import classnames from 'classnames';
|
||||
import React, { FC, forwardRef, Fragment, useImperativeHandle, useRef, useState } from 'react';
|
||||
import React, { FC, forwardRef, Fragment, useImperativeHandle, useRef } from 'react';
|
||||
import { ListDropTargetDelegate, ListKeyboardDelegate, mergeProps, useDraggableCollection, useDraggableItem, useDropIndicator, useDroppableCollection, useDroppableItem, useFocusRing, useListBox, useOption } from 'react-aria';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useFetcher, useParams, useRouteLoaderData } from 'react-router-dom';
|
||||
import { DraggableCollectionState, DroppableCollectionState, Item, ListState, useDraggableCollectionState, useDroppableCollectionState, useListState } from 'react-stately';
|
||||
|
||||
import { docsTemplateTags } from '../../../common/documentation';
|
||||
import * as models from '../../../models';
|
||||
import type { Environment } from '../../../models/environment';
|
||||
import { selectActiveWorkspace, selectActiveWorkspaceMeta, selectEnvironments } from '../../redux/selectors';
|
||||
import { selectActiveWorkspaceMeta } from '../../redux/selectors';
|
||||
import { WorkspaceLoaderData } from '../../routes/workspace';
|
||||
import { Dropdown, DropdownButton, DropdownItem, ItemContent } from '../base/dropdown';
|
||||
import { Editable } from '../base/editable';
|
||||
import { Link } from '../base/link';
|
||||
@@ -224,107 +225,83 @@ const ReorderableListBox = props => {
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
interface State {
|
||||
baseEnvironment: Environment | null;
|
||||
selectedEnvironmentId: string | null;
|
||||
}
|
||||
|
||||
export interface WorkspaceEnvironmentsEditModalHandle {
|
||||
show: () => void;
|
||||
hide: () => void;
|
||||
}
|
||||
export const WorkspaceEnvironmentsEditModal = forwardRef<WorkspaceEnvironmentsEditModalHandle, ModalProps>((props, ref) => {
|
||||
const { organizationId, projectId, workspaceId } = useParams<{ organizationId: string; projectId: string; workspaceId: string}>();
|
||||
const routeData = useRouteLoaderData(
|
||||
':workspaceId'
|
||||
) as WorkspaceLoaderData;
|
||||
const modalRef = useRef<ModalHandle>(null);
|
||||
const environmentEditorRef = useRef<EnvironmentEditorHandle>(null);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const [state, setState] = useState<State>({
|
||||
baseEnvironment: null,
|
||||
selectedEnvironmentId: null,
|
||||
});
|
||||
const createEnvironmentFetcher = useFetcher();
|
||||
const deleteEnvironmentFetcher = useFetcher();
|
||||
const updateEnvironmentFetcher = useFetcher();
|
||||
const setActiveEnvironmentFetcher = useFetcher();
|
||||
const duplicateEnvironmentFetcher = useFetcher();
|
||||
|
||||
const workspace = useSelector(selectActiveWorkspace);
|
||||
const workspaceMeta = useSelector(selectActiveWorkspaceMeta);
|
||||
const environments = useSelector(selectEnvironments);
|
||||
useImperativeHandle(ref, () => ({
|
||||
hide: () => {
|
||||
modalRef.current?.hide();
|
||||
},
|
||||
show: async () => {
|
||||
if (!workspace) {
|
||||
return;
|
||||
}
|
||||
const baseEnvironment = await models.environment.getOrCreateForParentId(workspace._id);
|
||||
|
||||
setState(state => ({
|
||||
...state,
|
||||
baseEnvironment,
|
||||
selectedEnvironmentId: workspaceMeta?.activeEnvironmentId || baseEnvironment._id,
|
||||
}));
|
||||
modalRef.current?.show();
|
||||
},
|
||||
}), [workspace, workspaceMeta?.activeEnvironmentId]);
|
||||
}), []);
|
||||
|
||||
if (!routeData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
baseEnvironment,
|
||||
activeWorkspaceMeta,
|
||||
activeEnvironment,
|
||||
subEnvironments,
|
||||
} = routeData;
|
||||
|
||||
function onSelectionChange(e: any) {
|
||||
const environmentId = e.anchorKey;
|
||||
// Only switch if valid
|
||||
if (environmentEditorRef.current?.isValid() && e.anchorKey) {
|
||||
const environment = subEnvironments.filter(evt => evt._id === e.anchorKey)[0];
|
||||
setState(state => ({
|
||||
...state,
|
||||
selectedEnvironmentId: environment._id || null,
|
||||
}));
|
||||
if (workspaceMeta?.activeEnvironmentId !== environment._id && workspaceMeta) {
|
||||
models.workspaceMeta.update(workspaceMeta, { activeEnvironmentId: environment._id });
|
||||
}
|
||||
if (environmentEditorRef.current?.isValid() && activeWorkspaceMeta?.activeEnvironmentId !== environmentId) {
|
||||
setActiveEnvironmentFetcher.submit({
|
||||
environmentId,
|
||||
},
|
||||
{
|
||||
method: 'post',
|
||||
action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/environment/set-active`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDeleteEnvironment(environmentId: string | null) {
|
||||
// Don't delete the root environment
|
||||
if (!environmentId || environmentId === state.baseEnvironment?._id) {
|
||||
return;
|
||||
}
|
||||
// Unset active environment if it's being deleted
|
||||
if (workspaceMeta?.activeEnvironmentId === environmentId && workspaceMeta) {
|
||||
models.workspaceMeta.update(workspaceMeta, { activeEnvironmentId: null });
|
||||
}
|
||||
// Delete the current one
|
||||
const current = environments.find(e => e._id === environmentId);
|
||||
current && models.environment.remove(current);
|
||||
setState(state => ({
|
||||
...state,
|
||||
selectedEnvironmentId: state.baseEnvironment?._id || null,
|
||||
}));
|
||||
async function handleDeleteEnvironment(environmentId: string) {
|
||||
deleteEnvironmentFetcher.submit({
|
||||
environmentId,
|
||||
},
|
||||
{
|
||||
encType: 'application/json',
|
||||
method: 'post',
|
||||
action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/environment/delete`,
|
||||
});
|
||||
}
|
||||
|
||||
const updateEnvironment = async (environmentId: string | null, patch: Partial<Environment>) => {
|
||||
if (environmentId === null) {
|
||||
return;
|
||||
}
|
||||
// NOTE: Fetch the environment first because it might not be up to date.
|
||||
const realEnvironment = await models.environment.getById(environmentId);
|
||||
if (realEnvironment) {
|
||||
const updated = await models.environment.update(realEnvironment, patch);
|
||||
// reload the root environment if it changed since its not updated by redux
|
||||
const isBaseEnvironment = realEnvironment?.parentId === workspace?._id;
|
||||
if (isBaseEnvironment) {
|
||||
setState({ ...state, baseEnvironment: updated });
|
||||
}
|
||||
}
|
||||
const updateEnvironment = async (environmentId: string, patch: Partial<Environment>) => {
|
||||
updateEnvironmentFetcher.submit({
|
||||
patch,
|
||||
environmentId,
|
||||
},
|
||||
{
|
||||
encType: 'application/json',
|
||||
method: 'post',
|
||||
action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/environment/update`,
|
||||
});
|
||||
};
|
||||
|
||||
const { baseEnvironment, selectedEnvironmentId } = state;
|
||||
const selectedEnvironment = baseEnvironment?._id === selectedEnvironmentId
|
||||
? baseEnvironment
|
||||
: environments.filter(e => e.parentId === baseEnvironment?._id).find(subEnvironment => subEnvironment._id === selectedEnvironmentId) || null;
|
||||
const selectedEnvironmentName = selectedEnvironment?.name || '';
|
||||
const selectedEnvironmentColor = selectedEnvironment?.color || null;
|
||||
const subEnvironments = environments
|
||||
.filter(environment => environment.parentId === (baseEnvironment && baseEnvironment._id))
|
||||
.sort((e1, e2) => e1.metaSortKey - e2.metaSortKey);
|
||||
if (inputRef.current && selectedEnvironmentColor) {
|
||||
inputRef.current.value = selectedEnvironmentColor;
|
||||
}
|
||||
|
||||
function onReorder(e: any) {
|
||||
const source = [...e.keys][0];
|
||||
const sourceEnv = subEnvironments.find(evt => evt._id === source);
|
||||
@@ -349,16 +326,19 @@ export const WorkspaceEnvironmentsEditModal = forwardRef<WorkspaceEnvironmentsEd
|
||||
<div className="env-modal__sidebar">
|
||||
<div
|
||||
className={classnames('env-modal__sidebar-root-item', {
|
||||
'env-modal__sidebar-item--active': selectedEnvironmentId === baseEnvironment?._id,
|
||||
'env-modal__sidebar-item--active': activeEnvironment._id === baseEnvironment._id,
|
||||
})}
|
||||
>
|
||||
<button
|
||||
onClick={() => {
|
||||
if (environmentEditorRef.current?.isValid() && selectedEnvironmentId !== baseEnvironment?._id) {
|
||||
baseEnvironment?._id && setState(state => ({
|
||||
...state,
|
||||
selectedEnvironmentId: baseEnvironment?._id,
|
||||
}));
|
||||
if (environmentEditorRef.current?.isValid() && activeEnvironment._id !== baseEnvironment._id) {
|
||||
setActiveEnvironmentFetcher.submit({
|
||||
environmentId: baseEnvironment._id,
|
||||
},
|
||||
{
|
||||
method: 'post',
|
||||
action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/environment/set-active`,
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -387,16 +367,14 @@ export const WorkspaceEnvironmentsEditModal = forwardRef<WorkspaceEnvironmentsEd
|
||||
icon="eye"
|
||||
label="Environment"
|
||||
onClick={async () => {
|
||||
if (baseEnvironment) {
|
||||
const environment = await models.environment.create({
|
||||
parentId: baseEnvironment._id,
|
||||
isPrivate: false,
|
||||
});
|
||||
setState(state => ({
|
||||
...state,
|
||||
selectedEnvironmentId: environment._id,
|
||||
}));
|
||||
}
|
||||
createEnvironmentFetcher.submit({
|
||||
isPrivate: false,
|
||||
},
|
||||
{
|
||||
encType: 'application/json',
|
||||
method: 'post',
|
||||
action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/environment/create`,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</DropdownItem>
|
||||
@@ -405,16 +383,14 @@ export const WorkspaceEnvironmentsEditModal = forwardRef<WorkspaceEnvironmentsEd
|
||||
icon="eye-slash"
|
||||
label="Private Environment"
|
||||
onClick={async () => {
|
||||
if (baseEnvironment) {
|
||||
const environment = await models.environment.create({
|
||||
parentId: baseEnvironment._id,
|
||||
isPrivate: true,
|
||||
});
|
||||
setState(state => ({
|
||||
...state,
|
||||
selectedEnvironmentId: environment._id,
|
||||
}));
|
||||
}
|
||||
createEnvironmentFetcher.submit({
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
encType: 'application/json',
|
||||
method: 'post',
|
||||
action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/environment/create`,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</DropdownItem>
|
||||
@@ -438,29 +414,29 @@ export const WorkspaceEnvironmentsEditModal = forwardRef<WorkspaceEnvironmentsEd
|
||||
<div className="env-modal__main">
|
||||
<div className="env-modal__main__header">
|
||||
<h1>
|
||||
{baseEnvironment?._id === selectedEnvironmentId ? (
|
||||
{baseEnvironment._id === activeEnvironment._id ? (
|
||||
ROOT_ENVIRONMENT_NAME
|
||||
) : (
|
||||
<Editable
|
||||
singleClick
|
||||
className="wide"
|
||||
onSubmit={name => {
|
||||
if (selectedEnvironmentId && name) {
|
||||
updateEnvironment(selectedEnvironmentId, { name });
|
||||
if (activeEnvironment._id && name) {
|
||||
updateEnvironment(activeEnvironment._id, { name });
|
||||
}
|
||||
}}
|
||||
value={selectedEnvironmentName}
|
||||
value={activeEnvironment.name}
|
||||
/>
|
||||
)}
|
||||
</h1>
|
||||
|
||||
{selectedEnvironmentId && baseEnvironment?._id !== selectedEnvironmentId ? (
|
||||
{baseEnvironment._id !== activeEnvironment._id ? (
|
||||
<Fragment>
|
||||
<input
|
||||
className="hidden"
|
||||
type="color"
|
||||
ref={inputRef}
|
||||
onChange={event => updateEnvironment(selectedEnvironmentId, { color: event.target.value })}
|
||||
onChange={event => updateEnvironment(activeEnvironment._id, { color: event.target.value })}
|
||||
/>
|
||||
|
||||
<Dropdown
|
||||
@@ -471,11 +447,11 @@ export const WorkspaceEnvironmentsEditModal = forwardRef<WorkspaceEnvironmentsEd
|
||||
className="btn btn--clicky"
|
||||
disableHoverBehavior={false}
|
||||
>
|
||||
{selectedEnvironmentColor && (
|
||||
{activeEnvironment.color && (
|
||||
<i
|
||||
className="fa fa-circle space-right"
|
||||
style={{
|
||||
color: selectedEnvironmentColor,
|
||||
color: activeEnvironment.color,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@@ -483,18 +459,18 @@ export const WorkspaceEnvironmentsEditModal = forwardRef<WorkspaceEnvironmentsEd
|
||||
</DropdownButton>
|
||||
}
|
||||
>
|
||||
<DropdownItem aria-label={selectedEnvironmentColor ? 'Change Color' : 'Assign Color'}>
|
||||
<DropdownItem aria-label={activeEnvironment.color ? 'Change Color' : 'Assign Color'}>
|
||||
<ItemContent
|
||||
icon="circle"
|
||||
label={selectedEnvironmentColor ? 'Change Color' : 'Assign Color'}
|
||||
label={activeEnvironment.color ? 'Change Color' : 'Assign Color'}
|
||||
iconStyle={{
|
||||
...(selectedEnvironmentColor ? { color: selectedEnvironmentColor } : {}),
|
||||
...(activeEnvironment.color ? { color: activeEnvironment.color } : {}),
|
||||
}}
|
||||
onClick={() => {
|
||||
if (!selectedEnvironmentColor) {
|
||||
if (!activeEnvironment.color) {
|
||||
// TODO: fix magic-number. Currently this is the `surprise` background color for the default theme,
|
||||
// but we should be grabbing the actual value from the user's actual theme instead.
|
||||
updateEnvironment(selectedEnvironmentId, { color: '#7d69cb' });
|
||||
updateEnvironment(activeEnvironment._id, { color: '#7d69cb' });
|
||||
}
|
||||
inputRef.current?.click();
|
||||
}}
|
||||
@@ -503,22 +479,24 @@ export const WorkspaceEnvironmentsEditModal = forwardRef<WorkspaceEnvironmentsEd
|
||||
|
||||
<DropdownItem aria-label='Unset Color'>
|
||||
<ItemContent
|
||||
isDisabled={!selectedEnvironmentColor}
|
||||
isDisabled={!activeEnvironment.color}
|
||||
icon="minus-circle"
|
||||
label="Unset Color"
|
||||
onClick={() => updateEnvironment(selectedEnvironmentId, { color: null })}
|
||||
onClick={() => updateEnvironment(activeEnvironment._id, { color: null })}
|
||||
/>
|
||||
</DropdownItem>
|
||||
</Dropdown>
|
||||
|
||||
<button
|
||||
onClick={async () => {
|
||||
if (selectedEnvironment) {
|
||||
const newEnvironment = await models.environment.duplicate(selectedEnvironment);
|
||||
setState(state => ({
|
||||
...state,
|
||||
selectedEnvironmentId: newEnvironment._id,
|
||||
}));
|
||||
if (activeEnvironment) {
|
||||
duplicateEnvironmentFetcher.submit({
|
||||
environmentId: activeEnvironment._id,
|
||||
}, {
|
||||
encType: 'application/json',
|
||||
method: 'post',
|
||||
action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/environment/duplicate`,
|
||||
});
|
||||
}
|
||||
}}
|
||||
className="btn btn--clicky space-right"
|
||||
@@ -526,22 +504,22 @@ export const WorkspaceEnvironmentsEditModal = forwardRef<WorkspaceEnvironmentsEd
|
||||
<i className="fa fa-copy" /> Duplicate
|
||||
</button>
|
||||
|
||||
<PromptButton
|
||||
onClick={() => handleDeleteEnvironment(selectedEnvironmentId)}
|
||||
{activeEnvironment._id !== baseEnvironment._id && <PromptButton
|
||||
onClick={() => handleDeleteEnvironment(activeEnvironment._id)}
|
||||
className="btn btn--clicky"
|
||||
>
|
||||
<i className="fa fa-trash-o" />
|
||||
</PromptButton>
|
||||
</PromptButton>}
|
||||
</Fragment>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="env-modal__editor">
|
||||
<EnvironmentEditor
|
||||
ref={environmentEditorRef}
|
||||
key={`${selectedEnvironmentId || 'n/a'}`}
|
||||
key={`${activeEnvironment._id}`}
|
||||
environmentInfo={{
|
||||
object: selectedEnvironment?.data || {},
|
||||
propertyOrder: selectedEnvironment?.dataPropertyOrder || null,
|
||||
object: activeEnvironment.data,
|
||||
propertyOrder: activeEnvironment.dataPropertyOrder,
|
||||
}}
|
||||
onBlur={() => {
|
||||
// Only save if it's valid
|
||||
@@ -549,8 +527,8 @@ export const WorkspaceEnvironmentsEditModal = forwardRef<WorkspaceEnvironmentsEd
|
||||
return;
|
||||
}
|
||||
const data = environmentEditorRef.current?.getValue();
|
||||
if (selectedEnvironment && data) {
|
||||
updateEnvironment(selectedEnvironmentId, {
|
||||
if (activeEnvironment && data) {
|
||||
updateEnvironment(activeEnvironment._id, {
|
||||
data: data.object,
|
||||
dataPropertyOrder: data.propertyOrder,
|
||||
});
|
||||
|
||||
@@ -166,6 +166,31 @@ const router = createMemoryRouter(
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'environment',
|
||||
children: [
|
||||
{
|
||||
path: 'update',
|
||||
action: async (...args) => (await import('./routes/actions')).updateEnvironment(...args),
|
||||
},
|
||||
{
|
||||
path: 'delete',
|
||||
action: async (...args) => (await import('./routes/actions')).deleteEnvironmentAction(...args),
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
action: async (...args) => (await import('./routes/actions')).createEnvironmentAction(...args),
|
||||
},
|
||||
{
|
||||
path: 'duplicate',
|
||||
action: async (...args) => (await import('./routes/actions')).duplicateEnvironmentAction(...args),
|
||||
},
|
||||
{
|
||||
path: 'set-active',
|
||||
action: async (...args) => (await import('./routes/actions')).setActiveEnvironmentAction(...args),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'test/*',
|
||||
loader: async (...args) => (await import('./routes/unit-test')).loader(...args),
|
||||
|
||||
@@ -778,3 +778,111 @@ export const accessAIApiAction: ActionFunction = async ({ params }) => {
|
||||
return { enabled: false };
|
||||
}
|
||||
};
|
||||
|
||||
export const createEnvironmentAction: ActionFunction = async ({
|
||||
params,
|
||||
request,
|
||||
}) => {
|
||||
const { workspaceId } = params;
|
||||
invariant(typeof workspaceId === 'string', 'Workspace ID is required');
|
||||
|
||||
const { isPrivate } = await request.json();
|
||||
|
||||
const baseEnvironment = await models.environment.getByParentId(workspaceId);
|
||||
|
||||
invariant(baseEnvironment, 'Base environment not found');
|
||||
|
||||
const environment = await models.environment.create({
|
||||
parentId: baseEnvironment._id,
|
||||
isPrivate,
|
||||
});
|
||||
|
||||
return environment;
|
||||
};
|
||||
export const updateEnvironment: ActionFunction = async ({ request, params }) => {
|
||||
const { workspaceId } = params;
|
||||
invariant(typeof workspaceId === 'string', 'Workspace ID is required');
|
||||
|
||||
const { environmentId, patch } = await request.json();
|
||||
|
||||
invariant(typeof environmentId === 'string', 'Environment ID is required');
|
||||
|
||||
const environment = await models.environment.getById(environmentId);
|
||||
|
||||
invariant(environment, 'Environment not found');
|
||||
invariant(typeof name === 'string', 'Name is required');
|
||||
|
||||
const baseEnvironment = await models.environment.getByParentId(workspaceId);
|
||||
|
||||
invariant(baseEnvironment, 'Base environment not found');
|
||||
|
||||
const updatedEnvironment = await models.environment.update(environment, patch);
|
||||
|
||||
return updatedEnvironment;
|
||||
};
|
||||
|
||||
export const deleteEnvironmentAction: ActionFunction = async ({
|
||||
request, params,
|
||||
}) => {
|
||||
const { workspaceId } = params;
|
||||
invariant(typeof workspaceId === 'string', 'Workspace ID is required');
|
||||
|
||||
const formData = await request.formData();
|
||||
|
||||
const environmentId = formData.get('environmentId');
|
||||
invariant(typeof environmentId === 'string', 'Environment ID is required');
|
||||
|
||||
const environment = await models.environment.getById(environmentId);
|
||||
|
||||
const baseEnvironment = await models.environment.getByParentId(workspaceId);
|
||||
|
||||
invariant(environment?._id !== baseEnvironment?._id, 'Cannot delete base environment');
|
||||
|
||||
invariant(environment, 'Environment not found');
|
||||
|
||||
await models.environment.remove(environment);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export const duplicateEnvironmentAction: ActionFunction = async ({
|
||||
request, params,
|
||||
}) => {
|
||||
const { workspaceId } = params;
|
||||
invariant(typeof workspaceId === 'string', 'Workspace ID is required');
|
||||
|
||||
const { environmentId } = await request.json();
|
||||
|
||||
invariant(typeof environmentId === 'string', 'Environment ID is required');
|
||||
|
||||
const environment = await models.environment.getById(environmentId);
|
||||
invariant(environment, 'Environment not found');
|
||||
|
||||
const newEnvironment = await models.environment.duplicate(environment);
|
||||
|
||||
return newEnvironment;
|
||||
};
|
||||
|
||||
export const setActiveEnvironmentAction: ActionFunction = async ({
|
||||
request, params,
|
||||
}) => {
|
||||
const { workspaceId } = params;
|
||||
invariant(typeof workspaceId === 'string', 'Workspace ID is required');
|
||||
|
||||
const formData = await request.formData();
|
||||
|
||||
const environmentId = formData.get('environmentId');
|
||||
|
||||
console.log('environmentId', environmentId);
|
||||
|
||||
invariant(typeof environmentId === 'string', 'Environment ID is required');
|
||||
|
||||
const workspaceMeta = await models.workspaceMeta.getOrCreateByParentId(workspaceId);
|
||||
|
||||
invariant(workspaceMeta, 'Workspace meta not found');
|
||||
|
||||
// @TODO - Null vs undefined vs empty string
|
||||
await models.workspaceMeta.update(workspaceMeta, { activeEnvironmentId: environmentId || null });
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -658,6 +658,16 @@ export const loader: LoaderFunction = async ({
|
||||
|
||||
const project = await models.project.getById(projectId);
|
||||
|
||||
console.log({
|
||||
project,
|
||||
});
|
||||
|
||||
const allProjects = await models.project.all();
|
||||
|
||||
console.log({
|
||||
allProjects,
|
||||
});
|
||||
|
||||
invariant(project, 'Project was not found');
|
||||
|
||||
const projectWorkspaces = await models.workspace.findByParentId(project._id);
|
||||
@@ -752,7 +762,7 @@ export const loader: LoaderFunction = async ({
|
||||
)?.indexes) : true)
|
||||
.sort((a, b) => sortMethodMap[sortOrder as DashboardSortOrder](a, b));
|
||||
|
||||
const allProjects = await models.project.all();
|
||||
// const allProjects = await models.project.all();
|
||||
|
||||
const organizationProjects =
|
||||
organizationId === DEFAULT_ORGANIZATION_ID
|
||||
|
||||
@@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { LoaderFunction, Outlet, useLoaderData } from 'react-router-dom';
|
||||
|
||||
import * as models from '../../models';
|
||||
import { Environment } from '../../models/environment';
|
||||
import { GitRepository } from '../../models/git-repository';
|
||||
import { Project } from '../../models/project';
|
||||
import { Workspace } from '../../models/workspace';
|
||||
@@ -12,6 +13,9 @@ export interface WorkspaceLoaderData {
|
||||
activeWorkspaceMeta?: WorkspaceMeta;
|
||||
activeProject: Project;
|
||||
gitRepository: GitRepository | null;
|
||||
activeEnvironment: Environment;
|
||||
baseEnvironment: Environment;
|
||||
subEnvironments: Environment[];
|
||||
}
|
||||
|
||||
export const workspaceLoader: LoaderFunction = async ({
|
||||
@@ -31,14 +35,29 @@ export const workspaceLoader: LoaderFunction = async ({
|
||||
const activeProject = await models.project.getById(projectId);
|
||||
invariant(activeProject, 'Project not found');
|
||||
|
||||
const activeWorkspaceMeta = await models.workspaceMeta.getOrCreateByParentId(workspaceId);
|
||||
const gitRepository = await models.gitRepository.getById(activeWorkspaceMeta.gitRepositoryId || '');
|
||||
const activeWorkspaceMeta = await models.workspaceMeta.getOrCreateByParentId(
|
||||
workspaceId,
|
||||
);
|
||||
const gitRepository = await models.gitRepository.getById(
|
||||
activeWorkspaceMeta.gitRepositoryId || '',
|
||||
);
|
||||
|
||||
const baseEnvironment = await models.environment.getByParentId(workspaceId);
|
||||
invariant(baseEnvironment, 'Base environment not found');
|
||||
|
||||
const subEnvironments = (await models.environment.findByParentId(baseEnvironment._id))
|
||||
.sort((e1, e2) => e1.metaSortKey - e2.metaSortKey);
|
||||
|
||||
const activeEnvironment = subEnvironments.find(({ _id }) => activeWorkspaceMeta.activeEnvironmentId === _id) || baseEnvironment;
|
||||
|
||||
return {
|
||||
activeWorkspace,
|
||||
activeProject,
|
||||
gitRepository,
|
||||
activeWorkspaceMeta,
|
||||
activeEnvironment,
|
||||
subEnvironments,
|
||||
baseEnvironment,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user