mirror of
https://github.com/Kong/insomnia.git
synced 2026-02-15 08:32:11 -05:00
redux->remix workspace route (#6191)
* checkpoint * workspace meta * select environment * fix sidebar layout * clean up * absorb apispec into workspace route * client certs * remove redux tests * update data docs * remove active project * revert selector change * fix selector * remove activeworkspace * remove activity
This commit is contained in:
@@ -45,8 +45,8 @@ There are a few notable directories inside it:
|
||||
Insomnia stores data in a few places:
|
||||
|
||||
- A local in-memory NeDB database stores data for data models (requests, folder, workspaces, etc.).
|
||||
- A local Redux store contains an in-memory copy of all database entities.
|
||||
- Multiple React Context stores, defined in `/src/ui/context`.
|
||||
- localstorage
|
||||
- a fake localstorage api that writes to file and is used for window sizing
|
||||
|
||||
> Note: NeDB is officially unmaintained (even for critical security bugs) and was last published in February 2016. Due to this, we hope to move away from it, however doing so is tricky because of how deeply tied it is to our architecture.
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import configureMockStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
|
||||
import { ACTIVITY_DEBUG } from '../common/constants';
|
||||
import * as models from '../models';
|
||||
import { Request } from '../models/request';
|
||||
import { RequestMeta } from '../models/request-meta';
|
||||
@@ -21,7 +20,6 @@ export const createMockStoreWithRequest = async ({ requestPatch, requestMetaPatc
|
||||
const store = mockStore(await reduxStateForTest({
|
||||
activeProjectId: projectId,
|
||||
activeWorkspaceId: workspaceId,
|
||||
activeActivity: ACTIVITY_DEBUG,
|
||||
}));
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { ACTIVITY_HOME } from '../common/constants';
|
||||
import { database } from '../common/database';
|
||||
import { DEFAULT_PROJECT_ID, type } from '../models/project';
|
||||
import { RootState } from '../ui/redux/modules';
|
||||
@@ -36,7 +35,6 @@ export const reduxStateForTest = async (global: Partial<GlobalState> = {}): Prom
|
||||
entities: entities.reducer(entities.initialEntitiesState, entities.initializeWith(allDocs)),
|
||||
global: {
|
||||
activeWorkspaceId: null,
|
||||
activeActivity: ACTIVITY_HOME,
|
||||
activeProjectId: DEFAULT_PROJECT_ID,
|
||||
dashboardSortOrder: 'modified-desc',
|
||||
isLoggedIn: false,
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { ApiSpec } from '../api-spec';
|
||||
import * as models from '../index';
|
||||
import { isDesign, Workspace } from '../workspace';
|
||||
|
||||
export async function rename(name: string, workspace: Workspace, apiSpec?: ApiSpec) {
|
||||
export async function rename(name: string, workspace: Workspace, apiSpec?: ApiSpec | null) {
|
||||
if (isDesign(workspace) && apiSpec) {
|
||||
await models.apiSpec.update(apiSpec, {
|
||||
fileName: name,
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import classNames from 'classnames';
|
||||
import React, { FC, Fragment, ReactNode } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { selectIsLoggedIn } from '../redux/selectors';
|
||||
import * as session from '../../account/session';
|
||||
import { GitHubStarsButton } from './github-stars-button';
|
||||
import { InsomniaAILogo } from './insomnia-icon';
|
||||
|
||||
const LogoWrapper = styled.div({
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
@@ -75,8 +73,6 @@ export const AppHeader: FC<AppHeaderProps> = ({
|
||||
gridCenter,
|
||||
gridRight,
|
||||
}) => {
|
||||
const isLoggedIn = useSelector(selectIsLoggedIn);
|
||||
|
||||
return (
|
||||
<Header
|
||||
gridLeft={(
|
||||
@@ -84,7 +80,7 @@ export const AppHeader: FC<AppHeaderProps> = ({
|
||||
<LogoWrapper>
|
||||
<InsomniaAILogo />
|
||||
</LogoWrapper>
|
||||
{!isLoggedIn ? <GitHubStarsButton /> : null}
|
||||
{!session.isLoggedIn() ? <GitHubStarsButton /> : null}
|
||||
</Fragment>
|
||||
)}
|
||||
gridCenter={gridCenter}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { forwardRef, useCallback, useImperativeHandle, useRef, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useRouteLoaderData } from 'react-router-dom';
|
||||
|
||||
import { toKebabCase } from '../../../common/misc';
|
||||
import { RENDER_PURPOSE_NO_RENDER } from '../../../common/render';
|
||||
@@ -11,11 +12,11 @@ import { getRequestGroupActions } from '../../../plugins';
|
||||
import * as pluginContexts from '../../../plugins/context/index';
|
||||
import { createRequest, CreateRequestType } from '../../hooks/create-request';
|
||||
import { createRequestGroup } from '../../hooks/create-request-group';
|
||||
import { selectActiveEnvironment, selectActiveProject, selectActiveWorkspace, selectHotKeyRegistry } from '../../redux/selectors';
|
||||
import { selectHotKeyRegistry } from '../../redux/selectors';
|
||||
import { WorkspaceLoaderData } from '../../routes/workspace';
|
||||
import { Dropdown, DropdownButton, type DropdownHandle, DropdownItem, type DropdownProps, DropdownSection, ItemContent } from '../base/dropdown';
|
||||
import { showError, showModal, showPrompt } from '../modals';
|
||||
import { EnvironmentEditModal } from '../modals/environment-edit-modal';
|
||||
|
||||
interface Props extends Partial<DropdownProps> {
|
||||
requestGroup: RequestGroup;
|
||||
handleShowSettings: (requestGroup: RequestGroup) => any;
|
||||
@@ -30,24 +31,24 @@ export const RequestGroupActionsDropdown = forwardRef<RequestGroupActionsDropdow
|
||||
handleShowSettings,
|
||||
...other
|
||||
}, ref) => {
|
||||
const {
|
||||
activeWorkspace,
|
||||
activeEnvironment,
|
||||
activeProject,
|
||||
} = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
|
||||
const hotKeyRegistry = useSelector(selectHotKeyRegistry);
|
||||
const [actionPlugins, setActionPlugins] = useState<RequestGroupAction[]>([]);
|
||||
const [loadingActions, setLoadingActions] = useState<Record<string, boolean>>({});
|
||||
const dropdownRef = useRef<DropdownHandle>(null);
|
||||
|
||||
const activeProject = useSelector(selectActiveProject);
|
||||
const activeEnvironment = useSelector(selectActiveEnvironment);
|
||||
const activeWorkspace = useSelector(selectActiveWorkspace);
|
||||
const activeWorkspaceId = activeWorkspace?._id;
|
||||
|
||||
const create = useCallback((requestType: CreateRequestType) => {
|
||||
if (activeWorkspaceId) {
|
||||
if (activeWorkspace._id) {
|
||||
createRequest({
|
||||
parentId: requestGroup._id,
|
||||
requestType, workspaceId: activeWorkspaceId,
|
||||
requestType, workspaceId: activeWorkspace._id,
|
||||
});
|
||||
}
|
||||
}, [activeWorkspaceId, requestGroup._id]);
|
||||
}, [activeWorkspace._id, requestGroup._id]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
show: () => {
|
||||
@@ -102,12 +103,11 @@ export const RequestGroupActionsDropdown = forwardRef<RequestGroupActionsDropdow
|
||||
setLoadingActions({ ...loadingActions, [label]: true });
|
||||
|
||||
try {
|
||||
const activeEnvironmentId = activeEnvironment ? activeEnvironment._id : null;
|
||||
const context = {
|
||||
...(pluginContexts.app.init(RENDER_PURPOSE_NO_RENDER) as Record<string, any>),
|
||||
...pluginContexts.data.init(activeProject._id),
|
||||
...(pluginContexts.store.init(plugin) as Record<string, any>),
|
||||
...(pluginContexts.network.init(activeEnvironmentId) as Record<string, any>),
|
||||
...(pluginContexts.network.init(activeEnvironment._id) as Record<string, any>),
|
||||
};
|
||||
const requests = await models.request.findByParentId(requestGroup._id);
|
||||
requests.sort((a, b) => a.metaSortKey - b.metaSortKey);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { differenceInHours, differenceInMinutes, isThisWeek, isToday } from 'date-fns';
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useRouteLoaderData } from 'react-router-dom';
|
||||
|
||||
import { decompressObject } from '../../../common/misc';
|
||||
import * as models from '../../../models/index';
|
||||
@@ -9,7 +10,8 @@ import { Response } from '../../../models/response';
|
||||
import { WebSocketRequest } from '../../../models/websocket-request';
|
||||
import { isWebSocketResponse, WebSocketResponse } from '../../../models/websocket-response';
|
||||
import { updateRequestMetaByParentId } from '../../hooks/create-request';
|
||||
import { selectActiveEnvironment, selectActiveRequest, selectActiveRequestResponses, selectRequestVersions } from '../../redux/selectors';
|
||||
import { selectActiveRequest, selectActiveRequestResponses, selectRequestVersions } 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 { SizeTag } from '../tags/size-tag';
|
||||
@@ -17,7 +19,6 @@ import { StatusTag } from '../tags/status-tag';
|
||||
import { TimeTag } from '../tags/time-tag';
|
||||
import { URLTag } from '../tags/url-tag';
|
||||
import { TimeFromNow } from '../time-from-now';
|
||||
|
||||
interface Props<GenericResponse extends Response | WebSocketResponse> {
|
||||
activeResponse: GenericResponse;
|
||||
className?: string;
|
||||
@@ -30,7 +31,9 @@ export const ResponseHistoryDropdown = <GenericResponse extends Response | WebSo
|
||||
requestId,
|
||||
}: Props<GenericResponse>) => {
|
||||
const dropdownRef = useRef<DropdownHandle>(null);
|
||||
const activeEnvironment = useSelector(selectActiveEnvironment);
|
||||
const {
|
||||
activeEnvironment,
|
||||
} = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
|
||||
const responses = useSelector(selectActiveRequestResponses) as GenericResponse[];
|
||||
const activeRequest = useSelector(selectActiveRequest);
|
||||
const requestVersions = useSelector(selectRequestVersions);
|
||||
@@ -56,12 +59,11 @@ export const ResponseHistoryDropdown = <GenericResponse extends Response | WebSo
|
||||
}, []);
|
||||
|
||||
const handleDeleteResponses = useCallback(async () => {
|
||||
const environmentId = activeEnvironment ? activeEnvironment._id : null;
|
||||
if (isWebSocketResponse(activeResponse)) {
|
||||
window.main.webSocket.closeAll();
|
||||
await models.webSocketResponse.removeForRequest(requestId, environmentId);
|
||||
await models.webSocketResponse.removeForRequest(requestId, activeEnvironment._id);
|
||||
} else {
|
||||
await models.response.removeForRequest(requestId, environmentId);
|
||||
await models.response.removeForRequest(requestId, activeEnvironment._id);
|
||||
}
|
||||
if (activeRequest && activeRequest._id === requestId) {
|
||||
await updateRequestMetaByParentId(requestId, { activeResponseId: null });
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import classnames from 'classnames';
|
||||
import React, { FC, Fragment, useCallback, useEffect, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useNavigate, useParams, useRouteLoaderData } from 'react-router-dom';
|
||||
import { useInterval, useMount } from 'react-use';
|
||||
|
||||
import * as session from '../../../account/session';
|
||||
@@ -18,8 +19,8 @@ import { BackendProjectWithTeam } from '../../../sync/vcs/normalize-backend-proj
|
||||
import { pullBackendProject } from '../../../sync/vcs/pull-backend-project';
|
||||
import { interceptAccessError } from '../../../sync/vcs/util';
|
||||
import { VCS } from '../../../sync/vcs/vcs';
|
||||
import { activateWorkspace } from '../../redux/modules/workspace';
|
||||
import { selectActiveWorkspaceMeta, selectRemoteProjects, selectSyncItems } from '../../redux/selectors';
|
||||
import { selectRemoteProjects, selectSyncItems } from '../../redux/selectors';
|
||||
import { WorkspaceLoaderData } from '../../routes/workspace';
|
||||
import { Dropdown, DropdownButton, DropdownItem, DropdownSection, ItemContent } from '../base/dropdown';
|
||||
import { Link } from '../base/link';
|
||||
import { HelpTooltip } from '../help-tooltip';
|
||||
@@ -32,7 +33,6 @@ import { SyncHistoryModal } from '../modals/sync-history-modal';
|
||||
import { SyncStagingModal } from '../modals/sync-staging-modal';
|
||||
import { Button } from '../themed-button';
|
||||
import { Tooltip } from '../tooltip';
|
||||
|
||||
// TODO: handle refetching logic in one place not here in a component
|
||||
|
||||
// Refresh dropdown periodically
|
||||
@@ -79,10 +79,13 @@ export const SyncDropdown: FC<Props> = ({ vcs, workspace, project }) => {
|
||||
},
|
||||
remoteBackendProjects: [],
|
||||
});
|
||||
const dispatch = useDispatch();
|
||||
const { organizationId, projectId } = useParams<{ organizationId: string; projectId: string }>();
|
||||
const navigate = useNavigate();
|
||||
const remoteProjects = useSelector(selectRemoteProjects);
|
||||
const syncItems = useSelector(selectSyncItems);
|
||||
const workspaceMeta = useSelector(selectActiveWorkspaceMeta);
|
||||
const {
|
||||
activeWorkspaceMeta,
|
||||
} = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
|
||||
const refetchRemoteBranch = useCallback(async () => {
|
||||
if (session.isLoggedIn()) {
|
||||
try {
|
||||
@@ -136,7 +139,7 @@ export const SyncDropdown: FC<Props> = ({ vcs, workspace, project }) => {
|
||||
|
||||
try {
|
||||
// NOTE pushes the first snapshot automatically
|
||||
await pushSnapshotOnInitialize({ vcs, workspace, workspaceMeta, project });
|
||||
await pushSnapshotOnInitialize({ vcs, workspace, workspaceMeta: activeWorkspaceMeta, project });
|
||||
await refreshVCSAndRefetchRemote();
|
||||
} catch (err) {
|
||||
console.log('[sync_menu] Error refreshing sync state', err);
|
||||
@@ -167,7 +170,7 @@ export const SyncDropdown: FC<Props> = ({ vcs, workspace, project }) => {
|
||||
const pulledIntoProject = await pullBackendProject({ vcs, backendProject, remoteProjects });
|
||||
if (pulledIntoProject.project._id !== project._id) {
|
||||
// If pulled into a different project, reactivate the workspace
|
||||
dispatch(activateWorkspace({ workspaceId: workspace._id }));
|
||||
navigate(`/organization/${organizationId}/project/${projectId}/workspace/${workspace._id}`);
|
||||
logCollectionMovedToProject(workspace, pulledIntoProject.project);
|
||||
}
|
||||
await refreshVCSAndRefetchRemote();
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import React, { FC, useCallback, useRef, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useRouteLoaderData } from 'react-router-dom';
|
||||
|
||||
import { isLoggedIn } from '../../../account/session';
|
||||
import { database as db } from '../../../common/database';
|
||||
import { getWorkspaceLabel } from '../../../common/get-workspace-label';
|
||||
import { RENDER_PURPOSE_NO_RENDER } from '../../../common/render';
|
||||
import { workspace } from '../../../models';
|
||||
import { isRequest } from '../../../models/request';
|
||||
import { isRequestGroup } from '../../../models/request-group';
|
||||
import { isDesign, Workspace } from '../../../models/workspace';
|
||||
@@ -12,20 +14,23 @@ import type { WorkspaceAction } from '../../../plugins';
|
||||
import { getWorkspaceActions } from '../../../plugins';
|
||||
import * as pluginContexts from '../../../plugins/context';
|
||||
import { useAIContext } from '../../context/app/ai-context';
|
||||
import { selectActiveApiSpec, selectActiveEnvironment, selectActiveProject, selectActiveWorkspace, selectActiveWorkspaceName, selectSettings } from '../../redux/selectors';
|
||||
import { selectSettings } from '../../redux/selectors';
|
||||
import { WorkspaceLoaderData } from '../../routes/workspace';
|
||||
import { Dropdown, DropdownButton, type DropdownHandle, DropdownItem, DropdownSection, ItemContent } from '../base/dropdown';
|
||||
import { InsomniaAI } from '../insomnia-ai-icon';
|
||||
import { showError, showModal } from '../modals';
|
||||
import { configGenerators, showGenerateConfigModal } from '../modals/generate-config-modal';
|
||||
import { SettingsModal, TAB_INDEX_EXPORT } from '../modals/settings-modal';
|
||||
import { WorkspaceSettingsModal } from '../modals/workspace-settings-modal';
|
||||
|
||||
export const WorkspaceDropdown: FC = () => {
|
||||
const activeEnvironment = useSelector(selectActiveEnvironment);
|
||||
const activeWorkspace = useSelector(selectActiveWorkspace);
|
||||
const activeWorkspaceName = useSelector(selectActiveWorkspaceName);
|
||||
const activeApiSpec = useSelector(selectActiveApiSpec);
|
||||
const activeProject = useSelector(selectActiveProject);
|
||||
const {
|
||||
activeWorkspace,
|
||||
activeEnvironment,
|
||||
activeProject,
|
||||
activeApiSpec,
|
||||
} = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
|
||||
const activeWorkspaceName = workspace.name;
|
||||
|
||||
const settings = useSelector(selectSettings);
|
||||
const { hotKeyRegistry } = settings;
|
||||
const [actionPlugins, setActionPlugins] = useState<WorkspaceAction[]>([]);
|
||||
@@ -41,12 +46,11 @@ export const WorkspaceDropdown: FC = () => {
|
||||
const handlePluginClick = useCallback(async ({ action, plugin, label }: WorkspaceAction, workspace: Workspace) => {
|
||||
setLoadingActions({ ...loadingActions, [label]: true });
|
||||
try {
|
||||
const activeEnvironmentId = activeEnvironment ? activeEnvironment._id : null;
|
||||
const context = {
|
||||
...(pluginContexts.app.init(RENDER_PURPOSE_NO_RENDER) as Record<string, any>),
|
||||
...pluginContexts.data.init(activeProject._id),
|
||||
...(pluginContexts.store.init(plugin) as Record<string, any>),
|
||||
...(pluginContexts.network.init(activeEnvironmentId) as Record<string, any>),
|
||||
...(pluginContexts.network.init(activeEnvironment._id) as Record<string, any>),
|
||||
};
|
||||
|
||||
const docs = await db.withDescendants(workspace);
|
||||
@@ -94,11 +98,6 @@ export const WorkspaceDropdown: FC = () => {
|
||||
});
|
||||
}, [activeApiSpec]);
|
||||
|
||||
if (!activeWorkspace) {
|
||||
console.error('warning: tried to render WorkspaceDropdown without an activeWorkspace');
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
aria-label="Workspace Dropdown"
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import clone from 'clone';
|
||||
import { isValid } from 'date-fns';
|
||||
import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useRouteLoaderData } from 'react-router-dom';
|
||||
import { Cookie as ToughCookie } from 'tough-cookie';
|
||||
|
||||
import { cookieToString } from '../../../common/cookies';
|
||||
import * as models from '../../../models';
|
||||
import type { Cookie } from '../../../models/cookie-jar';
|
||||
import { selectActiveCookieJar } from '../../redux/selectors';
|
||||
import { WorkspaceLoaderData } from '../../routes/workspace';
|
||||
import { Modal, type ModalHandle, ModalProps } from '../base/modal';
|
||||
import { ModalBody } from '../base/modal-body';
|
||||
import { ModalFooter } from '../base/modal-footer';
|
||||
@@ -24,7 +24,8 @@ export interface CookieModifyModalHandle {
|
||||
export const CookieModifyModal = forwardRef<CookieModifyModalHandle, ModalProps>((_, ref) => {
|
||||
const modalRef = useRef<ModalHandle>(null);
|
||||
const [cookie, setCookie] = useState<Cookie | null>(null);
|
||||
const activeCookieJar = useSelector(selectActiveCookieJar);
|
||||
const { activeCookieJar } = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
hide: () => {
|
||||
modalRef.current?.hide();
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useRouteLoaderData } from 'react-router-dom';
|
||||
|
||||
import { fuzzyMatch } from '../../../common/misc';
|
||||
import * as models from '../../../models';
|
||||
import type { Cookie } from '../../../models/cookie-jar';
|
||||
import { useNunjucks } from '../../context/nunjucks/use-nunjucks';
|
||||
import { selectActiveCookieJar } from '../../redux/selectors';
|
||||
import { WorkspaceLoaderData } from '../../routes/workspace';
|
||||
import { Modal, type ModalHandle, ModalProps } from '../base/modal';
|
||||
import { ModalBody } from '../base/modal-body';
|
||||
import { ModalFooter } from '../base/modal-footer';
|
||||
@@ -21,7 +21,7 @@ export const CookiesModal = forwardRef<CookiesModalHandle, ModalProps>((_, ref)
|
||||
const { handleRender } = useNunjucks();
|
||||
const [filter, setFilter] = useState<string>('');
|
||||
const [visibleCookieIndexes, setVisibleCookieIndexes] = useState<number[] | null>(null);
|
||||
const activeCookieJar = useSelector(selectActiveCookieJar);
|
||||
const { activeCookieJar } = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
hide: () => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import classnames from 'classnames';
|
||||
import React, { forwardRef, Fragment, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useNavigate, useParams, useRouteLoaderData } from 'react-router-dom';
|
||||
|
||||
import { METHOD_GRPC } from '../../../common/constants';
|
||||
import { fuzzyMatchAll } from '../../../common/misc';
|
||||
@@ -12,8 +13,8 @@ import { isWebSocketRequest, WebSocketRequest } from '../../../models/websocket-
|
||||
import { Workspace } from '../../../models/workspace';
|
||||
import { buildQueryStringFromParams, joinUrlAndQueryString } from '../../../utils/url/querystring';
|
||||
import { updateRequestMetaByParentId } from '../../hooks/create-request';
|
||||
import { activateWorkspace } from '../../redux/modules/workspace';
|
||||
import { selectActiveRequest, selectActiveWorkspace, selectActiveWorkspaceMeta, selectGrpcRequestMetas, selectRequestMetas, selectWorkspaceRequestsAndRequestGroups, selectWorkspacesForActiveProject } from '../../redux/selectors';
|
||||
import { selectActiveRequest, selectGrpcRequestMetas, selectRequestMetas, selectWorkspaceRequestsAndRequestGroups, selectWorkspacesForActiveProject } from '../../redux/selectors';
|
||||
import { WorkspaceLoaderData } from '../../routes/workspace';
|
||||
import { Highlight } from '../base/highlight';
|
||||
import { Modal, ModalHandle, ModalProps } from '../base/modal';
|
||||
import { ModalBody } from '../base/modal-body';
|
||||
@@ -23,7 +24,6 @@ import { GrpcTag } from '../tags/grpc-tag';
|
||||
import { MethodTag } from '../tags/method-tag';
|
||||
import { WebSocketTag } from '../tags/websocket-tag';
|
||||
import { wrapToIndex } from './utils';
|
||||
|
||||
interface State {
|
||||
searchString: string;
|
||||
workspacesForActiveProject: Workspace[];
|
||||
@@ -67,10 +67,13 @@ export const RequestSwitcherModal = forwardRef<RequestSwitcherModalHandle, Modal
|
||||
isModalVisible: true,
|
||||
title: null,
|
||||
});
|
||||
const dispatch = useDispatch();
|
||||
const { organizationId, projectId } = useParams<{ organizationId: string; projectId: string }>();
|
||||
const navigate = useNavigate();
|
||||
const activeRequest = useSelector(selectActiveRequest);
|
||||
const workspace = useSelector(selectActiveWorkspace);
|
||||
const activeWorkspaceMeta = useSelector(selectActiveWorkspaceMeta);
|
||||
const {
|
||||
activeWorkspace: workspace,
|
||||
activeWorkspaceMeta,
|
||||
} = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
|
||||
const workspacesForActiveProject = useSelector(selectWorkspacesForActiveProject);
|
||||
const requestMetas = useSelector(selectRequestMetas);
|
||||
const grpcRequestMetas = useSelector(selectGrpcRequestMetas);
|
||||
@@ -163,7 +166,7 @@ export const RequestSwitcherModal = forwardRef<RequestSwitcherModalHandle, Modal
|
||||
}
|
||||
|
||||
const matchedWorkspaces = workspacesForActiveProject
|
||||
.filter(w => w._id !== workspace?._id)
|
||||
.filter(w => w._id !== workspace._id)
|
||||
.filter(w => {
|
||||
const name = w.name.toLowerCase();
|
||||
const toMatch = searchString.toLowerCase();
|
||||
@@ -180,7 +183,7 @@ export const RequestSwitcherModal = forwardRef<RequestSwitcherModalHandle, Modal
|
||||
matchedRequests: matchedRequests.slice(0, maxRequests),
|
||||
matchedWorkspaces: matchedWorkspaces.slice(0, maxWorkspaces),
|
||||
}));
|
||||
}, [state, getLastActiveRequestMap, workspaceRequestsAndRequestGroups, workspacesForActiveProject, activeRequest, isMatch, workspace?._id]);
|
||||
}, [state, getLastActiveRequestMap, workspaceRequestsAndRequestGroups, workspacesForActiveProject, activeRequest, isMatch, workspace._id]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
hide: () => {
|
||||
@@ -206,21 +209,20 @@ export const RequestSwitcherModal = forwardRef<RequestSwitcherModalHandle, Modal
|
||||
},
|
||||
}), [handleChangeValue]);
|
||||
|
||||
const activateWorkspaceAndHide = useCallback((workspace?: Workspace) => {
|
||||
const activateWorkspaceAndHide = useCallback((workspace: Workspace) => {
|
||||
if (!workspace) {
|
||||
return;
|
||||
}
|
||||
dispatch(activateWorkspace({ workspace }));
|
||||
console.log(`[app] Activating workspace "${workspace.name}"`);
|
||||
navigate(`/organization/${organizationId}/project/${projectId}/workspace/${workspace._id}`);
|
||||
modalRef.current?.hide();
|
||||
}, [dispatch]);
|
||||
}, [navigate, organizationId, projectId]);
|
||||
|
||||
const activateRequestAndHide = useCallback((request?: Request | WebSocketRequest | GrpcRequest) => {
|
||||
if (!request) {
|
||||
return;
|
||||
}
|
||||
if (activeWorkspaceMeta) {
|
||||
models.workspaceMeta.update(activeWorkspaceMeta, { activeRequestId: request._id });
|
||||
}
|
||||
models.workspaceMeta.update(activeWorkspaceMeta, { activeRequestId: request._id });
|
||||
updateRequestMetaByParentId(request._id, { lastActive: Date.now() });
|
||||
modalRef.current?.hide();
|
||||
}, [activeWorkspaceMeta]);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React, { forwardRef, useImperativeHandle, useRef, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useRouteLoaderData } from 'react-router-dom';
|
||||
|
||||
import { strings } from '../../../common/strings';
|
||||
import { interceptAccessError } from '../../../sync/vcs/util';
|
||||
import { VCS } from '../../../sync/vcs/vcs';
|
||||
import { Button } from '../../components/themed-button';
|
||||
import { selectActiveWorkspace } from '../../redux/selectors';
|
||||
import { WorkspaceLoaderData } from '../../routes/workspace';
|
||||
import { Modal, type ModalHandle, ModalProps } from '../base/modal';
|
||||
import { ModalBody } from '../base/modal-body';
|
||||
import { ModalHeader } from '../base/modal-header';
|
||||
@@ -31,6 +31,9 @@ export const SyncDeleteModal = forwardRef<SyncDeleteModalHandle, Props>(({ vcs }
|
||||
error: '',
|
||||
workspaceName: '',
|
||||
});
|
||||
const {
|
||||
activeWorkspace,
|
||||
} = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
hide: () => modalRef.current?.hide(),
|
||||
@@ -43,7 +46,6 @@ export const SyncDeleteModal = forwardRef<SyncDeleteModalHandle, Props>(({ vcs }
|
||||
modalRef.current?.show({ onHide });
|
||||
},
|
||||
}), []);
|
||||
const activeWorkspace = useSelector(selectActiveWorkspace);
|
||||
const onSubmit = async (event: React.SyntheticEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
try {
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import classnames from 'classnames';
|
||||
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 type { Environment } from '../../../models/environment';
|
||||
import { selectActiveWorkspaceMeta } from '../../redux/selectors';
|
||||
import { WorkspaceLoaderData } from '../../routes/workspace';
|
||||
import { Dropdown, DropdownButton, DropdownItem, ItemContent } from '../base/dropdown';
|
||||
import { Editable } from '../base/editable';
|
||||
@@ -30,12 +28,14 @@ interface SidebarListItemProps {
|
||||
const SidebarListItem: FC<SidebarListItemProps> = ({
|
||||
environment,
|
||||
}: SidebarListItemProps) => {
|
||||
const workspaceMeta = useSelector(selectActiveWorkspaceMeta);
|
||||
const {
|
||||
activeWorkspaceMeta,
|
||||
} = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
|
||||
return (
|
||||
<div
|
||||
className={classnames({
|
||||
'env-modal__sidebar-item': true,
|
||||
'env-modal__sidebar-item--active': workspaceMeta?.activeEnvironmentId === environment._id,
|
||||
'env-modal__sidebar-item--active': activeWorkspaceMeta.activeEnvironmentId === environment._id,
|
||||
})}
|
||||
>
|
||||
{environment.color ? (
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { FC, forwardRef, ReactNode, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useRevalidator } from 'react-router-dom';
|
||||
import { useRouteLoaderData } from 'react-router-dom';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { ACTIVITY_HOME } from '../../../common/constants';
|
||||
import { database as db } from '../../../common/database';
|
||||
import { getWorkspaceLabel } from '../../../common/get-workspace-label';
|
||||
import { CaCertificate } from '../../../models/ca-certificate';
|
||||
@@ -12,8 +12,7 @@ import * as workspaceOperations from '../../../models/helpers/workspace-operatio
|
||||
import * as models from '../../../models/index';
|
||||
import { isRequest } from '../../../models/request';
|
||||
import { invariant } from '../../../utils/invariant';
|
||||
import { setActiveActivity } from '../../redux/modules/global';
|
||||
import { selectActiveApiSpec, selectActiveWorkspace, selectActiveWorkspaceClientCertificates, selectActiveWorkspaceMeta, selectActiveWorkspaceName } from '../../redux/selectors';
|
||||
import { WorkspaceLoaderData } from '../../routes/workspace';
|
||||
import { FileInputButton } from '../base/file-input-button';
|
||||
import { Modal, type ModalHandle, ModalProps } from '../base/modal';
|
||||
import { ModalBody } from '../base/modal-body';
|
||||
@@ -23,7 +22,6 @@ import { PanelContainer, TabItem, Tabs } from '../base/tabs';
|
||||
import { HelpTooltip } from '../help-tooltip';
|
||||
import { MarkdownEditor } from '../markdown-editor';
|
||||
import { PasswordViewer } from '../viewers/password-viewer';
|
||||
|
||||
const CertificateFields = styled.div({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
@@ -89,12 +87,16 @@ export const WorkspaceSettingsModal = forwardRef<WorkspaceSettingsModalHandle, M
|
||||
});
|
||||
|
||||
const { revalidate } = useRevalidator();
|
||||
const workspace = useSelector(selectActiveWorkspace);
|
||||
const apiSpec = useSelector(selectActiveApiSpec);
|
||||
const activeWorkspaceName = useSelector(selectActiveWorkspaceName);
|
||||
const clientCertificates = useSelector(selectActiveWorkspaceClientCertificates);
|
||||
const workspaceMeta = useSelector(selectActiveWorkspaceMeta);
|
||||
|
||||
const {
|
||||
activeWorkspace: workspace,
|
||||
activeWorkspaceMeta,
|
||||
activeApiSpec,
|
||||
clientCertificates,
|
||||
} = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
|
||||
const activeWorkspaceName = workspace.name;
|
||||
const navigate = useNavigate();
|
||||
const { organizationId } = useParams() as { organizationId: string };
|
||||
const [caCert, setCaCert] = useState<CaCertificate | null>(null);
|
||||
useEffect(() => {
|
||||
if (!workspace) {
|
||||
@@ -107,13 +109,12 @@ export const WorkspaceSettingsModal = forwardRef<WorkspaceSettingsModalHandle, M
|
||||
fn();
|
||||
}, [workspace]);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
useImperativeHandle(ref, () => ({
|
||||
hide: () => {
|
||||
modalRef.current?.hide();
|
||||
},
|
||||
show: () => {
|
||||
const hasDescription = !!workspace?.description;
|
||||
const hasDescription = !!workspace.description;
|
||||
setState(state => ({
|
||||
...state,
|
||||
showDescription: hasDescription,
|
||||
@@ -122,7 +123,7 @@ export const WorkspaceSettingsModal = forwardRef<WorkspaceSettingsModalHandle, M
|
||||
}));
|
||||
modalRef.current?.show();
|
||||
},
|
||||
}), [workspace?.description]);
|
||||
}), [workspace.description]);
|
||||
|
||||
const _handleClearAllResponses = async () => {
|
||||
if (!workspace) {
|
||||
@@ -157,7 +158,7 @@ export const WorkspaceSettingsModal = forwardRef<WorkspaceSettingsModalHandle, M
|
||||
const certificate = {
|
||||
host,
|
||||
isPrivate,
|
||||
parentId: workspace?._id,
|
||||
parentId: workspace._id,
|
||||
passphrase: passphrase || null,
|
||||
disabled: false,
|
||||
cert: crtPath || null,
|
||||
@@ -174,7 +175,8 @@ export const WorkspaceSettingsModal = forwardRef<WorkspaceSettingsModalHandle, M
|
||||
}
|
||||
await models.stats.incrementDeletedRequestsForDescendents(workspace);
|
||||
await models.workspace.remove(workspace);
|
||||
dispatch(setActiveActivity(ACTIVITY_HOME));
|
||||
navigate(`/organizations/${organizationId}`);
|
||||
|
||||
modalRef.current?.hide();
|
||||
};
|
||||
|
||||
@@ -248,7 +250,7 @@ export const WorkspaceSettingsModal = forwardRef<WorkspaceSettingsModalHandle, M
|
||||
type="text"
|
||||
placeholder="Awesome API"
|
||||
defaultValue={activeWorkspaceName}
|
||||
onChange={event => workspaceOperations.rename(event.target.value, workspace, apiSpec)}
|
||||
onChange={event => workspaceOperations.rename(event.target.value, workspace, activeApiSpec)}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
@@ -509,20 +511,20 @@ export const WorkspaceSettingsModal = forwardRef<WorkspaceSettingsModalHandle, M
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={Boolean(workspaceMeta?.gitRepositoryId)}
|
||||
checked={Boolean(activeWorkspaceMeta?.gitRepositoryId)}
|
||||
onChange={async () => {
|
||||
if (workspaceMeta?.gitRepositoryId) {
|
||||
await models.workspaceMeta.update(workspaceMeta, {
|
||||
if (activeWorkspaceMeta?.gitRepositoryId) {
|
||||
await models.workspaceMeta.update(activeWorkspaceMeta, {
|
||||
gitRepositoryId: null,
|
||||
});
|
||||
} else {
|
||||
invariant(workspaceMeta, 'Workspace meta not found');
|
||||
invariant(activeWorkspaceMeta, 'Workspace meta not found');
|
||||
|
||||
const repo = await models.gitRepository.create({
|
||||
uri: '',
|
||||
});
|
||||
|
||||
await models.workspaceMeta.update(workspaceMeta, {
|
||||
await models.workspaceMeta.update(activeWorkspaceMeta, {
|
||||
gitRepositoryId: repo._id,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { FunctionComponent, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useRouteLoaderData } from 'react-router-dom';
|
||||
import { useAsync } from 'react-use';
|
||||
import styled from 'styled-components';
|
||||
|
||||
@@ -12,8 +12,8 @@ import * as models from '../../../models';
|
||||
import type { GrpcRequest, GrpcRequestHeader } from '../../../models/grpc-request';
|
||||
import { queryAllWorkspaceUrls } from '../../../models/helpers/query-all-workspace-urls';
|
||||
import { useActiveRequestSyncVCSVersion, useGitVCSVersion } from '../../hooks/use-vcs-version';
|
||||
import { selectActiveEnvironment } from '../../redux/selectors';
|
||||
import { GrpcRequestState } from '../../routes/debug';
|
||||
import { WorkspaceLoaderData } from '../../routes/workspace';
|
||||
import { PanelContainer, TabItem, Tabs } from '../base/tabs';
|
||||
import { GrpcSendButton } from '../buttons/grpc-send-button';
|
||||
import { OneLineEditor } from '../codemirror/one-line-editor';
|
||||
@@ -92,10 +92,12 @@ export const GrpcRequestPane: FunctionComponent<Props> = ({
|
||||
|
||||
const gitVersion = useGitVCSVersion();
|
||||
const activeRequestSyncVersion = useActiveRequestSyncVCSVersion();
|
||||
const activeEnvironment = useSelector(selectActiveEnvironment);
|
||||
const environmentId = activeEnvironment?._id || 'n/a';
|
||||
const {
|
||||
activeEnvironment,
|
||||
} = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
|
||||
const environmentId = activeEnvironment._id;
|
||||
// Reset the response pane state when we switch requests, the environment gets modified, or the (Git|Sync)VCS version changes
|
||||
const uniquenessKey = `${activeEnvironment?.modified}::${activeRequest?._id}::${gitVersion}::${activeRequestSyncVersion}`;
|
||||
const uniquenessKey = `${activeEnvironment.modified}::${activeRequest?._id}::${gitVersion}::${activeRequestSyncVersion}`;
|
||||
const method = methods.find(c => c.fullPath === activeRequest.protoMethodName);
|
||||
const methodType = method?.type;
|
||||
const handleRequestSend = async () => {
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useRouteLoaderData } from 'react-router-dom';
|
||||
|
||||
import type { GrpcRequest } from '../../../models/grpc-request';
|
||||
import { useActiveRequestSyncVCSVersion, useGitVCSVersion } from '../../hooks/use-vcs-version';
|
||||
import { selectActiveEnvironment } from '../../redux/selectors';
|
||||
import { GrpcRequestState } from '../../routes/debug';
|
||||
import { WorkspaceLoaderData } from '../../routes/workspace';
|
||||
import { GrpcStatusTag } from '../tags/grpc-status-tag';
|
||||
import { GrpcTabbedMessages } from '../viewers/grpc-tabbed-messages';
|
||||
import { Pane, PaneBody, PaneHeader } from './pane';
|
||||
|
||||
interface Props {
|
||||
activeRequest: GrpcRequest;
|
||||
grpcState: GrpcRequestState;
|
||||
@@ -17,9 +16,11 @@ interface Props {
|
||||
export const GrpcResponsePane: FunctionComponent<Props> = ({ activeRequest, grpcState }) => {
|
||||
const gitVersion = useGitVCSVersion();
|
||||
const activeRequestSyncVersion = useActiveRequestSyncVCSVersion();
|
||||
const activeEnvironment = useSelector(selectActiveEnvironment);
|
||||
const {
|
||||
activeEnvironment,
|
||||
} = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
|
||||
// Force re-render when we switch requests, the environment gets modified, or the (Git|Sync)VCS version changes
|
||||
const uniquenessKey = `${activeEnvironment?.modified}::${activeRequest?._id}::${gitVersion}::${activeRequestSyncVersion}`;
|
||||
const uniquenessKey = `${activeEnvironment.modified}::${activeRequest?._id}::${gitVersion}::${activeRequestSyncVersion}`;
|
||||
|
||||
const { responseMessages, status, error } = grpcState;
|
||||
return (
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import React, { FC, useCallback } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { createRequest } from '../../hooks/create-request';
|
||||
import { selectActiveWorkspace, selectSettings } from '../../redux/selectors';
|
||||
import { selectSettings } from '../../redux/selectors';
|
||||
import { Hotkey } from '../hotkey';
|
||||
import { Pane, PaneBody, PaneHeader } from './pane';
|
||||
|
||||
export const PlaceholderRequestPane: FC = () => {
|
||||
const { hotKeyRegistry } = useSelector(selectSettings);
|
||||
const workspaceId = useSelector(selectActiveWorkspace)?._id;
|
||||
|
||||
const { workspaceId } = useParams<{ workspaceId: string }>();
|
||||
const createHttpRequest = useCallback(() => {
|
||||
if (workspaceId) {
|
||||
createRequest({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { FC, useCallback, useEffect, useRef } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useRouteLoaderData } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { version } from '../../../../package.json';
|
||||
@@ -14,7 +15,8 @@ import type { Settings } from '../../../models/settings';
|
||||
import { create, Workspace } from '../../../models/workspace';
|
||||
import { deconstructQueryStringToParams, extractQueryStringFromUrl } from '../../../utils/url/querystring';
|
||||
import { useActiveRequestSyncVCSVersion, useGitVCSVersion } from '../../hooks/use-vcs-version';
|
||||
import { selectActiveEnvironment, selectActiveRequestMeta } from '../../redux/selectors';
|
||||
import { selectActiveRequestMeta } from '../../redux/selectors';
|
||||
import { WorkspaceLoaderData } from '../../routes/workspace';
|
||||
import { PanelContainer, TabItem, Tabs } from '../base/tabs';
|
||||
import { AuthDropdown } from '../dropdowns/auth-dropdown';
|
||||
import { ContentTypeDropdown } from '../dropdowns/content-type-dropdown';
|
||||
@@ -31,7 +33,6 @@ import { RenderedQueryString } from '../rendered-query-string';
|
||||
import { RequestUrlBar, RequestUrlBarHandle } from '../request-url-bar';
|
||||
import { Pane, PaneHeader } from './pane';
|
||||
import { PlaceholderRequestPane } from './placeholder-request-pane';
|
||||
|
||||
const HeaderContainer = styled.div({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
@@ -268,7 +269,10 @@ export const RequestPane: FC<Props> = ({
|
||||
}, [request]);
|
||||
const gitVersion = useGitVCSVersion();
|
||||
const activeRequestSyncVersion = useActiveRequestSyncVCSVersion();
|
||||
const activeEnvironment = useSelector(selectActiveEnvironment);
|
||||
|
||||
const {
|
||||
activeEnvironment,
|
||||
} = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
|
||||
const activeRequestMeta = useSelector(selectActiveRequestMeta);
|
||||
// Force re-render when we switch requests, the environment gets modified, or the (Git|Sync)VCS version changes
|
||||
const uniqueKey = `${activeEnvironment?.modified}::${request?._id}::${gitVersion}::${activeRequestSyncVersion}::${activeRequestMeta?.activeResponseId}`;
|
||||
|
||||
@@ -5,6 +5,7 @@ import { extension as mimeExtension } from 'mime-types';
|
||||
import path from 'path';
|
||||
import React, { forwardRef, useCallback, useImperativeHandle, useRef, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useRouteLoaderData } from 'react-router-dom';
|
||||
import { useInterval } from 'react-use';
|
||||
import styled from 'styled-components';
|
||||
|
||||
@@ -16,13 +17,13 @@ import { update } from '../../models/helpers/request-operations';
|
||||
import { isEventStreamRequest, isRequest, Request } from '../../models/request';
|
||||
import * as network from '../../network/network';
|
||||
import { convert } from '../../utils/importers/convert';
|
||||
import { invariant } from '../../utils/invariant';
|
||||
import { buildQueryStringFromParams, joinUrlAndQueryString } from '../../utils/url/querystring';
|
||||
import { SegmentEvent } from '../analytics';
|
||||
import { updateRequestMetaByParentId } from '../hooks/create-request';
|
||||
import { useReadyState } from '../hooks/use-ready-state';
|
||||
import { useTimeoutWhen } from '../hooks/useTimeoutWhen';
|
||||
import { selectActiveEnvironment, selectActiveRequest, selectActiveWorkspace, selectHotKeyRegistry, selectResponseDownloadPath, selectSettings } from '../redux/selectors';
|
||||
import { selectActiveRequest, selectHotKeyRegistry, selectResponseDownloadPath, selectSettings } from '../redux/selectors';
|
||||
import { WorkspaceLoaderData } from '../routes/workspace';
|
||||
import { Dropdown, DropdownButton, type DropdownHandle, DropdownItem, DropdownSection, ItemContent } from './base/dropdown';
|
||||
import { OneLineEditor, OneLineEditorHandle } from './codemirror/one-line-editor';
|
||||
import { MethodDropdown } from './dropdowns/method-dropdown';
|
||||
@@ -61,10 +62,12 @@ export const RequestUrlBar = forwardRef<RequestUrlBarHandle, Props>(({
|
||||
uniquenessKey,
|
||||
setLoading,
|
||||
}, ref) => {
|
||||
const {
|
||||
activeWorkspace,
|
||||
activeEnvironment,
|
||||
} = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
|
||||
const downloadPath = useSelector(selectResponseDownloadPath);
|
||||
const hotKeyRegistry = useSelector(selectHotKeyRegistry);
|
||||
const activeEnvironment = useSelector(selectActiveEnvironment);
|
||||
const activeWorkspace = useSelector(selectActiveWorkspace);
|
||||
const activeRequest = useSelector(selectActiveRequest);
|
||||
const settings = useSelector(selectSettings);
|
||||
const methodDropdownRef = useRef<DropdownHandle>(null);
|
||||
@@ -123,7 +126,7 @@ export const RequestUrlBar = forwardRef<RequestUrlBarHandle, Props>(({
|
||||
});
|
||||
setLoading(true);
|
||||
try {
|
||||
const responsePatch = await network.send(request._id, activeEnvironment?._id);
|
||||
const responsePatch = await network.send(request._id, activeEnvironment._id);
|
||||
const headers = responsePatch.headers || [];
|
||||
const header = getContentDispositionHeader(headers);
|
||||
const nameFromHeader = header ? contentDisposition.parse(header.value).parameters.filename : null;
|
||||
@@ -189,7 +192,7 @@ export const RequestUrlBar = forwardRef<RequestUrlBarHandle, Props>(({
|
||||
await updateRequestMetaByParentId(request._id, { activeResponseId: null });
|
||||
setLoading(false);
|
||||
}
|
||||
}, [activeEnvironment?._id, request, setLoading, settings.maxHistoryResponses, settings.preferredHttpVersion]);
|
||||
}, [activeEnvironment._id, request, setLoading, settings.maxHistoryResponses, settings.preferredHttpVersion]);
|
||||
|
||||
const handleSend = useCallback(async () => {
|
||||
if (!request) {
|
||||
@@ -207,7 +210,7 @@ export const RequestUrlBar = forwardRef<RequestUrlBarHandle, Props>(({
|
||||
});
|
||||
setLoading(true);
|
||||
try {
|
||||
const responsePatch = await network.send(request._id, activeEnvironment?._id);
|
||||
const responsePatch = await network.send(request._id, activeEnvironment._id);
|
||||
await models.response.create(responsePatch, settings.maxHistoryResponses);
|
||||
} catch (err) {
|
||||
if (err.type === 'render') {
|
||||
@@ -232,7 +235,7 @@ export const RequestUrlBar = forwardRef<RequestUrlBarHandle, Props>(({
|
||||
// Unset active response because we just made a new one
|
||||
await updateRequestMetaByParentId(request._id, { activeResponseId: null });
|
||||
setLoading(false);
|
||||
}, [activeEnvironment?._id, request, setLoading, settings.maxHistoryResponses, settings.preferredHttpVersion]);
|
||||
}, [activeEnvironment._id, request, setLoading, settings.maxHistoryResponses, settings.preferredHttpVersion]);
|
||||
|
||||
const send = useCallback(() => {
|
||||
setCurrentTimeout(undefined);
|
||||
@@ -242,8 +245,7 @@ export const RequestUrlBar = forwardRef<RequestUrlBarHandle, Props>(({
|
||||
}
|
||||
if (isEventStreamRequest(request)) {
|
||||
const startListening = async () => {
|
||||
invariant(activeWorkspace, 'activeWorkspace not found (remove with redux)');
|
||||
const environmentId = activeEnvironment?._id;
|
||||
const environmentId = activeEnvironment._id;
|
||||
const workspaceId = activeWorkspace._id;
|
||||
const renderContext = await getRenderContext({ request, environmentId, purpose: RENDER_PURPOSE_SEND });
|
||||
// Render any nunjucks tags in the url/headers/authentication settings/cookies
|
||||
@@ -268,7 +270,7 @@ export const RequestUrlBar = forwardRef<RequestUrlBarHandle, Props>(({
|
||||
return;
|
||||
}
|
||||
handleSend();
|
||||
}, [activeEnvironment?._id, activeWorkspace, downloadPath, handleSend, request, sendThenSetFilePath]);
|
||||
}, [activeEnvironment._id, activeWorkspace, downloadPath, handleSend, request, sendThenSetFilePath]);
|
||||
|
||||
useInterval(send, currentInterval ? currentInterval : null);
|
||||
useTimeoutWhen(send, currentTimeout, !!currentTimeout);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { FC, Fragment, useCallback, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useRouteLoaderData } from 'react-router-dom';
|
||||
|
||||
import { getProductName } from '../../../common/constants';
|
||||
import { docsImportExport } from '../../../common/documentation';
|
||||
@@ -8,7 +9,8 @@ import { exportAllToFile } from '../../../common/export';
|
||||
import { getWorkspaceLabel } from '../../../common/get-workspace-label';
|
||||
import { strings } from '../../../common/strings';
|
||||
import { isRequestGroup } from '../../../models/request-group';
|
||||
import { selectActiveProjectName, selectActiveWorkspace, selectActiveWorkspaceName, selectWorkspaceRequestsAndRequestGroups, selectWorkspacesForActiveProject } from '../../redux/selectors';
|
||||
import { selectWorkspaceRequestsAndRequestGroups, selectWorkspacesForActiveProject } from '../../redux/selectors';
|
||||
import { WorkspaceLoaderData } from '../../routes/workspace';
|
||||
import { Dropdown, DropdownButton, DropdownItem, DropdownSection, ItemContent } from '../base/dropdown';
|
||||
import { Link } from '../base/link';
|
||||
import { AlertModal } from '../modals/alert-modal';
|
||||
@@ -16,7 +18,6 @@ import { ExportRequestsModal } from '../modals/export-requests-modal';
|
||||
import { ImportModal } from '../modals/import-modal';
|
||||
import { showModal } from '../modals/index';
|
||||
import { Button } from '../themed-button';
|
||||
|
||||
interface Props {
|
||||
hideSettingsModal: () => void;
|
||||
}
|
||||
@@ -27,9 +28,11 @@ export const ImportExport: FC<Props> = ({ hideSettingsModal }) => {
|
||||
projectId,
|
||||
workspaceId,
|
||||
} = useParams() as { organizationId: string; projectId: string; workspaceId?: string };
|
||||
const projectName = useSelector(selectActiveProjectName) ?? getProductName();
|
||||
const activeWorkspace = useSelector(selectActiveWorkspace);
|
||||
const activeWorkspaceName = useSelector(selectActiveWorkspaceName);
|
||||
|
||||
const workspaceData = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData | undefined;
|
||||
const activeWorkspaceName = workspaceData?.activeWorkspace.name;
|
||||
const projectName = workspaceData?.activeProject.name ?? getProductName();
|
||||
|
||||
const workspacesForActiveProject = useSelector(selectWorkspacesForActiveProject);
|
||||
const workspaceRequestsAndRequestGroups = useSelector(selectWorkspaceRequestsAndRequestGroups);
|
||||
const [isImportModalOpen, setIsImportModalOpen] = useState(false);
|
||||
@@ -61,7 +64,7 @@ export const ImportExport: FC<Props> = ({ hideSettingsModal }) => {
|
||||
Your format isn't supported? <Link href={docsImportExport}>Add Your Own</Link>.
|
||||
</p>
|
||||
<div className="pad-top">
|
||||
{activeWorkspace ?
|
||||
{workspaceData?.activeWorkspace ?
|
||||
(<Dropdown
|
||||
aria-label='Export Data Dropdown'
|
||||
triggerButton={
|
||||
@@ -74,10 +77,10 @@ export const ImportExport: FC<Props> = ({ hideSettingsModal }) => {
|
||||
aria-label="Choose Export Type"
|
||||
title="Choose Export Type"
|
||||
>
|
||||
<DropdownItem aria-label={`Export the "${activeWorkspaceName}" ${getWorkspaceLabel(activeWorkspace).singular}`}>
|
||||
<DropdownItem aria-label={`Export the "${activeWorkspaceName}" ${getWorkspaceLabel(workspaceData.activeWorkspace).singular}`}>
|
||||
<ItemContent
|
||||
icon="home"
|
||||
label={`Export the "${activeWorkspaceName}" ${getWorkspaceLabel(activeWorkspace).singular}`}
|
||||
label={`Export the "${activeWorkspaceName}" ${getWorkspaceLabel(workspaceData.activeWorkspace).singular}`}
|
||||
onClick={showExportRequestsModal}
|
||||
/>
|
||||
</DropdownItem>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import React, { FC, forwardRef, ReactNode, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useRouteLoaderData } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { COLLAPSE_SIDEBAR_REMS, DEFAULT_PANE_HEIGHT, DEFAULT_PANE_WIDTH, DEFAULT_SIDEBAR_WIDTH, MAX_PANE_HEIGHT, MAX_PANE_WIDTH, MAX_SIDEBAR_REMS, MIN_PANE_HEIGHT, MIN_PANE_WIDTH, MIN_SIDEBAR_REMS } from '../../common/constants';
|
||||
import { debounce } from '../../common/misc';
|
||||
import * as models from '../../models';
|
||||
import { selectActiveWorkspaceMeta, selectSettings } from '../redux/selectors';
|
||||
import { selectPaneHeight, selectPaneWidth, selectSidebarWidth } from '../redux/sidebar-selectors';
|
||||
import { selectSettings } from '../redux/selectors';
|
||||
import { WorkspaceLoaderData } from '../routes/workspace';
|
||||
import { ErrorBoundary } from './error-boundary';
|
||||
import { Sidebar } from './sidebar/sidebar';
|
||||
|
||||
const verticalStyles = {
|
||||
'.sidebar': {
|
||||
gridColumnStart: '2',
|
||||
@@ -203,11 +203,8 @@ export const SidebarLayout: FC<Props> = ({
|
||||
renderPageSidebar,
|
||||
}) => {
|
||||
const { forceVerticalLayout } = useSelector(selectSettings);
|
||||
const activeWorkspaceMeta = useSelector(selectActiveWorkspaceMeta);
|
||||
const reduxPaneHeight = useSelector(selectPaneHeight);
|
||||
const reduxPaneWidth = useSelector(selectPaneWidth);
|
||||
const reduxSidebarWidth = useSelector(selectSidebarWidth);
|
||||
|
||||
const workspaceData = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData | undefined;
|
||||
const { activeWorkspaceMeta } = workspaceData || {};
|
||||
const requestPaneRef = useRef<HTMLElement>(null);
|
||||
const responsePaneRef = useRef<HTMLElement>(null);
|
||||
const sidebarRef = useRef<HTMLElement>(null);
|
||||
@@ -215,9 +212,9 @@ export const SidebarLayout: FC<Props> = ({
|
||||
const [draggingSidebar, setDraggingSidebar] = useState(false);
|
||||
const [draggingPaneHorizontal, setDraggingPaneHorizontal] = useState(false);
|
||||
const [draggingPaneVertical, setDraggingPaneVertical] = useState(false);
|
||||
const [sidebarWidth, setSidebarWidth] = useState(reduxSidebarWidth || DEFAULT_SIDEBAR_WIDTH);
|
||||
const [paneWidth, setPaneWidth] = useState(reduxPaneWidth || DEFAULT_PANE_WIDTH);
|
||||
const [paneHeight, setPaneHeight] = useState(reduxPaneHeight || DEFAULT_PANE_HEIGHT);
|
||||
const [sidebarWidth, setSidebarWidth] = useState(activeWorkspaceMeta?.sidebarWidth || DEFAULT_SIDEBAR_WIDTH);
|
||||
const [paneWidth, setPaneWidth] = useState(activeWorkspaceMeta?.paneWidth || DEFAULT_PANE_WIDTH);
|
||||
const [paneHeight, setPaneHeight] = useState(activeWorkspaceMeta?.paneHeight || DEFAULT_PANE_HEIGHT);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = window.main.on('toggle-sidebar', () => {
|
||||
@@ -265,7 +262,7 @@ export const SidebarLayout: FC<Props> = ({
|
||||
const handleMouseMove = useCallback((event: MouseEvent) => {
|
||||
if (draggingPaneHorizontal) {
|
||||
// Only pop the overlay after we've moved it a bit (so we don't block doubleclick);
|
||||
const distance = reduxPaneWidth - paneWidth;
|
||||
const distance = (activeWorkspaceMeta?.paneWidth || DEFAULT_PANE_WIDTH) - paneWidth;
|
||||
|
||||
if (!showDragOverlay && Math.abs(distance) > 0.02) {
|
||||
setShowDragOverlay(true);
|
||||
@@ -283,7 +280,7 @@ export const SidebarLayout: FC<Props> = ({
|
||||
}
|
||||
} else if (draggingPaneVertical) {
|
||||
// Only pop the overlay after we've moved it a bit (so we don't block doubleclick);
|
||||
const distance = reduxPaneHeight - paneHeight;
|
||||
const distance = (activeWorkspaceMeta?.paneHeight || DEFAULT_PANE_HEIGHT) - paneHeight;
|
||||
/* % */
|
||||
if (!showDragOverlay && Math.abs(distance) > 0.02) {
|
||||
setShowDragOverlay(true);
|
||||
@@ -300,7 +297,7 @@ export const SidebarLayout: FC<Props> = ({
|
||||
}
|
||||
} else if (draggingSidebar) {
|
||||
// Only pop the overlay after we've moved it a bit (so we don't block doubleclick);
|
||||
const distance = reduxSidebarWidth - sidebarWidth;
|
||||
const distance = (activeWorkspaceMeta?.sidebarWidth || DEFAULT_SIDEBAR_WIDTH) - sidebarWidth;
|
||||
/* ems */
|
||||
if (!showDragOverlay && Math.abs(distance) > 2) {
|
||||
setShowDragOverlay(true);
|
||||
@@ -317,7 +314,7 @@ export const SidebarLayout: FC<Props> = ({
|
||||
handleSetSidebarWidth(localSidebarWidth);
|
||||
}
|
||||
}
|
||||
}, [draggingPaneHorizontal, draggingPaneVertical, draggingSidebar, handleSetPaneHeight, handleSetPaneWidth, handleSetSidebarWidth, paneHeight, paneWidth, reduxPaneHeight, reduxPaneWidth, reduxSidebarWidth, showDragOverlay, sidebarWidth]);
|
||||
}, [activeWorkspaceMeta?.paneHeight, activeWorkspaceMeta?.paneWidth, activeWorkspaceMeta?.sidebarWidth, draggingPaneHorizontal, draggingPaneVertical, draggingSidebar, handleSetPaneHeight, handleSetPaneWidth, handleSetSidebarWidth, paneHeight, paneWidth, showDragOverlay, sidebarWidth]);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('mouseup', handleMouseUp);
|
||||
|
||||
@@ -1,32 +1,34 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useRouteLoaderData } from 'react-router-dom';
|
||||
|
||||
import { createRequest, CreateRequestType } from '../../hooks/create-request';
|
||||
import { createRequestGroup } from '../../hooks/create-request-group';
|
||||
import { selectActiveWorkspace, selectHotKeyRegistry } from '../../redux/selectors';
|
||||
import { selectHotKeyRegistry } from '../../redux/selectors';
|
||||
import { WorkspaceLoaderData } from '../../routes/workspace';
|
||||
import { Dropdown, DropdownButton, DropdownItem, ItemContent } from '../base/dropdown';
|
||||
|
||||
export const SidebarCreateDropdown = () => {
|
||||
const hotKeyRegistry = useSelector(selectHotKeyRegistry);
|
||||
const activeWorkspace = useSelector(selectActiveWorkspace);
|
||||
const activeWorkspaceId = activeWorkspace?._id;
|
||||
const {
|
||||
activeWorkspace,
|
||||
} = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
|
||||
const create = useCallback((value: CreateRequestType) => {
|
||||
if (activeWorkspaceId) {
|
||||
if (activeWorkspace._id) {
|
||||
createRequest({
|
||||
requestType: value,
|
||||
parentId: activeWorkspaceId,
|
||||
workspaceId: activeWorkspaceId,
|
||||
parentId: activeWorkspace._id,
|
||||
workspaceId: activeWorkspace._id,
|
||||
});
|
||||
}
|
||||
}, [activeWorkspaceId]);
|
||||
}, [activeWorkspace._id]);
|
||||
|
||||
const createGroup = useCallback(() => {
|
||||
if (!activeWorkspaceId) {
|
||||
if (!activeWorkspace._id) {
|
||||
return;
|
||||
}
|
||||
|
||||
createRequestGroup(activeWorkspaceId);
|
||||
}, [activeWorkspaceId]);
|
||||
createRequestGroup(activeWorkspace._id);
|
||||
}, [activeWorkspace._id]);
|
||||
const dataTestId = 'SidebarCreateDropdown';
|
||||
return (
|
||||
<Dropdown
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
import React, { FC, useCallback, useRef } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useRouteLoaderData } from 'react-router-dom';
|
||||
|
||||
import { SortOrder } from '../../../common/constants';
|
||||
import { database as db } from '../../../common/database';
|
||||
import { sortMethodMap } from '../../../common/sorting';
|
||||
import * as models from '../../../models';
|
||||
import { isRequestGroup } from '../../../models/request-group';
|
||||
import { selectActiveWorkspace, selectActiveWorkspaceMeta } from '../../redux/selectors';
|
||||
import { WorkspaceLoaderData } from '../../routes/workspace';
|
||||
import { useDocBodyKeyboardShortcuts } from '../keydown-binder';
|
||||
import { SidebarCreateDropdown } from './sidebar-create-dropdown';
|
||||
import { SidebarSortDropdown } from './sidebar-sort-dropdown';
|
||||
|
||||
interface Props {
|
||||
filter: string;
|
||||
}
|
||||
|
||||
export const SidebarFilter: FC<Props> = ({ filter }) => {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const activeWorkspace = useSelector(selectActiveWorkspace);
|
||||
const activeWorkspaceMeta = useSelector(selectActiveWorkspaceMeta);
|
||||
|
||||
const {
|
||||
activeWorkspace,
|
||||
activeWorkspaceMeta,
|
||||
} = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
|
||||
|
||||
const handleClearFilter = useCallback(async () => {
|
||||
if (activeWorkspaceMeta) {
|
||||
await models.workspaceMeta.update(activeWorkspaceMeta, { sidebarFilter: '' });
|
||||
}
|
||||
await models.workspaceMeta.update(activeWorkspaceMeta, { sidebarFilter: '' });
|
||||
if (inputRef.current) {
|
||||
inputRef.current.value = '';
|
||||
inputRef.current.focus();
|
||||
@@ -31,9 +31,7 @@ export const SidebarFilter: FC<Props> = ({ filter }) => {
|
||||
}, [activeWorkspaceMeta]);
|
||||
|
||||
const handleOnChange = useCallback(async (event: React.SyntheticEvent<HTMLInputElement>) => {
|
||||
if (activeWorkspaceMeta) {
|
||||
await models.workspaceMeta.update(activeWorkspaceMeta, { sidebarFilter: event.currentTarget.value });
|
||||
}
|
||||
await models.workspaceMeta.update(activeWorkspaceMeta, { sidebarFilter: event.currentTarget.value });
|
||||
}, [activeWorkspaceMeta]);
|
||||
|
||||
useDocBodyKeyboardShortcuts({
|
||||
@@ -44,9 +42,6 @@ export const SidebarFilter: FC<Props> = ({ filter }) => {
|
||||
|
||||
const sortSidebar = async (order: SortOrder, parentId?: string) => {
|
||||
let flushId: number | undefined;
|
||||
if (!activeWorkspace) {
|
||||
return;
|
||||
}
|
||||
if (!parentId) {
|
||||
parentId = activeWorkspace._id;
|
||||
flushId = await db.bufferChanges();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import classnames from 'classnames';
|
||||
import React, { FC, forwardRef, MouseEvent, ReactElement, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
||||
import { DragSource, DragSourceSpec, DropTarget, DropTargetSpec } from 'react-dnd';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useRouteLoaderData } from 'react-router-dom';
|
||||
|
||||
import { CONTENT_TYPE_GRAPHQL } from '../../../common/constants';
|
||||
import { getMethodOverrideHeader } from '../../../common/misc';
|
||||
@@ -14,7 +14,7 @@ import { isWebSocketRequest, WebSocketRequest } from '../../../models/websocket-
|
||||
import { useNunjucks } from '../../context/nunjucks/use-nunjucks';
|
||||
import { createRequest, updateRequestMetaByParentId } from '../../hooks/create-request';
|
||||
import { useReadyState } from '../../hooks/use-ready-state';
|
||||
import { selectActiveEnvironment, selectActiveProject, selectActiveWorkspace, selectActiveWorkspaceMeta } from '../../redux/selectors';
|
||||
import { WorkspaceLoaderData } from '../../routes/workspace';
|
||||
import type { DropdownHandle } from '../base/dropdown';
|
||||
import { Editable } from '../base/editable';
|
||||
import { Highlight } from '../base/highlight';
|
||||
@@ -27,7 +27,6 @@ import { MethodTag } from '../tags/method-tag';
|
||||
import { WebSocketTag } from '../tags/websocket-tag';
|
||||
import { ConnectionCircle } from '../websockets/action-bar';
|
||||
import { DnDProps, DragObject, dropHandleCreator, hoverHandleCreator, sourceCollect, targetCollect } from './dnd';
|
||||
|
||||
interface RawProps {
|
||||
disableDragAndDrop?: boolean;
|
||||
filter: string;
|
||||
@@ -68,11 +67,14 @@ export const _SidebarRequestRow: FC<Props> = forwardRef(({
|
||||
requestGroup,
|
||||
}, ref) => {
|
||||
const { handleRender } = useNunjucks();
|
||||
const activeProject = useSelector(selectActiveProject);
|
||||
const activeEnvironment = useSelector(selectActiveEnvironment);
|
||||
const activeWorkspace = useSelector(selectActiveWorkspace);
|
||||
const activeWorkspaceMeta = useSelector(selectActiveWorkspaceMeta);
|
||||
const activeWorkspaceId = activeWorkspace?._id;
|
||||
const {
|
||||
activeWorkspace,
|
||||
activeWorkspaceMeta,
|
||||
activeEnvironment,
|
||||
activeProject,
|
||||
} = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
|
||||
|
||||
const activeWorkspaceId = activeWorkspace._id;
|
||||
const [dragDirection, setDragDirection] = useState(0);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const handleSetActiveRequest = useCallback(() => {
|
||||
@@ -236,7 +238,7 @@ export const _SidebarRequestRow: FC<Props> = forwardRef(({
|
||||
);
|
||||
} else {
|
||||
|
||||
let methodTag = null;
|
||||
let methodTag;
|
||||
|
||||
if (isGrpcRequest(request)) {
|
||||
methodTag = <GrpcTag />;
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import React, { FC, ReactNode, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useRouteLoaderData } from 'react-router-dom';
|
||||
|
||||
import { VCS } from '../../sync/vcs/vcs';
|
||||
import { selectActiveProject } from '../redux/selectors';
|
||||
import { WorkspaceLoaderData } from '../routes/workspace';
|
||||
import { showError } from './modals';
|
||||
|
||||
interface Props {
|
||||
vcs: VCS;
|
||||
branch: string;
|
||||
@@ -16,7 +15,9 @@ interface Props {
|
||||
|
||||
export const SyncPullButton: FC<Props> = props => {
|
||||
const { className, children, disabled } = props;
|
||||
const project = useSelector(selectActiveProject);
|
||||
const {
|
||||
activeProject,
|
||||
} = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
|
||||
const [loading, setLoading] = useState(false);
|
||||
const onClick = async () => {
|
||||
const { vcs, onPull, branch } = props;
|
||||
@@ -27,7 +28,7 @@ export const SyncPullButton: FC<Props> = props => {
|
||||
try {
|
||||
// Clone old VCS so we don't mess anything up while working on other projects
|
||||
await newVCS.checkout([], branch);
|
||||
await newVCS.pull([], project.remoteId);
|
||||
await newVCS.pull([], activeProject.remoteId);
|
||||
} catch (err) {
|
||||
showError({
|
||||
title: 'Pull Error',
|
||||
|
||||
@@ -1,195 +0,0 @@
|
||||
import { beforeEach, describe, expect, it, jest } from '@jest/globals';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { mocked } from 'jest-mock';
|
||||
import configureMockStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
import type { PromiseValue } from 'type-fest';
|
||||
|
||||
import { globalBeforeEach } from '../../../../__jest__/before-each';
|
||||
import { reduxStateForTest } from '../../../../__jest__/redux-state-for-test';
|
||||
import { withReduxStore } from '../../../../__jest__/with-redux-store';
|
||||
import { ACTIVITY_DEBUG } from '../../../../common/constants';
|
||||
import { getRenderContext, getRenderContextAncestors, render } from '../../../../common/render';
|
||||
import * as models from '../../../../models';
|
||||
import { RootState } from '../../../redux/modules';
|
||||
import { initializeNunjucksRenderPromiseCache, useNunjucks } from '../use-nunjucks';
|
||||
|
||||
const renderMock = mocked(render);
|
||||
const getRenderContextMock = mocked(getRenderContext);
|
||||
const getRenderContextAncestorsMock = mocked(getRenderContextAncestors);
|
||||
|
||||
jest.mock('../../../../common/render', () => ({
|
||||
render: jest.fn(),
|
||||
getRenderContext: jest.fn(),
|
||||
getRenderContextAncestors: jest.fn(),
|
||||
}));
|
||||
|
||||
const middlewares = [thunk];
|
||||
const mockStore = configureMockStore<RootState>(middlewares);
|
||||
|
||||
const mockAncestors: PromiseValue<ReturnType<typeof getRenderContextAncestorsMock>> = [];
|
||||
const mockContext: PromiseValue<ReturnType<typeof getRenderContextMock>> = { foo: 'bar' };
|
||||
|
||||
describe('useNunjucks', () => {
|
||||
beforeEach(async () => {
|
||||
await globalBeforeEach();
|
||||
getRenderContextMock.mockResolvedValue(mockContext);
|
||||
getRenderContextAncestorsMock.mockResolvedValue(mockAncestors);
|
||||
initializeNunjucksRenderPromiseCache();
|
||||
});
|
||||
|
||||
describe('handleGetRenderContext', () => {
|
||||
|
||||
it('should return context with keys', async () => {
|
||||
const store = mockStore(await reduxStateForTest({
|
||||
activeActivity: ACTIVITY_DEBUG,
|
||||
}));
|
||||
|
||||
const { result } = renderHook(useNunjucks, { wrapper: withReduxStore(store) });
|
||||
|
||||
const context = await result.current.handleGetRenderContext();
|
||||
|
||||
expect(context).toStrictEqual({
|
||||
context: mockContext,
|
||||
keys: [{
|
||||
name: '_.foo',
|
||||
value: 'bar',
|
||||
}],
|
||||
});
|
||||
});
|
||||
|
||||
it('should get context using the active entities', async () => {
|
||||
// Arrange
|
||||
const workspace = await models.workspace.create();
|
||||
await models.workspaceMeta.getOrCreateByParentId(workspace._id);
|
||||
const environment = await models.environment.getOrCreateForParentId(workspace._id);
|
||||
const request = await models.request.create({ parentId: workspace._id });
|
||||
|
||||
await models.workspaceMeta.updateByParentId(workspace._id, {
|
||||
activeEnvironmentId: environment._id,
|
||||
activeRequestId: request._id,
|
||||
});
|
||||
|
||||
const store = mockStore(await reduxStateForTest({
|
||||
activeActivity: ACTIVITY_DEBUG,
|
||||
activeWorkspaceId: workspace._id,
|
||||
}));
|
||||
|
||||
// Act
|
||||
const { result } = renderHook(useNunjucks, { wrapper: withReduxStore(store) });
|
||||
await result.current.handleGetRenderContext();
|
||||
|
||||
// Assert
|
||||
expect(getRenderContextAncestorsMock).toBeCalledWith(request);
|
||||
expect(getRenderContextMock).toBeCalledWith({
|
||||
request,
|
||||
environmentId: environment._id,
|
||||
ancestors: mockAncestors,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('handleRender', () => {
|
||||
it('should render and get context once', async () => {
|
||||
// Arrange
|
||||
const store = mockStore(await reduxStateForTest());
|
||||
|
||||
// Act
|
||||
const { result } = renderHook(useNunjucks, { wrapper: withReduxStore(store) });
|
||||
await result.current.handleRender('abc');
|
||||
|
||||
// Assert
|
||||
expect(getRenderContextMock).toHaveBeenCalledTimes(1);
|
||||
expect(renderMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should render and get context twice because there is no caching', async () => {
|
||||
// Arrange
|
||||
const store = mockStore(await reduxStateForTest());
|
||||
|
||||
// Act
|
||||
const { result } = renderHook(useNunjucks, { wrapper: withReduxStore(store) });
|
||||
await result.current.handleRender('abc');
|
||||
await result.current.handleRender('def');
|
||||
|
||||
// Assert
|
||||
expect(getRenderContextMock).toHaveBeenCalledTimes(2);
|
||||
expect(renderMock).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should render and get context once because there is a cache', async () => {
|
||||
// Arrange
|
||||
const store = mockStore(await reduxStateForTest());
|
||||
|
||||
// Act
|
||||
const { result } = renderHook(useNunjucks, { wrapper: withReduxStore(store) });
|
||||
const cacheKey = 'cache';
|
||||
|
||||
await result.current.handleRender('abc', cacheKey);
|
||||
await result.current.handleRender('def', cacheKey);
|
||||
|
||||
// Assert
|
||||
expect(getRenderContextMock).toHaveBeenCalledTimes(1);
|
||||
expect(renderMock).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should render and get context twice because there are different cache keys', async () => {
|
||||
// Arrange
|
||||
const store = mockStore(await reduxStateForTest());
|
||||
|
||||
// Act
|
||||
const { result } = renderHook(useNunjucks, { wrapper: withReduxStore(store) });
|
||||
const cacheKeyOne = 'cache-1';
|
||||
const cacheKeyTwo = 'cache-2';
|
||||
|
||||
await result.current.handleRender('abc', cacheKeyOne);
|
||||
await result.current.handleRender('def', cacheKeyTwo);
|
||||
await result.current.handleRender('ghi', cacheKeyOne);
|
||||
await result.current.handleRender('jkl', cacheKeyTwo);
|
||||
|
||||
// Assert
|
||||
expect(getRenderContextMock).toHaveBeenCalledTimes(2);
|
||||
expect(renderMock).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
|
||||
it('should not change the cache during re-renders of the hook', async () => {
|
||||
// Arrange
|
||||
const store = mockStore(await reduxStateForTest());
|
||||
|
||||
// Act
|
||||
const { result, rerender } = renderHook(useNunjucks, { wrapper: withReduxStore(store) });
|
||||
const cacheKeyOne = 'cache-1';
|
||||
const cacheKeyTwo = 'cache-2';
|
||||
|
||||
await result.current.handleRender('abc', cacheKeyOne);
|
||||
rerender();
|
||||
await result.current.handleRender('def', cacheKeyTwo);
|
||||
rerender();
|
||||
await result.current.handleRender('ghi', cacheKeyOne);
|
||||
rerender();
|
||||
await result.current.handleRender('jkl', cacheKeyTwo);
|
||||
|
||||
// Assert
|
||||
expect(getRenderContextMock).toHaveBeenCalledTimes(2);
|
||||
expect(renderMock).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
|
||||
it('should not change the cache during multiple renders of the hook', async () => {
|
||||
// Arrange
|
||||
const store = mockStore(await reduxStateForTest());
|
||||
|
||||
// Act
|
||||
const cacheKeyOne = 'cache-1';
|
||||
const cacheKeyTwo = 'cache-2';
|
||||
|
||||
await renderHook(useNunjucks, { wrapper: withReduxStore(store) }).result.current.handleRender('abc', cacheKeyOne);
|
||||
await renderHook(useNunjucks, { wrapper: withReduxStore(store) }).result.current.handleRender('def', cacheKeyTwo);
|
||||
await renderHook(useNunjucks, { wrapper: withReduxStore(store) }).result.current.handleRender('ghi', cacheKeyOne);
|
||||
await renderHook(useNunjucks, { wrapper: withReduxStore(store) }).result.current.handleRender('jkl', cacheKeyTwo);
|
||||
|
||||
// Assert
|
||||
expect(getRenderContextMock).toHaveBeenCalledTimes(2);
|
||||
expect(renderMock).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useRouteLoaderData } from 'react-router-dom';
|
||||
|
||||
import { getRenderContext, getRenderContextAncestors, HandleGetRenderContext, HandleRender, render } from '../../../common/render';
|
||||
import { NUNJUCKS_TEMPLATE_GLOBAL_PROPERTY_NAME } from '../../../templating';
|
||||
import { getKeys } from '../../../templating/utils';
|
||||
import { selectActiveEnvironment, selectActiveRequest, selectActiveWorkspace } from '../../redux/selectors';
|
||||
|
||||
import { selectActiveRequest } from '../../redux/selectors';
|
||||
import { WorkspaceLoaderData } from '../../routes/workspace';
|
||||
let getRenderContextPromiseCache: any = {};
|
||||
|
||||
export const initializeNunjucksRenderPromiseCache = () => {
|
||||
@@ -18,18 +19,19 @@ initializeNunjucksRenderPromiseCache();
|
||||
* Access to functions useful for Nunjucks rendering
|
||||
*/
|
||||
export const useNunjucks = () => {
|
||||
const environmentId = useSelector(selectActiveEnvironment)?._id;
|
||||
const request = useSelector(selectActiveRequest);
|
||||
const workspace = useSelector(selectActiveWorkspace);
|
||||
|
||||
const {
|
||||
activeWorkspace,
|
||||
activeEnvironment,
|
||||
} = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
|
||||
const fetchRenderContext = useCallback(async () => {
|
||||
const ancestors = await getRenderContextAncestors(request || workspace);
|
||||
const ancestors = await getRenderContextAncestors(request || activeWorkspace);
|
||||
return getRenderContext({
|
||||
request: request || undefined,
|
||||
environmentId,
|
||||
environmentId: activeEnvironment._id,
|
||||
ancestors,
|
||||
});
|
||||
}, [environmentId, request, workspace]);
|
||||
}, [activeEnvironment._id, request, activeWorkspace]);
|
||||
|
||||
const handleGetRenderContext: HandleGetRenderContext = useCallback(async () => {
|
||||
const context = await fetchRenderContext();
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useRouteLoaderData } from 'react-router-dom';
|
||||
|
||||
import { ACTIVITY_HOME, getProductName } from '../../common/constants';
|
||||
import { selectActiveActivity, selectActiveEnvironment, selectActiveProject, selectActiveRequest, selectActiveWorkspace, selectActiveWorkspaceName } from '../redux/selectors';
|
||||
import { getProductName } from '../../common/constants';
|
||||
import { selectActiveRequest } from '../redux/selectors';
|
||||
import { WorkspaceLoaderData } from '../routes/workspace';
|
||||
|
||||
export const useDocumentTitle = () => {
|
||||
const activeActivity = useSelector(selectActiveActivity);
|
||||
const activeProject = useSelector(selectActiveProject);
|
||||
const activeWorkspaceName = useSelector(selectActiveWorkspaceName);
|
||||
const activeWorkspace = useSelector(selectActiveWorkspace);
|
||||
const {
|
||||
activeWorkspace,
|
||||
activeEnvironment,
|
||||
activeProject,
|
||||
} = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
|
||||
|
||||
const activeEnvironment = useSelector(selectActiveEnvironment);
|
||||
const activeRequest = useSelector(selectActiveRequest);
|
||||
// Update document title
|
||||
useEffect(() => {
|
||||
let title;
|
||||
if (activeActivity === ACTIVITY_HOME) {
|
||||
title = getProductName();
|
||||
} else if (activeWorkspace && activeWorkspaceName) {
|
||||
if (activeWorkspace && activeWorkspace.name) {
|
||||
title = activeProject.name;
|
||||
title += ` - ${activeWorkspaceName}`;
|
||||
title += ` - ${activeWorkspace.name}`;
|
||||
if (activeEnvironment) {
|
||||
title += ` (${activeEnvironment.name})`;
|
||||
}
|
||||
@@ -28,6 +28,6 @@ export const useDocumentTitle = () => {
|
||||
}
|
||||
}
|
||||
document.title = title || getProductName();
|
||||
}, [activeActivity, activeEnvironment, activeProject.name, activeRequest, activeWorkspace, activeWorkspaceName]);
|
||||
}, [activeEnvironment, activeProject.name, activeRequest, activeWorkspace]);
|
||||
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useRouteLoaderData } from 'react-router-dom';
|
||||
|
||||
import * as models from '../../models';
|
||||
import * as plugins from '../../plugins';
|
||||
@@ -6,13 +7,12 @@ import { useDocBodyKeyboardShortcuts } from '../components/keydown-binder';
|
||||
import { showModal } from '../components/modals';
|
||||
import { SettingsModal, TAB_INDEX_SHORTCUTS } from '../components/modals/settings-modal';
|
||||
import { WorkspaceSettingsModal } from '../components/modals/workspace-settings-modal';
|
||||
import { selectActiveWorkspace, selectActiveWorkspaceMeta, selectSettings } from '../redux/selectors';
|
||||
|
||||
import { selectSettings } from '../redux/selectors';
|
||||
import { WorkspaceLoaderData } from '../routes/workspace';
|
||||
export const useGlobalKeyboardShortcuts = () => {
|
||||
const activeWorkspace = useSelector(selectActiveWorkspace);
|
||||
const activeWorkspaceMeta = useSelector(selectActiveWorkspaceMeta);
|
||||
const workspaceData = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData | undefined;
|
||||
const settings = useSelector(selectSettings);
|
||||
|
||||
const { activeWorkspace, activeWorkspaceMeta } = workspaceData || {};
|
||||
useDocBodyKeyboardShortcuts({
|
||||
workspace_showSettings:
|
||||
() => activeWorkspace && showModal(WorkspaceSettingsModal),
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useRouteLoaderData } from 'react-router-dom';
|
||||
|
||||
import { ChangeBufferEvent, database } from '../../common/database';
|
||||
import { BaseModel } from '../../models';
|
||||
import {
|
||||
selectActiveApiSpec,
|
||||
selectActiveRequest,
|
||||
selectActiveWorkspaceMeta,
|
||||
} from '../redux/selectors';
|
||||
|
||||
import { WorkspaceLoaderData } from '../routes/workspace';
|
||||
// We use this hook to determine if the active request has been updated from the system (not the user typing)
|
||||
// For example, by pulling a new version from the remote, switching branches, etc.
|
||||
export function useActiveRequestSyncVCSVersion() {
|
||||
@@ -27,8 +26,9 @@ export function useActiveRequestSyncVCSVersion() {
|
||||
// For example, by pulling a new version from the remote, switching branches, etc.
|
||||
export function useActiveApiSpecSyncVCSVersion() {
|
||||
const [version, setVersion] = useState(0);
|
||||
const activeApiSpec = useSelector(selectActiveApiSpec);
|
||||
|
||||
const {
|
||||
activeApiSpec,
|
||||
} = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
|
||||
useEffect(() => {
|
||||
const isRequestUpdatedFromSync = (changes: ChangeBufferEvent<BaseModel>[]) => changes.find(([, doc, fromSync]) => activeApiSpec?._id === doc._id && fromSync);
|
||||
database.onChange(changes => isRequestUpdatedFromSync(changes) && setVersion(v => v + 1));
|
||||
@@ -40,7 +40,8 @@ export function useActiveApiSpecSyncVCSVersion() {
|
||||
// We use this hook to determine if the active workspace has been updated from the Git VCS
|
||||
// For example, by pulling a new version from the remote, switching branches, etc.
|
||||
export function useGitVCSVersion() {
|
||||
const activeWorkspaceMeta = useSelector(selectActiveWorkspaceMeta);
|
||||
|
||||
const {
|
||||
activeWorkspaceMeta,
|
||||
} = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
|
||||
return ((activeWorkspaceMeta?.cachedGitLastCommitTime + '') + activeWorkspaceMeta?.cachedGitRepositoryBranch) + '';
|
||||
}
|
||||
|
||||
@@ -22,19 +22,17 @@ import { database } from '../common/database';
|
||||
import { initializeLogging } from '../common/log';
|
||||
import * as models from '../models';
|
||||
import { DEFAULT_ORGANIZATION_ID } from '../models/organization';
|
||||
import { DEFAULT_PROJECT_ID, isRemoteProject } from '../models/project';
|
||||
import { DEFAULT_PROJECT_ID } from '../models/project';
|
||||
import { initNewOAuthSession } from '../network/o-auth-2/get-token';
|
||||
import { init as initPlugins } from '../plugins';
|
||||
import { applyColorScheme } from '../plugins/misc';
|
||||
import { invariant } from '../utils/invariant';
|
||||
import { AppLoadingIndicator } from './components/app-loading-indicator';
|
||||
import { init as initStore, RootState } from './redux/modules';
|
||||
import { init as initStore } from './redux/modules';
|
||||
import {
|
||||
setActiveActivity,
|
||||
setActiveProject,
|
||||
setActiveWorkspace,
|
||||
} from './redux/modules/global';
|
||||
import { selectActiveProject } from './redux/selectors';
|
||||
import { ErrorRoute } from './routes/error';
|
||||
import Root from './routes/root';
|
||||
import { initializeSentry } from './sentry';
|
||||
@@ -500,7 +498,6 @@ function updateReduxNavigationState(store: Store, pathname: string) {
|
||||
store.dispatch(
|
||||
setActiveWorkspace(isActivityDebug?.params.workspaceId || '')
|
||||
);
|
||||
store.dispatch(setActiveActivity(ACTIVITY_DEBUG));
|
||||
} else if (isActivityDesign) {
|
||||
currentActivity = ACTIVITY_SPEC;
|
||||
store.dispatch(
|
||||
@@ -509,7 +506,6 @@ function updateReduxNavigationState(store: Store, pathname: string) {
|
||||
store.dispatch(
|
||||
setActiveWorkspace(isActivityDesign?.params.workspaceId || '')
|
||||
);
|
||||
store.dispatch(setActiveActivity(ACTIVITY_SPEC));
|
||||
} else if (isActivityTest) {
|
||||
currentActivity = ACTIVITY_UNIT_TEST;
|
||||
store.dispatch(
|
||||
@@ -518,13 +514,11 @@ function updateReduxNavigationState(store: Store, pathname: string) {
|
||||
store.dispatch(
|
||||
setActiveWorkspace(isActivityTest?.params.workspaceId || '')
|
||||
);
|
||||
store.dispatch(setActiveActivity(ACTIVITY_UNIT_TEST));
|
||||
} else {
|
||||
currentActivity = ACTIVITY_HOME;
|
||||
store.dispatch(
|
||||
setActiveProject(isActivityHome?.params.projectId || '')
|
||||
);
|
||||
store.dispatch(setActiveActivity(ACTIVITY_HOME));
|
||||
}
|
||||
|
||||
return currentActivity;
|
||||
@@ -549,46 +543,14 @@ async function renderApp() {
|
||||
// Synchronizes the Redux store with the router history
|
||||
// @HACK: This is temporary until we completely remove navigation through Redux
|
||||
const synchronizeRouterState = () => {
|
||||
let currentActivity = (store.getState() as RootState).global.activeActivity;
|
||||
let currentPathname = router.state.location.pathname;
|
||||
|
||||
currentActivity = updateReduxNavigationState(store, router.state.location.pathname);
|
||||
|
||||
updateReduxNavigationState(store, router.state.location.pathname);
|
||||
router.subscribe(({ location }) => {
|
||||
if (location.pathname !== currentPathname) {
|
||||
currentPathname = location.pathname;
|
||||
currentActivity = updateReduxNavigationState(store, location.pathname);
|
||||
updateReduxNavigationState(store, location.pathname);
|
||||
}
|
||||
});
|
||||
|
||||
store.subscribe(() => {
|
||||
const state = store.getState() as RootState;
|
||||
const activity = state.global.activeActivity;
|
||||
|
||||
const activeProject = selectActiveProject(state);
|
||||
const organizationId = activeProject && isRemoteProject(activeProject) ? activeProject._id : DEFAULT_ORGANIZATION_ID;
|
||||
|
||||
if (activity !== currentActivity) {
|
||||
currentActivity = activity;
|
||||
const activeProjectId = activeProject ? activeProject._id : DEFAULT_PROJECT_ID;
|
||||
if (activity === ACTIVITY_HOME) {
|
||||
router.navigate(`/organization/${organizationId}/project/${activeProject._id}`);
|
||||
} else if (activity === ACTIVITY_DEBUG) {
|
||||
router.navigate(
|
||||
`/organization/${organizationId}/project/${activeProjectId}/workspace/${state.global.activeWorkspaceId}/${ACTIVITY_DEBUG}`
|
||||
);
|
||||
} else if (activity === ACTIVITY_SPEC) {
|
||||
router.navigate(
|
||||
`/organization/${organizationId}/project/${activeProjectId}/workspace/${state.global.activeWorkspaceId}/${ACTIVITY_SPEC}`
|
||||
);
|
||||
} else if (activity === ACTIVITY_UNIT_TEST) {
|
||||
router.navigate(
|
||||
`/organization/${organizationId}/project/${state.global.activeProjectId}/workspace/${state.global.activeWorkspaceId}/test`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
synchronizeRouterState();
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
import { beforeEach, describe, expect, it } from '@jest/globals';
|
||||
|
||||
import { globalBeforeEach } from '../../../__jest__/before-each';
|
||||
import { reduxStateForTest } from '../../../__jest__/redux-state-for-test';
|
||||
import { ACTIVITY_DEBUG, ACTIVITY_HOME } from '../../../common/constants';
|
||||
import * as models from '../../../models';
|
||||
import { DEFAULT_PROJECT_ID, Project } from '../../../models/project';
|
||||
import { WorkspaceScopeKeys } from '../../../models/workspace';
|
||||
import { selectActiveApiSpec, selectActiveProject, selectActiveWorkspaceName, selectWorkspacesWithResolvedNameForActiveProject } from '../selectors';
|
||||
|
||||
describe('selectors', () => {
|
||||
beforeEach(globalBeforeEach);
|
||||
|
||||
describe('selectActiveProject', () => {
|
||||
it('should return the active project', async () => {
|
||||
// create two projects
|
||||
const projectA = await models.project.create();
|
||||
await models.project.create();
|
||||
|
||||
// set first as selected
|
||||
const state = await reduxStateForTest({ activeProjectId: projectA._id });
|
||||
|
||||
const project = selectActiveProject(state);
|
||||
expect(project).toStrictEqual(projectA);
|
||||
});
|
||||
|
||||
it('should return default project if active project not found', async () => {
|
||||
// create two projects
|
||||
await models.project.create();
|
||||
await models.project.create();
|
||||
|
||||
// set first as selected
|
||||
const state = await reduxStateForTest({ activeProjectId: 'some-other-project' });
|
||||
|
||||
const project = selectActiveProject(state);
|
||||
expect(project).toStrictEqual(expect.objectContaining<Partial<Project>>({ _id: DEFAULT_PROJECT_ID }));
|
||||
});
|
||||
|
||||
it('should return default project if no active project', async () => {
|
||||
// create two projects
|
||||
await models.project.create();
|
||||
await models.project.create();
|
||||
|
||||
// set nothing as active
|
||||
const state = await reduxStateForTest({ activeProjectId: undefined });
|
||||
|
||||
const project = selectActiveProject(state);
|
||||
expect(project).toStrictEqual(expect.objectContaining<Partial<Project>>({ _id: DEFAULT_PROJECT_ID }));
|
||||
});
|
||||
});
|
||||
|
||||
describe('selectActiveApiSpec', () => {
|
||||
it('will return undefined when there is not an active workspace', async () => {
|
||||
const state = await reduxStateForTest({
|
||||
activeWorkspaceId: null,
|
||||
});
|
||||
|
||||
expect(selectActiveApiSpec(state)).toBe(undefined);
|
||||
});
|
||||
|
||||
it('will return the apiSpec for a given workspace', async () => {
|
||||
const workspace = await models.workspace.create({
|
||||
name: 'workspace.name',
|
||||
scope: WorkspaceScopeKeys.design,
|
||||
});
|
||||
const spec = await models.apiSpec.updateOrCreateForParentId(
|
||||
workspace._id,
|
||||
{ fileName: 'apiSpec.fileName' },
|
||||
);
|
||||
|
||||
const state = await reduxStateForTest({
|
||||
activeActivity: ACTIVITY_DEBUG,
|
||||
activeWorkspaceId: workspace._id,
|
||||
});
|
||||
|
||||
expect(selectActiveApiSpec(state)).toEqual(spec);
|
||||
});
|
||||
});
|
||||
|
||||
describe('selectActiveWorkspaceName', () => {
|
||||
it('returns workspace name for collections', async () => {
|
||||
const workspace = await models.workspace.create({
|
||||
name: 'workspace.name',
|
||||
scope: WorkspaceScopeKeys.collection,
|
||||
});
|
||||
// even though this shouldn't technically happen, we want to make sure the selector still makes the right decision (and ignores the api spec for collections)
|
||||
await models.apiSpec.updateOrCreateForParentId(
|
||||
workspace._id,
|
||||
{ fileName: 'apiSpec.fileName' },
|
||||
);
|
||||
const state = await reduxStateForTest({
|
||||
activeActivity: ACTIVITY_DEBUG,
|
||||
activeWorkspaceId: workspace._id,
|
||||
});
|
||||
|
||||
expect(selectActiveWorkspaceName(state)).toBe('workspace.name');
|
||||
});
|
||||
|
||||
it('returns api spec name for design documents', async () => {
|
||||
const workspace = await models.workspace.create({
|
||||
name: 'workspace.name',
|
||||
scope: WorkspaceScopeKeys.design,
|
||||
});
|
||||
await models.apiSpec.updateOrCreateForParentId(
|
||||
workspace._id,
|
||||
{ fileName: 'apiSpec.fileName' },
|
||||
);
|
||||
const state = await reduxStateForTest({
|
||||
activeActivity: ACTIVITY_DEBUG,
|
||||
activeWorkspaceId: workspace._id,
|
||||
});
|
||||
|
||||
expect(selectActiveWorkspaceName(state)).toBe('apiSpec.fileName');
|
||||
});
|
||||
|
||||
it('returns undefined when there is not an active workspace', async () => {
|
||||
await models.workspace.create({
|
||||
name: 'workspace.name',
|
||||
scope: WorkspaceScopeKeys.collection,
|
||||
});
|
||||
const state = await reduxStateForTest({
|
||||
activeActivity: ACTIVITY_DEBUG,
|
||||
activeWorkspaceId: null,
|
||||
});
|
||||
|
||||
expect(selectActiveWorkspaceName(state)).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('selectWorkspacesWithResolvedNameForActiveProject', () => {
|
||||
it('returns the workspaces with resolved names for the active project', async () => {
|
||||
const newCollectionWorkspace = await models.workspace.create({
|
||||
name: 'collectionWorkspace.name',
|
||||
scope: WorkspaceScopeKeys.collection,
|
||||
});
|
||||
|
||||
const newDesignWorkspace = await models.workspace.create({
|
||||
name: 'designWorkspace.name',
|
||||
scope: WorkspaceScopeKeys.design,
|
||||
});
|
||||
|
||||
const newApiSpec = await models.apiSpec.getOrCreateForParentId(
|
||||
newDesignWorkspace._id
|
||||
);
|
||||
|
||||
// The database will update the api spec with the workspace name
|
||||
// That's why we need to explicitly update the ApiSpec name
|
||||
await models.apiSpec.update(newApiSpec, {
|
||||
fileName: 'apiSpec.name',
|
||||
});
|
||||
|
||||
const state = await reduxStateForTest({
|
||||
activeActivity: ACTIVITY_HOME,
|
||||
activeWorkspaceId: null,
|
||||
});
|
||||
|
||||
const workspaces = selectWorkspacesWithResolvedNameForActiveProject(state);
|
||||
|
||||
const designWorkspace = workspaces.find(
|
||||
workspace => workspace._id === newDesignWorkspace._id
|
||||
);
|
||||
|
||||
const collectionWorkspace = workspaces.find(
|
||||
workspace => workspace._id === newCollectionWorkspace._id
|
||||
);
|
||||
|
||||
expect(
|
||||
designWorkspace
|
||||
).toMatchObject(
|
||||
{
|
||||
_id: newDesignWorkspace._id,
|
||||
name: 'apiSpec.name',
|
||||
scope: WorkspaceScopeKeys.design,
|
||||
type: 'Workspace',
|
||||
},
|
||||
);
|
||||
|
||||
expect(
|
||||
collectionWorkspace
|
||||
).toMatchObject(
|
||||
{
|
||||
_id: newCollectionWorkspace._id,
|
||||
name: 'collectionWorkspace.name',
|
||||
scope: WorkspaceScopeKeys.collection,
|
||||
type: 'Workspace',
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,19 +1,10 @@
|
||||
import { beforeEach, describe, expect, it, jest } from '@jest/globals';
|
||||
|
||||
import { globalBeforeEach } from '../../../../__jest__/before-each';
|
||||
import {
|
||||
ACTIVITY_DEBUG,
|
||||
ACTIVITY_HOME,
|
||||
ACTIVITY_SPEC,
|
||||
ACTIVITY_UNIT_TEST,
|
||||
GlobalActivity,
|
||||
} from '../../../../common/constants';
|
||||
import {
|
||||
LOCALSTORAGE_PREFIX,
|
||||
SET_ACTIVE_ACTIVITY,
|
||||
SET_ACTIVE_PROJECT,
|
||||
SET_ACTIVE_WORKSPACE,
|
||||
setActiveActivity,
|
||||
setActiveProject,
|
||||
setActiveWorkspace,
|
||||
} from '../global';
|
||||
@@ -27,24 +18,6 @@ describe('global', () => {
|
||||
global.localStorage.clear();
|
||||
});
|
||||
|
||||
describe('setActiveActivity', () => {
|
||||
it.each([
|
||||
ACTIVITY_SPEC,
|
||||
ACTIVITY_DEBUG,
|
||||
ACTIVITY_UNIT_TEST,
|
||||
ACTIVITY_HOME,
|
||||
])('should update local storage and track event: %s', (activity: GlobalActivity) => {
|
||||
const expectedEvent = {
|
||||
type: SET_ACTIVE_ACTIVITY,
|
||||
activity,
|
||||
};
|
||||
expect(setActiveActivity(activity)).toStrictEqual(expectedEvent);
|
||||
expect(global.localStorage.getItem(`${LOCALSTORAGE_PREFIX}::activity`)).toBe(
|
||||
JSON.stringify(activity),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setActiveProject', () => {
|
||||
it('should update local storage', () => {
|
||||
const projectId = 'id';
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
import { beforeEach, describe, expect, it, jest } from '@jest/globals';
|
||||
import configureMockStore from 'redux-mock-store';
|
||||
import thunk from 'redux-thunk';
|
||||
|
||||
import { globalBeforeEach } from '../../../../__jest__/before-each';
|
||||
import { reduxStateForTest } from '../../../../__jest__/redux-state-for-test';
|
||||
import { ACTIVITY_DEBUG, ACTIVITY_SPEC } from '../../../../common/constants';
|
||||
import * as models from '../../../../models';
|
||||
import { SET_ACTIVE_ACTIVITY, SET_ACTIVE_PROJECT, SET_ACTIVE_WORKSPACE } from '../global';
|
||||
import { activateWorkspace } from '../workspace';
|
||||
|
||||
jest.mock('../../../components/modals');
|
||||
jest.mock('../../../../ui/analytics');
|
||||
|
||||
const middlewares = [thunk];
|
||||
const mockStore = configureMockStore(middlewares);
|
||||
|
||||
describe('workspace', () => {
|
||||
beforeEach(globalBeforeEach);
|
||||
describe('activateWorkspace', () => {
|
||||
it('should do nothing if workspace cannot be found', async () => {
|
||||
const store = mockStore(await reduxStateForTest({ activeProjectId: 'abc', activeWorkspaceId: 'def' }));
|
||||
|
||||
await store.dispatch(activateWorkspace({ workspaceId: 'DOES_NOT_EXIST' }));
|
||||
|
||||
expect(store.getActions()).toEqual([]);
|
||||
});
|
||||
|
||||
it('should activate project and workspace and activity using workspaceId', async () => {
|
||||
const project = await models.project.create();
|
||||
const workspace = await models.workspace.create({ scope: 'design', parentId: project._id });
|
||||
const store = mockStore(await reduxStateForTest({ activeProjectId: 'abc', activeWorkspaceId: 'def' }));
|
||||
|
||||
await store.dispatch(activateWorkspace({ workspaceId: workspace._id }));
|
||||
|
||||
expect(store.getActions()).toEqual([
|
||||
{
|
||||
type: SET_ACTIVE_PROJECT,
|
||||
projectId: project._id,
|
||||
},
|
||||
{
|
||||
type: SET_ACTIVE_WORKSPACE,
|
||||
workspaceId: workspace._id,
|
||||
},
|
||||
{
|
||||
type: SET_ACTIVE_ACTIVITY,
|
||||
activity: ACTIVITY_SPEC,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should activate project and workspace and activity from home', async () => {
|
||||
const project = await models.project.create();
|
||||
const workspace = await models.workspace.create({ scope: 'design', parentId: project._id });
|
||||
const store = mockStore(await reduxStateForTest({ activeProjectId: 'abc', activeWorkspaceId: 'def' }));
|
||||
|
||||
await store.dispatch(activateWorkspace({ workspace }));
|
||||
|
||||
expect(store.getActions()).toEqual([
|
||||
{
|
||||
type: SET_ACTIVE_PROJECT,
|
||||
projectId: project._id,
|
||||
},
|
||||
{
|
||||
type: SET_ACTIVE_WORKSPACE,
|
||||
workspaceId: workspace._id,
|
||||
},
|
||||
{
|
||||
type: SET_ACTIVE_ACTIVITY,
|
||||
activity: ACTIVITY_SPEC,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should switch to the default design activity', async () => {
|
||||
const project = await models.project.create();
|
||||
const workspace = await models.workspace.create({ scope: 'design', parentId: project._id });
|
||||
const store = mockStore(await reduxStateForTest({ activeProjectId: project._id, activeWorkspaceId: workspace._id }));
|
||||
|
||||
await store.dispatch(activateWorkspace({ workspace }));
|
||||
|
||||
expect(store.getActions()).toEqual([
|
||||
{
|
||||
type: SET_ACTIVE_PROJECT,
|
||||
projectId: project._id,
|
||||
},
|
||||
{
|
||||
type: SET_ACTIVE_WORKSPACE,
|
||||
workspaceId: workspace._id,
|
||||
},
|
||||
{
|
||||
type: SET_ACTIVE_ACTIVITY,
|
||||
activity: ACTIVITY_SPEC,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it.each([ACTIVITY_DEBUG])('should not switch activity if already in a supported collection activity: %s', async activeActivity => {
|
||||
const project = await models.project.create();
|
||||
const workspace = await models.workspace.create({ scope: 'design', parentId: project._id });
|
||||
const store = mockStore(await reduxStateForTest({ activeProjectId: project._id, activeWorkspaceId: workspace._id, activeActivity }));
|
||||
|
||||
await store.dispatch(activateWorkspace({ workspace }));
|
||||
|
||||
expect(store.getActions()).toEqual([
|
||||
{
|
||||
type: SET_ACTIVE_PROJECT,
|
||||
projectId: project._id,
|
||||
},
|
||||
{
|
||||
type: SET_ACTIVE_WORKSPACE,
|
||||
workspaceId: workspace._id,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('should switch to the default collection activity', async () => {
|
||||
const project = await models.project.create();
|
||||
const workspace = await models.workspace.create({ scope: 'collection', parentId: project._id });
|
||||
const store = mockStore(await reduxStateForTest({ activeProjectId: project._id, activeWorkspaceId: workspace._id }));
|
||||
|
||||
await store.dispatch(activateWorkspace({ workspace }));
|
||||
|
||||
expect(store.getActions()).toEqual([
|
||||
{
|
||||
type: SET_ACTIVE_PROJECT,
|
||||
projectId: project._id,
|
||||
},
|
||||
{
|
||||
type: SET_ACTIVE_WORKSPACE,
|
||||
workspaceId: workspace._id,
|
||||
},
|
||||
{
|
||||
type: SET_ACTIVE_ACTIVITY,
|
||||
activity: ACTIVITY_DEBUG,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,10 +1,6 @@
|
||||
import { combineReducers } from 'redux';
|
||||
|
||||
import type { DashboardSortOrder, GlobalActivity } from '../../../common/constants';
|
||||
import {
|
||||
ACTIVITY_HOME,
|
||||
isValidActivity,
|
||||
} from '../../../common/constants';
|
||||
import type { DashboardSortOrder } from '../../../common/constants';
|
||||
import { DEFAULT_PROJECT_ID } from '../../../models/project';
|
||||
|
||||
export const LOCALSTORAGE_PREFIX = 'insomnia::meta';
|
||||
@@ -17,15 +13,6 @@ export const SET_ACTIVE_ACTIVITY = 'global/activate-activity';
|
||||
// ~~~~~~~~ //
|
||||
// REDUCERS //
|
||||
// ~~~~~~~~ //
|
||||
function activeActivityReducer(state: string | null = null, action: any) {
|
||||
switch (action.type) {
|
||||
case SET_ACTIVE_ACTIVITY:
|
||||
return action.activity;
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
function activeProjectReducer(state: string = DEFAULT_PROJECT_ID, action: any) {
|
||||
switch (action.type) {
|
||||
@@ -71,7 +58,6 @@ export interface GlobalState {
|
||||
activeProjectId: string;
|
||||
dashboardSortOrder: DashboardSortOrder;
|
||||
activeWorkspaceId: string | null;
|
||||
activeActivity: GlobalActivity | null;
|
||||
isLoggedIn: boolean;
|
||||
}
|
||||
|
||||
@@ -79,7 +65,6 @@ export const reducer = combineReducers<GlobalState>({
|
||||
dashboardSortOrder: dashboardSortOrderReducer,
|
||||
activeProjectId: activeProjectReducer,
|
||||
activeWorkspaceId: activeWorkspaceReducer,
|
||||
activeActivity: activeActivityReducer,
|
||||
isLoggedIn: loginStateChangeReducer,
|
||||
});
|
||||
|
||||
@@ -91,19 +76,6 @@ export const loginStateChange = (loggedIn: boolean) => ({
|
||||
loggedIn,
|
||||
});
|
||||
|
||||
/*
|
||||
Go to an explicit activity
|
||||
*/
|
||||
export const setActiveActivity = (activity: GlobalActivity) => {
|
||||
activity = isValidActivity(activity) ? activity : ACTIVITY_HOME;
|
||||
window.localStorage.setItem(`${LOCALSTORAGE_PREFIX}::activity`, JSON.stringify(activity));
|
||||
window.main.trackPageView({ name: activity });
|
||||
return {
|
||||
type: SET_ACTIVE_ACTIVITY,
|
||||
activity,
|
||||
};
|
||||
};
|
||||
|
||||
export const setActiveProject = (projectId: string) => {
|
||||
const key = `${LOCALSTORAGE_PREFIX}::activeProjectId`;
|
||||
window.localStorage.setItem(key, JSON.stringify(projectId));
|
||||
@@ -113,17 +85,6 @@ export const setActiveProject = (projectId: string) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const setDashboardSortOrder = (sortOrder: DashboardSortOrder) => {
|
||||
const key = `${LOCALSTORAGE_PREFIX}::dashboard-sort-order`;
|
||||
window.localStorage.setItem(key, JSON.stringify(sortOrder));
|
||||
return {
|
||||
type: SET_DASHBOARD_SORT_ORDER,
|
||||
payload: {
|
||||
sortOrder,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const setActiveWorkspace = (workspaceId: string | null) => {
|
||||
const key = `${LOCALSTORAGE_PREFIX}::activeWorkspaceId`;
|
||||
window.localStorage.setItem(key, JSON.stringify(workspaceId));
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import { Dispatch } from 'redux';
|
||||
import type { RequireExactlyOne } from 'type-fest';
|
||||
|
||||
import { ACTIVITY_DEBUG, ACTIVITY_SPEC, GlobalActivity, isCollectionActivity, isDesignActivity } from '../../../common/constants';
|
||||
import * as models from '../../../models';
|
||||
import { isCollection, isDesign, Workspace } from '../../../models/workspace';
|
||||
import { selectActiveActivity, selectWorkspaces } from '../selectors';
|
||||
import { RootState } from '.';
|
||||
import { setActiveActivity, setActiveProject, setActiveWorkspace } from './global';
|
||||
|
||||
export const activateWorkspace = ({ workspace, workspaceId }: RequireExactlyOne<{workspace: Workspace; workspaceId: string}>) => {
|
||||
return async (dispatch: Dispatch, getState: () => RootState) => {
|
||||
// If we have no workspace but we do have an id, search for it
|
||||
if (!workspace && workspaceId) {
|
||||
workspace = selectWorkspaces(getState()).find(({ _id }) => _id === workspaceId);
|
||||
}
|
||||
|
||||
// If we still have no workspace, exit
|
||||
if (!workspace) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeActivity = selectActiveActivity(getState()) || undefined;
|
||||
|
||||
// Activate the correct project
|
||||
const nextProjectId = workspace.parentId;
|
||||
dispatch(setActiveProject(nextProjectId));
|
||||
|
||||
// Activate the correct workspace
|
||||
const nextWorkspaceId = workspace._id;
|
||||
dispatch(setActiveWorkspace(nextWorkspaceId));
|
||||
|
||||
// Activate the correct activity
|
||||
if (isCollection(workspace) && isCollectionActivity(activeActivity)) {
|
||||
// we are in a collection, and our active activity is a collection activity
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDesign(workspace) && isDesignActivity(activeActivity)) {
|
||||
// we are in a design document, and our active activity is a design activity
|
||||
return;
|
||||
}
|
||||
|
||||
const { activeActivity: cachedActivity } = await models.workspaceMeta.getOrCreateByParentId(workspace._id);
|
||||
const nextActivity = cachedActivity as GlobalActivity || (isDesign(workspace) ? ACTIVITY_SPEC : ACTIVITY_DEBUG);
|
||||
dispatch(setActiveActivity(nextActivity));
|
||||
|
||||
// TODO: dispatch one action to activate the project, workspace and activity in one go to avoid jumps in the UI
|
||||
};
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createSelector } from 'reselect';
|
||||
import type { ValueOf } from 'type-fest';
|
||||
|
||||
import { isWorkspaceActivity, PREVIEW_MODE_SOURCE } from '../../common/constants';
|
||||
import { PREVIEW_MODE_SOURCE } from '../../common/constants';
|
||||
import * as models from '../../models';
|
||||
import { BaseModel } from '../../models';
|
||||
import { GrpcRequest, isGrpcRequest } from '../../models/grpc-request';
|
||||
@@ -11,10 +11,8 @@ import { DEFAULT_PROJECT_ID, isRemoteProject } from '../../models/project';
|
||||
import { isRequest, Request } from '../../models/request';
|
||||
import { isRequestGroup, RequestGroup } from '../../models/request-group';
|
||||
import { type Response } from '../../models/response';
|
||||
import { UnitTestResult } from '../../models/unit-test-result';
|
||||
import { isWebSocketRequest, WebSocketRequest } from '../../models/websocket-request';
|
||||
import { type WebSocketResponse } from '../../models/websocket-response';
|
||||
import { isCollection } from '../../models/workspace';
|
||||
import { RootState } from './modules';
|
||||
|
||||
type EntitiesLists = {
|
||||
@@ -24,43 +22,29 @@ type EntitiesLists = {
|
||||
// ~~~~~~~~~ //
|
||||
// Selectors //
|
||||
// ~~~~~~~~~ //
|
||||
export const selectEntities = createSelector(
|
||||
(state: RootState) => state.entities,
|
||||
entities => entities,
|
||||
);
|
||||
|
||||
export const selectGlobal = createSelector(
|
||||
(state: RootState) => state.global,
|
||||
global => global,
|
||||
);
|
||||
|
||||
export const selectEntitiesLists = createSelector(
|
||||
selectEntities,
|
||||
(state: RootState) => state.entities,
|
||||
entities => {
|
||||
// transforms entities object from object keyed on id to array of entities containing id
|
||||
const entitiesLists: any = {};
|
||||
|
||||
for (const k of Object.keys(entities)) {
|
||||
const entityMap = (entities as any)[k];
|
||||
entitiesLists[k] = Object.keys(entityMap).map(id => entityMap[id]);
|
||||
for (const [k, v] of Object.entries(entities)) {
|
||||
entitiesLists[k] = Object.keys(v).map(id => v[id]);
|
||||
}
|
||||
|
||||
return entitiesLists as EntitiesLists;
|
||||
},
|
||||
);
|
||||
|
||||
export const selectEntitiesChildrenMap = createSelector(selectEntitiesLists, entities => {
|
||||
const parentLookupMap: any = {};
|
||||
|
||||
for (const key of Object.keys(entities)) {
|
||||
for (const entity of (entities as any)[key]) {
|
||||
if (!entity.parentId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (parentLookupMap[entity.parentId]) {
|
||||
parentLookupMap[entity.parentId].push(entity);
|
||||
} else {
|
||||
parentLookupMap[entity.parentId] = [entity];
|
||||
// group entities by parent
|
||||
for (const value of Object.values(entities)) {
|
||||
for (const entity of value) {
|
||||
if (entity.parentId) {
|
||||
if (parentLookupMap[entity.parentId]) {
|
||||
parentLookupMap[entity.parentId].push(entity);
|
||||
} else {
|
||||
parentLookupMap[entity.parentId] = [entity];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,50 +80,21 @@ export const selectRemoteProjects = createSelector(
|
||||
projects => projects.filter(isRemoteProject),
|
||||
);
|
||||
|
||||
export const selectActiveProject = createSelector(
|
||||
selectEntities,
|
||||
(state: RootState) => state.global.activeProjectId,
|
||||
(entities, activeProjectId) => {
|
||||
return entities.projects[activeProjectId] || entities.projects[DEFAULT_PROJECT_ID];
|
||||
},
|
||||
);
|
||||
|
||||
export const selectDashboardSortOrder = createSelector(
|
||||
selectGlobal,
|
||||
global => global.dashboardSortOrder
|
||||
);
|
||||
|
||||
export const selectWorkspaces = createSelector(
|
||||
selectEntitiesLists,
|
||||
entities => entities.workspaces,
|
||||
);
|
||||
|
||||
export const selectWorkspacesForActiveProject = createSelector(
|
||||
selectWorkspaces,
|
||||
selectActiveProject,
|
||||
(workspaces, activeProject) => workspaces.filter(workspace => workspace.parentId === activeProject._id),
|
||||
selectEntitiesLists,
|
||||
(state: RootState) => state.global.activeProjectId,
|
||||
(entities, activeProjectId) => entities.workspaces.filter(workspace => workspace.parentId === (activeProjectId || DEFAULT_PROJECT_ID)),
|
||||
);
|
||||
|
||||
export const selectActiveWorkspace = createSelector(
|
||||
selectWorkspacesForActiveProject,
|
||||
(state: RootState) => state.global.activeWorkspaceId,
|
||||
(state: RootState) => state.global.activeActivity,
|
||||
(workspaces, activeWorkspaceId, activeActivity) => {
|
||||
// Only return an active workspace if we're in an activity
|
||||
if (activeActivity && isWorkspaceActivity(activeActivity)) {
|
||||
const workspace = workspaces.find(workspace => workspace._id === activeWorkspaceId);
|
||||
return workspace;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
(workspaces, activeWorkspaceId) => {
|
||||
const workspace = workspaces.find(workspace => workspace._id === activeWorkspaceId);
|
||||
return workspace;
|
||||
},
|
||||
);
|
||||
|
||||
export const selectWorkspaceMetas = createSelector(
|
||||
selectEntitiesLists,
|
||||
entities => entities.workspaceMetas,
|
||||
);
|
||||
|
||||
export const selectActiveWorkspaceMeta = createSelector(
|
||||
selectActiveWorkspace,
|
||||
selectEntitiesLists,
|
||||
@@ -154,57 +109,6 @@ export const selectApiSpecs = createSelector(
|
||||
entities => entities.apiSpecs,
|
||||
);
|
||||
|
||||
export const selectWorkspacesWithResolvedNameForActiveProject = createSelector(
|
||||
selectWorkspacesForActiveProject,
|
||||
selectApiSpecs,
|
||||
(workspaces, apiSpecs) => {
|
||||
return workspaces.map(workspace => {
|
||||
if (isCollection(workspace)) {
|
||||
return workspace;
|
||||
}
|
||||
|
||||
const apiSpec = apiSpecs.find(
|
||||
apiSpec => apiSpec.parentId === workspace._id
|
||||
);
|
||||
|
||||
return {
|
||||
...workspace,
|
||||
name: apiSpec?.fileName || workspace.name,
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
export const selectActiveApiSpec = createSelector(
|
||||
selectApiSpecs,
|
||||
selectActiveWorkspace,
|
||||
(apiSpecs, activeWorkspace) => {
|
||||
if (!activeWorkspace) {
|
||||
// There should never be an active api spec without an active workspace
|
||||
return undefined;
|
||||
}
|
||||
return apiSpecs.find(apiSpec => apiSpec.parentId === activeWorkspace._id);
|
||||
}
|
||||
);
|
||||
|
||||
export const selectActiveWorkspaceName = createSelector(
|
||||
selectActiveWorkspace,
|
||||
selectActiveApiSpec,
|
||||
(activeWorkspace, activeApiSpec) => {
|
||||
if (!activeWorkspace) {
|
||||
// see above, but since the selectActiveWorkspace selector really can return undefined, we need to handle it here.
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return isCollection(activeWorkspace) ? activeWorkspace.name : activeApiSpec?.fileName;
|
||||
}
|
||||
);
|
||||
|
||||
export const selectEnvironments = createSelector(
|
||||
selectEntitiesLists,
|
||||
entities => entities.environments,
|
||||
);
|
||||
|
||||
export const selectGitRepositories = createSelector(
|
||||
selectEntitiesLists,
|
||||
entities => entities.gitRepositories,
|
||||
@@ -227,22 +131,16 @@ export const selectRequests = createSelector(
|
||||
|
||||
export const selectActiveEnvironment = createSelector(
|
||||
selectActiveWorkspaceMeta,
|
||||
selectEnvironments,
|
||||
(meta, environments) => {
|
||||
selectEntitiesLists,
|
||||
(meta, entities) => {
|
||||
if (!meta) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return environments.find(environment => environment._id === meta.activeEnvironmentId) || null;
|
||||
return entities.environments.find(environment => environment._id === meta.activeEnvironmentId) || null;
|
||||
},
|
||||
);
|
||||
|
||||
export const selectActiveWorkspaceClientCertificates = createSelector(
|
||||
selectEntitiesLists,
|
||||
selectActiveWorkspace,
|
||||
(entities, activeWorkspace) => entities.clientCertificates.filter(c => c.parentId === activeWorkspace?._id),
|
||||
);
|
||||
|
||||
export const selectActiveGitRepository = createSelector(
|
||||
selectEntitiesLists,
|
||||
selectActiveWorkspaceMeta,
|
||||
@@ -333,7 +231,7 @@ export const selectWorkspaceRequestsAndRequestGroups = createSelector(
|
||||
);
|
||||
|
||||
export const selectActiveRequest = createSelector(
|
||||
selectEntities,
|
||||
(state: RootState) => state.entities,
|
||||
selectActiveWorkspaceMeta,
|
||||
(entities, workspaceMeta) => {
|
||||
const id = workspaceMeta?.activeRequestId || 'n/a';
|
||||
@@ -354,25 +252,6 @@ export const selectActiveRequest = createSelector(
|
||||
},
|
||||
);
|
||||
|
||||
export const selectActiveCookieJar = createSelector(
|
||||
selectEntitiesLists,
|
||||
selectActiveWorkspace,
|
||||
(entities, workspace) => {
|
||||
const cookieJar = entities.cookieJars.find(cj => cj.parentId === workspace?._id);
|
||||
return cookieJar || null;
|
||||
},
|
||||
);
|
||||
|
||||
export const selectUnseenWorkspaces = createSelector(
|
||||
selectEntitiesLists,
|
||||
entities => {
|
||||
const { workspaces, workspaceMetas } = entities;
|
||||
return workspaces.filter(workspace => {
|
||||
const meta = workspaceMetas.find(m => m.parentId === workspace._id);
|
||||
return !!(meta && !meta.hasSeen);
|
||||
});
|
||||
});
|
||||
|
||||
export const selectActiveRequestMeta = createSelector(
|
||||
selectActiveRequest,
|
||||
selectEntitiesLists,
|
||||
@@ -449,84 +328,7 @@ export const selectActiveResponse = createSelector(
|
||||
},
|
||||
);
|
||||
|
||||
export const selectActiveUnitTestResult = createSelector(
|
||||
selectEntitiesLists,
|
||||
selectActiveWorkspace,
|
||||
(entities, activeWorkspace) => {
|
||||
if (!activeWorkspace) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let recentResult: UnitTestResult | null = null;
|
||||
|
||||
for (const r of entities.unitTestResults) {
|
||||
if (r.parentId !== activeWorkspace._id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!recentResult) {
|
||||
recentResult = r;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (r.created > recentResult.created) {
|
||||
recentResult = r;
|
||||
}
|
||||
}
|
||||
|
||||
return recentResult;
|
||||
},
|
||||
);
|
||||
|
||||
export const selectActiveUnitTestSuite = createSelector(
|
||||
selectEntitiesLists,
|
||||
selectActiveWorkspaceMeta,
|
||||
(entities, activeWorkspaceMeta) => {
|
||||
if (!activeWorkspaceMeta) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const id = activeWorkspaceMeta.activeUnitTestSuiteId;
|
||||
return entities.unitTestSuites.find(s => s._id === id) || null;
|
||||
},
|
||||
);
|
||||
|
||||
export const selectActiveUnitTests = createSelector(
|
||||
selectEntitiesLists,
|
||||
selectActiveUnitTestSuite,
|
||||
(entities, activeUnitTestSuite) => {
|
||||
if (!activeUnitTestSuite) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return entities.unitTests.filter(s => s.parentId === activeUnitTestSuite._id);
|
||||
},
|
||||
);
|
||||
|
||||
export const selectActiveProjectName = createSelector(
|
||||
selectActiveProject,
|
||||
activeProject => activeProject.name,
|
||||
);
|
||||
|
||||
export const selectActiveUnitTestSuites = createSelector(
|
||||
selectEntitiesLists,
|
||||
selectActiveWorkspace,
|
||||
(entities, activeWorkspace) => {
|
||||
return entities.unitTestSuites.filter(s => s.parentId === activeWorkspace?._id);
|
||||
},
|
||||
);
|
||||
|
||||
export const selectSyncItems = createSelector(
|
||||
selectActiveWorkspaceEntities,
|
||||
getStatusCandidates,
|
||||
);
|
||||
|
||||
export const selectIsLoggedIn = createSelector(
|
||||
selectGlobal,
|
||||
global => global.isLoggedIn,
|
||||
);
|
||||
|
||||
export const selectActiveActivity = createSelector(
|
||||
selectGlobal,
|
||||
global => global.activeActivity,
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import { DEFAULT_PANE_HEIGHT, DEFAULT_PANE_WIDTH, DEFAULT_SIDEBAR_WIDTH } from '../../common/constants';
|
||||
import { fuzzyMatchAll } from '../../common/misc';
|
||||
import type { BaseModel } from '../../models';
|
||||
import { GrpcRequest, isGrpcRequest } from '../../models/grpc-request';
|
||||
@@ -43,27 +42,10 @@ export interface SidebarChildren {
|
||||
all: Child[];
|
||||
pinned: Child[];
|
||||
}
|
||||
|
||||
export const selectSidebarWidth = createSelector(
|
||||
selectActiveWorkspaceMeta,
|
||||
activeWorkspaceMeta => activeWorkspaceMeta?.sidebarWidth || DEFAULT_SIDEBAR_WIDTH,
|
||||
);
|
||||
|
||||
export const selectPaneWidth = createSelector(
|
||||
selectActiveWorkspaceMeta,
|
||||
activeWorkspaceMeta => activeWorkspaceMeta?.paneWidth || DEFAULT_PANE_WIDTH,
|
||||
);
|
||||
|
||||
export const selectPaneHeight = createSelector(
|
||||
selectActiveWorkspaceMeta,
|
||||
activeWorkspaceMeta => activeWorkspaceMeta?.paneHeight || DEFAULT_PANE_HEIGHT,
|
||||
);
|
||||
|
||||
export const selectSidebarFilter = createSelector(
|
||||
selectActiveWorkspaceMeta,
|
||||
activeWorkspaceMeta => activeWorkspaceMeta ? activeWorkspaceMeta.sidebarFilter : '',
|
||||
);
|
||||
|
||||
export const selectSidebarChildren = createSelector(
|
||||
selectCollapsedRequestGroups,
|
||||
selectPinnedRequests,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ServiceError, StatusObject } from '@grpc/grpc-js';
|
||||
import React, { FC, Fragment, useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useRouteLoaderData } from 'react-router-dom';
|
||||
|
||||
import { ChangeBufferEvent, database as db } from '../../common/database';
|
||||
import { generateId } from '../../common/misc';
|
||||
@@ -39,13 +40,10 @@ import { WebSocketRequestPane } from '../components/websockets/websocket-request
|
||||
import { updateRequestMetaByParentId } from '../hooks/create-request';
|
||||
import { createRequestGroup } from '../hooks/create-request-group';
|
||||
import {
|
||||
selectActiveEnvironment,
|
||||
selectActiveRequest,
|
||||
selectActiveWorkspace,
|
||||
selectActiveWorkspaceMeta,
|
||||
selectSettings,
|
||||
} from '../redux/selectors';
|
||||
import { selectSidebarFilter } from '../redux/sidebar-selectors';
|
||||
import { WorkspaceLoaderData } from './workspace';
|
||||
export interface GrpcMessage {
|
||||
id: string;
|
||||
text: string;
|
||||
@@ -74,9 +72,12 @@ const INITIAL_GRPC_REQUEST_STATE = {
|
||||
};
|
||||
|
||||
export const Debug: FC = () => {
|
||||
const activeEnvironment = useSelector(selectActiveEnvironment);
|
||||
const {
|
||||
activeWorkspace,
|
||||
activeWorkspaceMeta,
|
||||
activeEnvironment,
|
||||
} = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
|
||||
const activeRequest = useSelector(selectActiveRequest);
|
||||
const activeWorkspace = useSelector(selectActiveWorkspace);
|
||||
const [grpcStates, setGrpcStates] = useState<GrpcRequestState[]>([]);
|
||||
useEffect(() => {
|
||||
db.onChange(async (changes: ChangeBufferEvent[]) => {
|
||||
@@ -90,18 +91,14 @@ export const Debug: FC = () => {
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
const fn = async () => {
|
||||
if (activeWorkspace) {
|
||||
const children = await db.withDescendants(activeWorkspace);
|
||||
const grpcRequests = children.filter(d => isGrpcRequest(d));
|
||||
setGrpcStates(grpcRequests.map(r => ({ requestId: r._id, ...INITIAL_GRPC_REQUEST_STATE })));
|
||||
}
|
||||
const children = await db.withDescendants(activeWorkspace);
|
||||
const grpcRequests = children.filter(d => isGrpcRequest(d));
|
||||
setGrpcStates(grpcRequests.map(r => ({ requestId: r._id, ...INITIAL_GRPC_REQUEST_STATE })));
|
||||
};
|
||||
fn();
|
||||
}, [activeWorkspace]);
|
||||
|
||||
const settings = useSelector(selectSettings);
|
||||
const sidebarFilter = useSelector(selectSidebarFilter);
|
||||
const activeWorkspaceMeta = useSelector(selectActiveWorkspaceMeta);
|
||||
const [runningRequests, setRunningRequests] = useState({});
|
||||
const setLoading = (isLoading: boolean) => {
|
||||
invariant(activeRequest, 'No active request');
|
||||
@@ -192,27 +189,21 @@ export const Debug: FC = () => {
|
||||
},
|
||||
request_createHTTP:
|
||||
async () => {
|
||||
if (activeWorkspace) {
|
||||
const parentId = activeRequest ? activeRequest.parentId : activeWorkspace._id;
|
||||
const request = await models.request.create({
|
||||
parentId,
|
||||
name: 'New Request',
|
||||
});
|
||||
if (activeWorkspaceMeta) {
|
||||
await models.workspaceMeta.update(activeWorkspaceMeta, { activeRequestId: request._id });
|
||||
}
|
||||
await updateRequestMetaByParentId(request._id, {
|
||||
lastActive: Date.now(),
|
||||
});
|
||||
models.stats.incrementCreatedRequests();
|
||||
window.main.trackSegmentEvent({ event: SegmentEvent.requestCreate, properties: { requestType: 'HTTP' } });
|
||||
}
|
||||
const parentId = activeRequest ? activeRequest.parentId : activeWorkspace._id;
|
||||
const request = await models.request.create({
|
||||
parentId,
|
||||
name: 'New Request',
|
||||
});
|
||||
await models.workspaceMeta.update(activeWorkspaceMeta, { activeRequestId: request._id });
|
||||
await updateRequestMetaByParentId(request._id, {
|
||||
lastActive: Date.now(),
|
||||
});
|
||||
models.stats.incrementCreatedRequests();
|
||||
window.main.trackSegmentEvent({ event: SegmentEvent.requestCreate, properties: { requestType: 'HTTP' } });
|
||||
},
|
||||
request_showCreateFolder:
|
||||
() => {
|
||||
if (activeWorkspace) {
|
||||
createRequestGroup(activeRequest ? activeRequest.parentId : activeWorkspace._id);
|
||||
}
|
||||
createRequestGroup(activeRequest ? activeRequest.parentId : activeWorkspace._id);
|
||||
},
|
||||
request_showRecent:
|
||||
() => showModal(RequestSwitcherModal, {
|
||||
@@ -263,11 +254,11 @@ export const Debug: FC = () => {
|
||||
|
||||
<SidebarFilter
|
||||
key={`${activeWorkspace._id}::filter`}
|
||||
filter={sidebarFilter || ''}
|
||||
filter={activeWorkspaceMeta.sidebarFilter || ''}
|
||||
/>
|
||||
|
||||
<SidebarChildren
|
||||
filter={sidebarFilter || ''}
|
||||
filter={activeWorkspaceMeta.sidebarFilter || ''}
|
||||
/>
|
||||
<WorkspaceSyncDropdown />
|
||||
</Fragment>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { FC, Fragment } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useRouteLoaderData } from 'react-router-dom';
|
||||
|
||||
import { ErrorBoundary } from '../components/error-boundary';
|
||||
import { registerModal } from '../components/modals';
|
||||
@@ -34,17 +34,11 @@ import { WorkspaceEnvironmentsEditModal } from '../components/modals/workspace-e
|
||||
import { WorkspaceSettingsModal } from '../components/modals/workspace-settings-modal';
|
||||
import { WrapperModal } from '../components/modals/wrapper-modal';
|
||||
import { useVCS } from '../hooks/use-vcs';
|
||||
import {
|
||||
selectActiveCookieJar,
|
||||
selectActiveEnvironment,
|
||||
selectActiveWorkspace,
|
||||
} from '../redux/selectors';
|
||||
import { WorkspaceLoaderData } from './workspace';
|
||||
|
||||
const Modals: FC = () => {
|
||||
const activeCookieJar = useSelector(selectActiveCookieJar);
|
||||
const activeWorkspace = useSelector(selectActiveWorkspace);
|
||||
const activeEnvironment = useSelector(selectActiveEnvironment);
|
||||
|
||||
const workspaceData = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData | undefined;
|
||||
const { activeWorkspace, activeEnvironment, activeCookieJar } = workspaceData || {};
|
||||
const vcs = useVCS({
|
||||
workspaceId: activeWorkspace?._id,
|
||||
});
|
||||
@@ -113,6 +107,10 @@ const Modals: FC = () => {
|
||||
registerModal(instance, 'WorkspaceSettingsModal')
|
||||
}
|
||||
/>
|
||||
|
||||
<RequestSwitcherModal
|
||||
ref={instance => registerModal(instance, 'RequestSwitcherModal')}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
@@ -128,10 +126,6 @@ const Modals: FC = () => {
|
||||
ref={instance => registerModal(instance, 'ResponseDebugModal')}
|
||||
/>
|
||||
|
||||
<RequestSwitcherModal
|
||||
ref={instance => registerModal(instance, 'RequestSwitcherModal')}
|
||||
/>
|
||||
|
||||
<EnvironmentEditModal
|
||||
ref={instance => registerModal(instance, 'EnvironmentEditModal')}
|
||||
/>
|
||||
|
||||
@@ -2,6 +2,9 @@ import React from 'react';
|
||||
import { LoaderFunction, Outlet, useLoaderData } from 'react-router-dom';
|
||||
|
||||
import * as models from '../../models';
|
||||
import { ApiSpec } from '../../models/api-spec';
|
||||
import { ClientCertificate } from '../../models/client-certificate';
|
||||
import { CookieJar } from '../../models/cookie-jar';
|
||||
import { Environment } from '../../models/environment';
|
||||
import { GitRepository } from '../../models/git-repository';
|
||||
import { Project } from '../../models/project';
|
||||
@@ -10,12 +13,15 @@ import { WorkspaceMeta } from '../../models/workspace-meta';
|
||||
import { invariant } from '../../utils/invariant';
|
||||
export interface WorkspaceLoaderData {
|
||||
activeWorkspace: Workspace;
|
||||
activeWorkspaceMeta?: WorkspaceMeta;
|
||||
activeWorkspaceMeta: WorkspaceMeta;
|
||||
activeProject: Project;
|
||||
gitRepository: GitRepository | null;
|
||||
activeEnvironment: Environment;
|
||||
activeCookieJar: CookieJar;
|
||||
baseEnvironment: Environment;
|
||||
subEnvironments: Environment[];
|
||||
activeApiSpec: ApiSpec | null;
|
||||
clientCertificates: ClientCertificate[];
|
||||
}
|
||||
|
||||
export const workspaceLoader: LoaderFunction = async ({
|
||||
@@ -38,6 +44,7 @@ export const workspaceLoader: LoaderFunction = async ({
|
||||
const activeWorkspaceMeta = await models.workspaceMeta.getOrCreateByParentId(
|
||||
workspaceId,
|
||||
);
|
||||
invariant(activeWorkspaceMeta, 'Workspace meta not found');
|
||||
const gitRepository = await models.gitRepository.getById(
|
||||
activeWorkspaceMeta.gitRepositoryId || '',
|
||||
);
|
||||
@@ -50,20 +57,28 @@ export const workspaceLoader: LoaderFunction = async ({
|
||||
|
||||
const activeEnvironment = subEnvironments.find(({ _id }) => activeWorkspaceMeta.activeEnvironmentId === _id) || baseEnvironment;
|
||||
|
||||
const activeCookieJar = await models.cookieJar.getOrCreateForParentId(workspaceId);
|
||||
invariant(activeCookieJar, 'Cookie jar not found');
|
||||
|
||||
const activeApiSpec = await models.apiSpec.getByParentId(workspaceId);
|
||||
const clientCertificates = await models.clientCertificate.findByParentId(workspaceId);
|
||||
return {
|
||||
activeWorkspace,
|
||||
activeProject,
|
||||
gitRepository,
|
||||
activeWorkspaceMeta,
|
||||
activeCookieJar,
|
||||
activeEnvironment,
|
||||
subEnvironments,
|
||||
baseEnvironment,
|
||||
activeApiSpec,
|
||||
clientCertificates,
|
||||
};
|
||||
};
|
||||
|
||||
const WorkspaceRoute = () => {
|
||||
const workspaceData = useLoaderData() as WorkspaceLoaderData;
|
||||
const branch = workspaceData.activeWorkspaceMeta?.cachedGitRepositoryBranch;
|
||||
const branch = workspaceData.activeWorkspaceMeta.cachedGitRepositoryBranch;
|
||||
return <Outlet key={branch} />;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user