diff --git a/packages/insomnia/package-lock.json b/packages/insomnia/package-lock.json index 8d51438a78..52eaae14f1 100644 --- a/packages/insomnia/package-lock.json +++ b/packages/insomnia/package-lock.json @@ -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" diff --git a/packages/insomnia/package.json b/packages/insomnia/package.json index ee983642f1..d8c80bacb7 100644 --- a/packages/insomnia/package.json +++ b/packages/insomnia/package.json @@ -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", diff --git a/packages/insomnia/src/sync/git/http-client.ts b/packages/insomnia/src/sync/git/http-client.ts index fe1bf736c0..2f686305e4 100644 --- a/packages/insomnia/src/sync/git/http-client.ts +++ b/packages/insomnia/src/sync/git/http-client.ts @@ -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, diff --git a/packages/insomnia/src/ui/components/dropdowns/environments-dropdown.tsx b/packages/insomnia/src/ui/components/dropdowns/environments-dropdown.tsx index 5b0f2cbd02..cb6791d598 100644 --- a/packages/insomnia/src/ui/components/dropdowns/environments-dropdown.tsx +++ b/packages/insomnia/src/ui/components/dropdowns/environments-dropdown.tsx @@ -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 = ({ - activeEnvironment, - workspaceId, -}) => { - const environments = useSelector(selectEnvironments); +export const EnvironmentsDropdown: FC = () => { + 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(null); const toggleSwitchMenu = useCallback(() => { @@ -33,10 +38,6 @@ export const EnvironmentsDropdown: FC = ({ }); // 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 = ({ ...(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`, + }); }} /> @@ -97,9 +102,13 @@ export const EnvironmentsDropdown: FC = ({ 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`, + }); }} /> diff --git a/packages/insomnia/src/ui/components/modals/workspace-environments-edit-modal.tsx b/packages/insomnia/src/ui/components/modals/workspace-environments-edit-modal.tsx index 9f4622802d..ca196afc8e 100644 --- a/packages/insomnia/src/ui/components/modals/workspace-environments-edit-modal.tsx +++ b/packages/insomnia/src/ui/components/modals/workspace-environments-edit-modal.tsx @@ -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 => { ); }; -interface State { - baseEnvironment: Environment | null; - selectedEnvironmentId: string | null; -} + export interface WorkspaceEnvironmentsEditModalHandle { show: () => void; hide: () => void; } export const WorkspaceEnvironmentsEditModal = forwardRef((props, ref) => { + const { organizationId, projectId, workspaceId } = useParams<{ organizationId: string; projectId: string; workspaceId: string}>(); + const routeData = useRouteLoaderData( + ':workspaceId' + ) as WorkspaceLoaderData; const modalRef = useRef(null); const environmentEditorRef = useRef(null); const inputRef = useRef(null); - const [state, setState] = useState({ - 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) => { - 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) => { + 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
- handleDeleteEnvironment(selectedEnvironmentId)} + {activeEnvironment._id !== baseEnvironment._id && handleDeleteEnvironment(activeEnvironment._id)} className="btn btn--clicky" > - + } ) : null}
{ // Only save if it's valid @@ -549,8 +527,8 @@ export const WorkspaceEnvironmentsEditModal = forwardRef (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), diff --git a/packages/insomnia/src/ui/routes/actions.tsx b/packages/insomnia/src/ui/routes/actions.tsx index 9a2aaa6cff..e432344de3 100644 --- a/packages/insomnia/src/ui/routes/actions.tsx +++ b/packages/insomnia/src/ui/routes/actions.tsx @@ -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; +}; diff --git a/packages/insomnia/src/ui/routes/project.tsx b/packages/insomnia/src/ui/routes/project.tsx index 6550d287ab..718d278ef9 100644 --- a/packages/insomnia/src/ui/routes/project.tsx +++ b/packages/insomnia/src/ui/routes/project.tsx @@ -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 diff --git a/packages/insomnia/src/ui/routes/workspace.tsx b/packages/insomnia/src/ui/routes/workspace.tsx index 0f770b5304..15da9a4e07 100644 --- a/packages/insomnia/src/ui/routes/workspace.tsx +++ b/packages/insomnia/src/ui/routes/workspace.tsx @@ -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, }; };