unifies getting of workspace name in displayment (#4060)

This commit is contained in:
Dimitri Mitropoulos 2021-10-07 08:38:11 -04:00 committed by GitHub
parent 615287ccfc
commit e793e6f166
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 199 additions and 78 deletions

View File

@ -1,19 +0,0 @@
import * as models from '../../../models';
import { WorkspaceScopeKeys } from '../../workspace';
import getWorkspaceName from '../get-workspace-name';
describe('getWorkspaceName', () => {
it('returns workspace name', () => {
const w = models.workspace.init();
const s = models.apiSpec.init();
w.scope = WorkspaceScopeKeys.collection;
expect(getWorkspaceName(w, s)).toBe(w.name);
});
it('returns api spec name', () => {
const w = models.workspace.init();
const s = models.apiSpec.init();
w.scope = WorkspaceScopeKeys.design;
expect(getWorkspaceName(w, s)).toBe(s.fileName);
});
});

View File

@ -1,6 +0,0 @@
import type { ApiSpec } from '../api-spec';
import { isDesign, Workspace } from '../workspace';
export default function getWorkspaceName(w: Workspace, s: ApiSpec) {
return isDesign(w) ? s.fileName : w.name;
}

View File

@ -3,20 +3,23 @@ import type { ApiSpec } from '../api-spec';
import * as models from '../index'; import * as models from '../index';
import { isDesign, Workspace } from '../workspace'; import { isDesign, Workspace } from '../workspace';
export async function rename(w: Workspace, s: ApiSpec, name: string) { export async function rename(workspace: Workspace, apiSpec: ApiSpec, name: string) {
if (isDesign(w)) { if (isDesign(workspace)) {
await models.apiSpec.update(s, { await models.apiSpec.update(apiSpec, {
fileName: name, fileName: name,
}); });
} else { } else {
await models.workspace.update(w, { await models.workspace.update(workspace, {
name, name,
}); });
} }
} }
export async function duplicate(w: Workspace, { name, parentId }: Pick<Workspace, 'name' | 'parentId'>) { export async function duplicate(
const newWorkspace = await db.duplicate(w, { workspace: Workspace,
{ name, parentId }: Pick<Workspace, 'name' | 'parentId'>,
) {
const newWorkspace = await db.duplicate(workspace, {
name, name,
parentId, parentId,
}); });

View File

@ -1,12 +1,12 @@
import { SvgIcon } from 'insomnia-components'; import { SvgIcon } from 'insomnia-components';
import React, { FC, useCallback, useState } from 'react'; import React, { FC, useCallback, useState } from 'react';
import { useSelector } from 'react-redux';
import { parseApiSpec } from '../../../common/api-specs'; import { parseApiSpec } from '../../../common/api-specs';
import { getWorkspaceLabel } from '../../../common/get-workspace-label'; import { getWorkspaceLabel } from '../../../common/get-workspace-label';
import { RENDER_PURPOSE_NO_RENDER } from '../../../common/render'; import { RENDER_PURPOSE_NO_RENDER } from '../../../common/render';
import * as models from '../../../models'; import * as models from '../../../models';
import type { ApiSpec } from '../../../models/api-spec'; import type { ApiSpec } from '../../../models/api-spec';
import getWorkspaceName from '../../../models/helpers/get-workspace-name';
import * as workspaceOperations from '../../../models/helpers/workspace-operations'; import * as workspaceOperations from '../../../models/helpers/workspace-operations';
import { Project } from '../../../models/project'; import { Project } from '../../../models/project';
import type { Workspace } from '../../../models/workspace'; import type { Workspace } from '../../../models/workspace';
@ -15,6 +15,7 @@ import type { DocumentAction } from '../../../plugins';
import { getDocumentActions } from '../../../plugins'; import { getDocumentActions } from '../../../plugins';
import * as pluginContexts from '../../../plugins/context'; import * as pluginContexts from '../../../plugins/context';
import { useLoadingRecord } from '../../hooks/use-loading-record'; import { useLoadingRecord } from '../../hooks/use-loading-record';
import { selectActiveWorkspaceName } from '../../redux/selectors';
import { Dropdown } from '../base/dropdown/dropdown'; import { Dropdown } from '../base/dropdown/dropdown';
import { DropdownButton } from '../base/dropdown/dropdown-button'; import { DropdownButton } from '../base/dropdown/dropdown-button';
import { DropdownDivider } from '../base/dropdown/dropdown-divider'; import { DropdownDivider } from '../base/dropdown/dropdown-divider';
@ -33,13 +34,15 @@ const spinner = <i className="fa fa-refresh fa-spin" />;
const useWorkspaceHandlers = ({ workspace, apiSpec }: Props) => { const useWorkspaceHandlers = ({ workspace, apiSpec }: Props) => {
const handleDuplicate = useCallback(() => { const handleDuplicate = useCallback(() => {
showWorkspaceDuplicateModal({ workspace, apiSpec }); showWorkspaceDuplicateModal({ workspace });
}, [apiSpec, workspace]); }, [workspace]);
const activeWorkspaceName = useSelector(selectActiveWorkspaceName);
const handleRename = useCallback(() => { const handleRename = useCallback(() => {
showPrompt({ showPrompt({
title: `Rename ${getWorkspaceLabel(workspace).singular}`, title: `Rename ${getWorkspaceLabel(workspace).singular}`,
defaultValue: getWorkspaceName(workspace, apiSpec), defaultValue: activeWorkspaceName,
submitName: 'Rename', submitName: 'Rename',
selectText: true, selectText: true,
label: 'Name', label: 'Name',
@ -47,13 +50,13 @@ const useWorkspaceHandlers = ({ workspace, apiSpec }: Props) => {
await workspaceOperations.rename(workspace, apiSpec, name); await workspaceOperations.rename(workspace, apiSpec, name);
}, },
}); });
}, [apiSpec, workspace]); }, [apiSpec, workspace, activeWorkspaceName]);
const handleDelete = useCallback(() => { const handleDelete = useCallback(() => {
const label = getWorkspaceLabel(workspace); const label = getWorkspaceLabel(workspace);
showModal(AskModal, { showModal(AskModal, {
title: `Delete ${label.singular}`, title: `Delete ${label.singular}`,
message: `Do you really want to delete "${getWorkspaceName(workspace, apiSpec)}"?`, message: `Do you really want to delete "${activeWorkspaceName}"?`,
yesText: 'Yes', yesText: 'Yes',
noText: 'Cancel', noText: 'Cancel',
onDone: async (isYes: boolean) => { onDone: async (isYes: boolean) => {
@ -65,7 +68,7 @@ const useWorkspaceHandlers = ({ workspace, apiSpec }: Props) => {
await models.workspace.remove(workspace); await models.workspace.remove(workspace);
}, },
}); });
}, [apiSpec, workspace]); }, [workspace, activeWorkspaceName]);
return { handleDelete, handleDuplicate, handleRename }; return { handleDelete, handleDuplicate, handleRename };
}; };

View File

@ -30,9 +30,9 @@ import { SettingsModal, TAB_INDEX_EXPORT } from '../modals/settings-modal';
import { WorkspaceSettingsModal } from '../modals/workspace-settings-modal'; import { WorkspaceSettingsModal } from '../modals/workspace-settings-modal';
interface Props { interface Props {
displayName: string;
activeEnvironment: Environment | null; activeEnvironment: Environment | null;
activeWorkspace: Workspace; activeWorkspace: Workspace;
activeWorkspaceName: string;
activeApiSpec: ApiSpec; activeApiSpec: ApiSpec;
activeProject: Project; activeProject: Project;
hotKeyRegistry: HotKeyRegistry; hotKeyRegistry: HotKeyRegistry;
@ -131,7 +131,7 @@ export class WorkspaceDropdown extends PureComponent<Props, State> {
render() { render() {
const { const {
displayName, activeWorkspaceName,
className, className,
activeWorkspace, activeWorkspace,
isLoading, isLoading,
@ -157,9 +157,9 @@ export class WorkspaceDropdown extends PureComponent<Props, State> {
style={{ style={{
maxWidth: '400px', maxWidth: '400px',
}} }}
title={displayName} title={activeWorkspaceName}
> >
{displayName} {activeWorkspaceName}
</div> </div>
<i className="fa fa-caret-down space-left" /> <i className="fa fa-caret-down space-left" />
{isLoading ? <i className="fa fa-refresh fa-spin space-left" /> : null} {isLoading ? <i className="fa fa-refresh fa-spin space-left" /> : null}

View File

@ -8,15 +8,13 @@ import { AUTOBIND_CFG } from '../../../common/constants';
import { getWorkspaceLabel } from '../../../common/get-workspace-label'; import { getWorkspaceLabel } from '../../../common/get-workspace-label';
import { strings } from '../../../common/strings'; import { strings } from '../../../common/strings';
import * as models from '../../../models'; import * as models from '../../../models';
import { ApiSpec } from '../../../models/api-spec';
import getWorkspaceName from '../../../models/helpers/get-workspace-name';
import * as workspaceOperations from '../../../models/helpers/workspace-operations'; import * as workspaceOperations from '../../../models/helpers/workspace-operations';
import { isDefaultProject, isLocalProject, isRemoteProject, Project } from '../../../models/project'; import { isDefaultProject, isLocalProject, isRemoteProject, Project } from '../../../models/project';
import { Workspace } from '../../../models/workspace'; import { Workspace } from '../../../models/workspace';
import { initializeLocalBackendProjectAndMarkForSync } from '../../../sync/vcs/initialize-backend-project'; import { initializeLocalBackendProjectAndMarkForSync } from '../../../sync/vcs/initialize-backend-project';
import { VCS } from '../../../sync/vcs/vcs'; import { VCS } from '../../../sync/vcs/vcs';
import { activateWorkspace } from '../../redux/modules/workspace'; import { activateWorkspace } from '../../redux/modules/workspace';
import { selectActiveProject, selectIsLoggedIn, selectProjects } from '../../redux/selectors'; import { selectActiveProject, selectActiveWorkspaceName, selectIsLoggedIn, selectProjects } from '../../redux/selectors';
import { Modal } from '../base/modal'; import { Modal } from '../base/modal';
import { ModalBody } from '../base/modal-body'; import { ModalBody } from '../base/modal-body';
import { ModalFooter } from '../base/modal-footer'; import { ModalFooter } from '../base/modal-footer';
@ -25,7 +23,6 @@ import { showModal } from '.';
interface Options { interface Options {
workspace: Workspace; workspace: Workspace;
apiSpec: ApiSpec;
onDone?: () => void; onDone?: () => void;
} }
@ -44,7 +41,7 @@ const ProjectOption: FC<Project> = project => (
</option> </option>
); );
const WorkspaceDuplicateModalInternalWithRef: ForwardRefRenderFunction<Modal, InnerProps> = ({ workspace, apiSpec, onDone, hide, vcs }, ref) => { const WorkspaceDuplicateModalInternalWithRef: ForwardRefRenderFunction<Modal, InnerProps> = ({ workspace, onDone, hide, vcs }, ref) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const projects = useSelector(selectProjects); const projects = useSelector(selectProjects);
@ -52,7 +49,7 @@ const WorkspaceDuplicateModalInternalWithRef: ForwardRefRenderFunction<Modal, In
const isLoggedIn = useSelector(selectIsLoggedIn); const isLoggedIn = useSelector(selectIsLoggedIn);
const title = `Duplicate ${getWorkspaceLabel(workspace).singular}`; const title = `Duplicate ${getWorkspaceLabel(workspace).singular}`;
const defaultWorkspaceName = getWorkspaceName(workspace, apiSpec); const activeWorkspaceName = useSelector(selectActiveWorkspaceName);
const { const {
register, register,
@ -62,7 +59,7 @@ const WorkspaceDuplicateModalInternalWithRef: ForwardRefRenderFunction<Modal, In
errors, errors,
} } = useForm<FormFields>({ } } = useForm<FormFields>({
defaultValues: { defaultValues: {
newName: defaultWorkspaceName, newName: activeWorkspaceName,
projectId: activeProject._id, projectId: activeProject._id,
}, },
}); });

View File

@ -1,5 +1,6 @@
import { autoBindMethodsForReact } from 'class-autobind-decorator'; import { autoBindMethodsForReact } from 'class-autobind-decorator';
import React, { FC, PureComponent, ReactNode } from 'react'; import React, { FC, PureComponent, ReactNode } from 'react';
import { connect } from 'react-redux';
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs'; import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
import styled from 'styled-components'; import styled from 'styled-components';
@ -8,10 +9,11 @@ import { getWorkspaceLabel } from '../../../common/get-workspace-label';
import { HandleGetRenderContext, HandleRender } from '../../../common/render'; import { HandleGetRenderContext, HandleRender } from '../../../common/render';
import type { ApiSpec } from '../../../models/api-spec'; import type { ApiSpec } from '../../../models/api-spec';
import type { ClientCertificate } from '../../../models/client-certificate'; import type { ClientCertificate } from '../../../models/client-certificate';
import getWorkspaceName from '../../../models/helpers/get-workspace-name';
import * as workspaceOperations from '../../../models/helpers/workspace-operations'; import * as workspaceOperations from '../../../models/helpers/workspace-operations';
import * as models from '../../../models/index'; import * as models from '../../../models/index';
import type { Workspace } from '../../../models/workspace'; import type { Workspace } from '../../../models/workspace';
import { RootState } from '../../redux/modules';
import { selectActiveWorkspaceName } from '../../redux/selectors';
import { DebouncedInput } from '../base/debounced-input'; import { DebouncedInput } from '../base/debounced-input';
import { FileInputButton } from '../base/file-input-button'; import { FileInputButton } from '../base/file-input-button';
import { Modal } from '../base/modal'; import { Modal } from '../base/modal';
@ -58,7 +60,9 @@ const CertificateField: FC<{
); );
}; };
interface Props { type ReduxProps = ReturnType<typeof mapStateToProps>;
interface Props extends ReduxProps {
clientCertificates: ClientCertificate[]; clientCertificates: ClientCertificate[];
workspace: Workspace; workspace: Workspace;
apiSpec: ApiSpec; apiSpec: ApiSpec;
@ -87,7 +91,7 @@ interface State {
} }
@autoBindMethodsForReact(AUTOBIND_CFG) @autoBindMethodsForReact(AUTOBIND_CFG)
export class WorkspaceSettingsModal extends PureComponent<Props, State> { export class UnconnectedWorkspaceSettingsModal extends PureComponent<Props, State> {
modal: Modal | null = null; modal: Modal | null = null;
state: State = { state: State = {
@ -127,8 +131,8 @@ export class WorkspaceSettingsModal extends PureComponent<Props, State> {
} }
_handleDuplicateWorkspace() { _handleDuplicateWorkspace() {
const { workspace, apiSpec } = this.props; const { workspace } = this.props;
showWorkspaceDuplicateModal({ workspace, apiSpec, onDone: this.hide }); showWorkspaceDuplicateModal({ workspace, onDone: this.hide });
} }
_handleToggleCertificateForm() { _handleToggleCertificateForm() {
@ -292,7 +296,7 @@ export class WorkspaceSettingsModal extends PureComponent<Props, State> {
const { const {
clientCertificates, clientCertificates,
workspace, workspace,
apiSpec, activeWorkspaceName,
editorLineWrapping, editorLineWrapping,
editorFontSize, editorFontSize,
editorIndentSize, editorIndentSize,
@ -333,7 +337,7 @@ export class WorkspaceSettingsModal extends PureComponent<Props, State> {
type="text" type="text"
delay={500} delay={500}
placeholder="Awesome API" placeholder="Awesome API"
defaultValue={getWorkspaceName(workspace, apiSpec)} defaultValue={activeWorkspaceName}
onChange={this._handleRename} onChange={this._handleRename}
/> />
</label> </label>
@ -539,3 +543,9 @@ export class WorkspaceSettingsModal extends PureComponent<Props, State> {
); );
} }
} }
const mapStateToProps = (state: RootState) => ({
activeWorkspaceName: selectActiveWorkspaceName(state),
});
export const WorkspaceSettingsModal = connect(mapStateToProps, null, null, { forwardRef: true })(UnconnectedWorkspaceSettingsModal);

View File

@ -8,7 +8,7 @@ import { getWorkspaceLabel } from '../../../common/get-workspace-label';
import { strings } from '../../../common/strings'; import { strings } from '../../../common/strings';
import { exportAllToFile } from '../../redux/modules/global'; import { exportAllToFile } from '../../redux/modules/global';
import { importClipBoard, importFile, importUri } from '../../redux/modules/import'; import { importClipBoard, importFile, importUri } from '../../redux/modules/import';
import { selectActiveProjectName, selectActiveWorkspace } from '../../redux/selectors'; import { selectActiveProjectName, selectActiveWorkspace, selectActiveWorkspaceName } from '../../redux/selectors';
import { Dropdown } from '../base/dropdown/dropdown'; import { Dropdown } from '../base/dropdown/dropdown';
import { DropdownButton } from '../base/dropdown/dropdown-button'; import { DropdownButton } from '../base/dropdown/dropdown-button';
import { DropdownDivider } from '../base/dropdown/dropdown-divider'; import { DropdownDivider } from '../base/dropdown/dropdown-divider';
@ -64,6 +64,8 @@ export const ImportExport: FC<Props> = ({ hideSettingsModal }) => {
hideSettingsModal(); hideSettingsModal();
}, [hideSettingsModal, activeWorkspace, dispatch]); }, [hideSettingsModal, activeWorkspace, dispatch]);
const activeWorkspaceName = useSelector(selectActiveWorkspaceName);
return ( return (
<div> <div>
<div className="no-margin-top"> <div className="no-margin-top">
@ -83,7 +85,7 @@ export const ImportExport: FC<Props> = ({ hideSettingsModal }) => {
<DropdownDivider>Choose Export Type</DropdownDivider> <DropdownDivider>Choose Export Type</DropdownDivider>
{activeWorkspace && <DropdownItem onClick={showExportRequestsModal}> {activeWorkspace && <DropdownItem onClick={showExportRequestsModal}>
<i className="fa fa-home" /> <i className="fa fa-home" />
Export the "{activeWorkspace.name}" {getWorkspaceLabel(activeWorkspace).singular} Export the "{activeWorkspaceName}" {getWorkspaceLabel(activeWorkspace).singular}
</DropdownItem>} </DropdownItem>}
<DropdownItem onClick={handleExportAllToFile}> <DropdownItem onClick={handleExportAllToFile}>
<i className="fa fa-empty" /> <i className="fa fa-empty" />

View File

@ -3,7 +3,7 @@ import React, { Fragment, FunctionComponent, ReactNode, useCallback } from 'reac
import { ACTIVITY_HOME, GlobalActivity } from '../../common/constants'; import { ACTIVITY_HOME, GlobalActivity } from '../../common/constants';
import { strings } from '../../common/strings'; import { strings } from '../../common/strings';
import { isCollection, isDesign } from '../../models/workspace'; import { isDesign } from '../../models/workspace';
import coreLogo from '../images/insomnia-core-logo.png'; import coreLogo from '../images/insomnia-core-logo.png';
import { ActivityToggle } from './activity-toggle'; import { ActivityToggle } from './activity-toggle';
import SettingsButton from './buttons/settings-button'; import SettingsButton from './buttons/settings-button';
@ -23,6 +23,7 @@ export const WorkspacePageHeader: FunctionComponent<Props> = ({
wrapperProps: { wrapperProps: {
activeApiSpec, activeApiSpec,
activeWorkspace, activeWorkspace,
activeWorkspaceName,
activeProject, activeProject,
activeEnvironment, activeEnvironment,
settings, settings,
@ -35,18 +36,15 @@ export const WorkspacePageHeader: FunctionComponent<Props> = ({
[activeWorkspace, handleActivityChange], [activeWorkspace, handleActivityChange],
); );
if (!activeWorkspace || !activeApiSpec || !activity) { if (!activeWorkspace || !activeWorkspaceName || !activeApiSpec || !activity) {
return null; return null;
} }
const collection = isCollection(activeWorkspace);
const design = isDesign(activeWorkspace);
const workspace = ( const workspace = (
<WorkspaceDropdown <WorkspaceDropdown
displayName={collection ? activeWorkspace.name : activeApiSpec.fileName}
activeEnvironment={activeEnvironment} activeEnvironment={activeEnvironment}
activeWorkspace={activeWorkspace} activeWorkspace={activeWorkspace}
activeWorkspaceName={activeWorkspaceName}
activeApiSpec={activeApiSpec} activeApiSpec={activeApiSpec}
activeProject={activeProject} activeProject={activeProject}
hotKeyRegistry={settings.hotKeyRegistry} hotKeyRegistry={settings.hotKeyRegistry}
@ -69,7 +67,7 @@ export const WorkspacePageHeader: FunctionComponent<Props> = ({
</Fragment> </Fragment>
} }
gridCenter={ gridCenter={
design && ( isDesign(activeWorkspace) && (
<ActivityToggle <ActivityToggle
activity={activity} activity={activity}
handleActivityChange={handleActivityChange} handleActivityChange={handleActivityChange}

View File

@ -52,7 +52,7 @@ import { Request, updateMimeType } from '../../models/request';
import { isRequestGroup, RequestGroup } from '../../models/request-group'; import { isRequestGroup, RequestGroup } from '../../models/request-group';
import { RequestMeta } from '../../models/request-meta'; import { RequestMeta } from '../../models/request-meta';
import { Response } from '../../models/response'; import { Response } from '../../models/response';
import { isCollection, isWorkspace } from '../../models/workspace'; import { isWorkspace } from '../../models/workspace';
import { WorkspaceMeta } from '../../models/workspace-meta'; import { WorkspaceMeta } from '../../models/workspace-meta';
import * as network from '../../network/network'; import * as network from '../../network/network';
import * as plugins from '../../plugins'; import * as plugins from '../../plugins';
@ -97,6 +97,7 @@ import {
} from '../redux/modules/global'; } from '../redux/modules/global';
import { importUri } from '../redux/modules/import'; import { importUri } from '../redux/modules/import';
import { import {
selectActiveApiSpec,
selectActiveCookieJar, selectActiveCookieJar,
selectActiveEnvironment, selectActiveEnvironment,
selectActiveGitRepository, selectActiveGitRepository,
@ -113,6 +114,7 @@ import {
selectActiveWorkspace, selectActiveWorkspace,
selectActiveWorkspaceClientCertificates, selectActiveWorkspaceClientCertificates,
selectActiveWorkspaceMeta, selectActiveWorkspaceMeta,
selectActiveWorkspaceName,
selectEntitiesLists, selectEntitiesLists,
selectSettings, selectSettings,
selectSyncItems, selectSyncItems,
@ -1094,7 +1096,7 @@ class App extends PureComponent<AppProps, State> {
const { const {
activeWorkspace, activeWorkspace,
activeProject, activeProject,
activeApiSpec, activeWorkspaceName,
activeEnvironment, activeEnvironment,
activeRequest, activeRequest,
activity, activity,
@ -1103,9 +1105,9 @@ class App extends PureComponent<AppProps, State> {
if (activity === ACTIVITY_HOME || activity === ACTIVITY_MIGRATION) { if (activity === ACTIVITY_HOME || activity === ACTIVITY_MIGRATION) {
title = getAppName(); title = getAppName();
} else if (activeWorkspace && activeApiSpec) { } else if (activeWorkspace && activeWorkspaceName) {
title = activeProject.name; title = activeProject.name;
title += ` - ${isCollection(activeWorkspace) ? activeWorkspace.name : activeApiSpec.fileName}`; title += ` - ${activeWorkspaceName}`;
if (activeEnvironment) { if (activeEnvironment) {
title += ` (${activeEnvironment.name})`; title += ` (${activeEnvironment.name})`;
@ -1590,6 +1592,7 @@ function mapStateToProps(state: RootState) {
const workspaces = selectWorkspacesForActiveProject(state); const workspaces = selectWorkspacesForActiveProject(state);
const activeWorkspaceMeta = selectActiveWorkspaceMeta(state); const activeWorkspaceMeta = selectActiveWorkspaceMeta(state);
const activeWorkspace = selectActiveWorkspace(state); const activeWorkspace = selectActiveWorkspace(state);
const activeWorkspaceName = selectActiveWorkspaceName(state);
const activeWorkspaceClientCertificates = selectActiveWorkspaceClientCertificates(state); const activeWorkspaceClientCertificates = selectActiveWorkspaceClientCertificates(state);
const activeGitRepository = selectActiveGitRepository(state); const activeGitRepository = selectActiveGitRepository(state);
@ -1631,7 +1634,7 @@ function mapStateToProps(state: RootState) {
const syncItems = selectSyncItems(state); const syncItems = selectSyncItems(state);
// Api spec stuff // Api spec stuff
const activeApiSpec = apiSpecs.find(s => s.parentId === activeWorkspace?._id); const activeApiSpec = selectActiveApiSpec(state);
// Test stuff // Test stuff
const activeUnitTests = selectActiveUnitTests(state); const activeUnitTests = selectActiveUnitTests(state);
@ -1643,6 +1646,7 @@ function mapStateToProps(state: RootState) {
activity: activeActivity, activity: activeActivity,
activeProject, activeProject,
activeApiSpec, activeApiSpec,
activeWorkspaceName,
activeCookieJar, activeCookieJar,
activeEnvironment, activeEnvironment,
activeGitRepository, activeGitRepository,

View File

@ -1,8 +1,10 @@
import { globalBeforeEach } from '../../../__jest__/before-each'; import { globalBeforeEach } from '../../../__jest__/before-each';
import { reduxStateForTest } from '../../../__jest__/redux-state-for-test'; import { reduxStateForTest } from '../../../__jest__/redux-state-for-test';
import { ACTIVITY_DEBUG } from '../../../common/constants';
import * as models from '../../../models'; import * as models from '../../../models';
import { DEFAULT_PROJECT_ID, Project } from '../../../models/project'; import { DEFAULT_PROJECT_ID, Project } from '../../../models/project';
import { selectActiveProject } from '../selectors'; import { WorkspaceScopeKeys } from '../../../models/workspace';
import { selectActiveApiSpec, selectActiveProject, selectActiveWorkspaceName } from '../selectors';
describe('selectors', () => { describe('selectors', () => {
beforeEach(globalBeforeEach); beforeEach(globalBeforeEach);
@ -44,4 +46,98 @@ describe('selectors', () => {
expect(project).toStrictEqual(expect.objectContaining<Partial<Project>>({ _id: DEFAULT_PROJECT_ID })); 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 throw when there is not an active apiSpec', async () => {
const workspace = await models.workspace.create({
name: 'workspace.name',
scope: WorkspaceScopeKeys.design,
});
const state = await reduxStateForTest({
activeActivity: ACTIVITY_DEBUG,
activeWorkspaceId: workspace._id,
});
state.entities.apiSpecs = {};
const execute = () => selectActiveApiSpec(state);
expect(execute).toThrowError(`an api spec not found for the workspace ${workspace._id} (workspace.name)`);
});
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);
});
});
}); });

View File

@ -10,6 +10,7 @@ import { DEFAULT_PROJECT_ID, isRemoteProject } from '../../models/project';
import { isRequest, Request } from '../../models/request'; import { isRequest, Request } from '../../models/request';
import { isRequestGroup, RequestGroup } from '../../models/request-group'; import { isRequestGroup, RequestGroup } from '../../models/request-group';
import { UnitTestResult } from '../../models/unit-test-result'; import { UnitTestResult } from '../../models/unit-test-result';
import { isCollection } from '../../models/workspace';
import { RootState } from './modules'; import { RootState } from './modules';
type EntitiesLists = { type EntitiesLists = {
@ -100,15 +101,10 @@ export const selectAllWorkspaces = createSelector(
entities => entities.workspaces, entities => entities.workspaces,
); );
export const selectAllApiSpecs = createSelector(
selectEntitiesLists,
entities => entities.apiSpecs,
);
export const selectWorkspacesForActiveProject = createSelector( export const selectWorkspacesForActiveProject = createSelector(
selectAllWorkspaces, selectAllWorkspaces,
selectActiveProject, selectActiveProject,
(workspaces, activeProject) => workspaces.filter(w => w.parentId === activeProject._id), (workspaces, activeProject) => workspaces.filter(workspace => workspace.parentId === activeProject._id),
); );
export const selectActiveWorkspace = createSelector( export const selectActiveWorkspace = createSelector(
@ -118,7 +114,7 @@ export const selectActiveWorkspace = createSelector(
(workspaces, activeWorkspaceId, activeActivity) => { (workspaces, activeWorkspaceId, activeActivity) => {
// Only return an active workspace if we're in an activity // Only return an active workspace if we're in an activity
if (activeActivity && isWorkspaceActivity(activeActivity)) { if (activeActivity && isWorkspaceActivity(activeActivity)) {
const workspace = workspaces.find(w => w._id === activeWorkspaceId); const workspace = workspaces.find(workspace => workspace._id === activeWorkspaceId);
return workspace; return workspace;
} }
@ -135,6 +131,43 @@ export const selectActiveWorkspaceMeta = createSelector(
}, },
); );
export const selectAllApiSpecs = createSelector(
selectEntitiesLists,
entities => entities.apiSpecs,
);
export const selectActiveApiSpec = createSelector(
selectAllApiSpecs,
selectActiveWorkspace,
(apiSpecs, activeWorkspace) => {
if (!activeWorkspace) {
// There should never be an active api spec without an active workspace
return undefined;
}
const activeSpec = apiSpecs.find(apiSpec => apiSpec.parentId === activeWorkspace._id);
if (!activeSpec) {
// This case should never be reached; an api spec should always exist for a given workspace.
throw new Error(`an api spec not found for the workspace ${activeWorkspace._id} (${activeWorkspace.name})`);
}
return activeSpec;
}
);
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 selectActiveEnvironment = createSelector( export const selectActiveEnvironment = createSelector(
selectActiveWorkspaceMeta, selectActiveWorkspaceMeta,
selectEntitiesLists, selectEntitiesLists,