Skip to content

Commit 4daa44e

Browse files
authored
Create a workspace in the active space and filter dashboard for active space (Kong#3446)
1 parent 6f6fa08 commit 4daa44e

File tree

8 files changed

+137
-76
lines changed

8 files changed

+137
-76
lines changed

packages/insomnia-app/app/models/workspace.ts

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,9 @@ import { ValueOf } from 'type-fest';
77
import { isSpaceId } from './helpers/is-model';
88

99
export const name = 'Workspace';
10-
1110
export const type = 'Workspace';
12-
1311
export const prefix = 'wrk';
14-
1512
export const canDuplicate = true;
16-
1713
export const canSync = true;
1814

1915
export const WorkspaceScopeKeys = {
@@ -60,18 +56,18 @@ export async function create(patch: Partial<Workspace> = {}) {
6056
}
6157

6258
export async function all() {
63-
const workspaces = await db.all<Workspace>(type) || [];
59+
const workspaces = await db.all<Workspace>(type);
6460

65-
if (workspaces.length === 0) {
66-
// Create default workspace
67-
await create({
68-
name: getAppName(),
69-
scope: WorkspaceScopeKeys.collection,
70-
});
71-
return all();
72-
} else {
61+
if (workspaces.length > 0) {
7362
return workspaces;
7463
}
64+
65+
// Create default workspace
66+
await create({
67+
name: getAppName(),
68+
scope: WorkspaceScopeKeys.collection,
69+
});
70+
return db.all<Workspace>(type);
7571
}
7672

7773
export function count() {

packages/insomnia-app/app/ui/components/wrapper-home.tsx

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ import { strings } from '../../common/strings';
4848
import { descendingNumberSort } from '../../common/sorting';
4949
import { connect } from 'react-redux';
5050
import { bindActionCreators } from 'redux';
51-
import * as workspaceActions from '../redux/modules/workspace';
52-
import * as gitActions from '../redux/modules/git';
51+
import { createWorkspace } from '../redux/modules/workspace';
52+
import { cloneGitRepository } from '../redux/modules/git';
5353
import { MemClient } from '../../sync/git/mem-client';
5454
import { SpaceDropdown } from './dropdowns/space-dropdown';
5555

@@ -58,13 +58,16 @@ interface RenderedCard {
5858
lastModifiedTimestamp?: number | null;
5959
}
6060

61-
interface Props {
61+
interface ReduxDispatchProps {
62+
handleCreateWorkspace: typeof createWorkspace;
63+
handleGitCloneWorkspace: typeof cloneGitRepository;
64+
}
65+
66+
interface Props extends ReduxDispatchProps {
6267
wrapperProps: WrapperProps;
6368
handleImportFile: HandleImportFileCallback;
6469
handleImportUri: HandleImportUriCallback;
6570
handleImportClipboard: HandleImportClipboardCallback;
66-
handleCreateWorkspace: workspaceActions.CreateWorkspaceCallback;
67-
handleGitCloneWorkspace: Function;
6871
}
6972

7073
interface State {
@@ -415,11 +418,9 @@ class WrapperHome extends PureComponent<Props, State> {
415418
}
416419
}
417420

418-
function mapDispatchToProps(dispatch) {
419-
return {
420-
handleCreateWorkspace: bindActionCreators(workspaceActions.createWorkspace, dispatch),
421-
handleGitCloneWorkspace: bindActionCreators(gitActions.cloneGitRepository, dispatch),
422-
};
423-
}
421+
const mapDispatchToProps = (dispatch): ReduxDispatchProps => ({
422+
handleCreateWorkspace: bindActionCreators(createWorkspace, dispatch),
423+
handleGitCloneWorkspace: bindActionCreators(cloneGitRepository, dispatch),
424+
});
424425

425426
export default connect(null, mapDispatchToProps)(WrapperHome);

packages/insomnia-app/app/ui/containers/app.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import {
5555
selectSyncItems,
5656
selectUnseenWorkspaces,
5757
selectWorkspaceRequestsAndRequestGroups,
58+
selectWorkspacesForActiveSpace,
5859
} from '../redux/selectors';
5960
import { selectSidebarChildren } from '../redux/sidebar-selectors';
6061
import RequestCreateModal from '../components/modals/request-create-modal';
@@ -1596,12 +1597,13 @@ function mapStateToProps(state, props) {
15961597
requests,
15971598
// @ts-expect-error -- TSCONVERSION
15981599
workspaceMetas,
1599-
// @ts-expect-error -- TSCONVERSION
1600-
workspaces,
16011600
} = entitiesLists;
16021601
// @ts-expect-error -- TSCONVERSION
16031602
const settings = entitiesLists.settings[0];
16041603
// Workspace stuff
1604+
1605+
// @ts-expect-error -- TSCONVERSION https://github.com/reduxjs/reselect#accessing-react-props-in-selectors
1606+
const workspaces = selectWorkspacesForActiveSpace(state, props);
16051607
// @ts-expect-error -- TSCONVERSION https://github.com/reduxjs/reselect#accessing-react-props-in-selectors
16061608
const activeWorkspaceMeta = selectActiveWorkspaceMeta(state, props);
16071609
// @ts-expect-error -- TSCONVERSION https://github.com/reduxjs/reselect#accessing-react-props-in-selectors

packages/insomnia-app/app/ui/redux/modules/__tests__/git.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ describe('git', () => {
3131
let store;
3232
beforeEach(async () => {
3333
await globalBeforeEach();
34-
store = mockStore();
34+
store = mockStore({ entities: { spaces: [] }, global: {} });
3535
});
3636
// Check loading events
3737
afterEach(() => {

packages/insomnia-app/app/ui/redux/modules/__tests__/workspace.test.ts

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,36 @@ jest.mock('../../../../common/analytics');
1515
const middlewares = [thunk];
1616
const mockStore = configureMockStore(middlewares);
1717

18+
const createStoreWithSpace = async () => {
19+
const space = await models.initModel(models.space.type);
20+
21+
const entities = { spaces: { [space._id]: space } };
22+
const global = { activeSpaceId: space._id };
23+
const store = mockStore({ entities, global });
24+
return { store, space };
25+
};
26+
1827
describe('workspace', () => {
1928
beforeEach(globalBeforeEach);
2029
describe('createWorkspace', () => {
2130
it('should create document', async () => {
22-
const store = mockStore();
23-
store.dispatch(
24-
createWorkspace({
25-
scope: WorkspaceScopeKeys.design,
26-
}),
27-
);
31+
const { store, space } = await createStoreWithSpace();
32+
33+
// @ts-expect-error redux-thunk types
34+
store.dispatch(createWorkspace({ scope: WorkspaceScopeKeys.design }));
35+
2836
const { title, submitName, defaultValue, onComplete } = getAndClearShowPromptMockArgs();
2937
expect(title).toBe('Create New Design Document');
3038
expect(submitName).toBe('Create');
3139
expect(defaultValue).toBe('my-spec.yaml');
3240
const workspaceName = 'name';
33-
await onComplete(workspaceName);
41+
await onComplete?.(workspaceName);
3442
const workspaces = await models.workspace.all();
3543
expect(workspaces).toHaveLength(1);
3644
const workspace = workspaces[0];
3745
expect(workspace.name).toBe(workspaceName);
3846
expect(workspace.scope).toBe(WorkspaceScopeKeys.design);
47+
expect(workspace.parentId).toBe(space._id);
3948
expect(trackSegmentEvent).toHaveBeenCalledWith('Document Created');
4049
expect(trackEvent).toHaveBeenCalledWith('Workspace', 'Create');
4150
expect(store.getActions()).toEqual([
@@ -51,23 +60,61 @@ describe('workspace', () => {
5160
});
5261

5362
it('should create collection', async () => {
54-
const store = mockStore();
55-
store.dispatch(
56-
createWorkspace({
57-
scope: WorkspaceScopeKeys.collection,
58-
}),
59-
);
63+
const { store, space } = await createStoreWithSpace();
64+
65+
// @ts-expect-error redux-thunk types
66+
store.dispatch(createWorkspace({ scope: WorkspaceScopeKeys.collection }));
67+
6068
const { title, submitName, defaultValue, onComplete } = getAndClearShowPromptMockArgs();
6169
expect(title).toBe('Create New Request Collection');
6270
expect(submitName).toBe('Create');
6371
expect(defaultValue).toBe('My Collection');
6472
const workspaceName = 'name';
65-
await onComplete(workspaceName);
73+
await onComplete?.(workspaceName);
6674
const workspaces = await models.workspace.all();
6775
expect(workspaces).toHaveLength(1);
6876
const workspace = workspaces[0];
69-
expect(workspace.name).toBe(workspaceName);
70-
expect(workspace.scope).toBe(WorkspaceScopeKeys.collection);
77+
expect(workspace).toMatchObject({
78+
name: workspaceName,
79+
scope: WorkspaceScopeKeys.collection,
80+
parentId: space._id,
81+
});
82+
expect(trackSegmentEvent).toHaveBeenCalledWith('Collection Created');
83+
expect(trackEvent).toHaveBeenCalledWith('Workspace', 'Create');
84+
expect(store.getActions()).toEqual([
85+
{
86+
type: SET_ACTIVE_WORKSPACE,
87+
workspaceId: workspace._id,
88+
},
89+
{
90+
type: SET_ACTIVE_ACTIVITY,
91+
activity: ACTIVITY_DEBUG,
92+
},
93+
]);
94+
});
95+
96+
it('should create with no space', async () => {
97+
const entities = { spaces: {} };
98+
const global = { };
99+
const store = mockStore({ entities, global });
100+
101+
// @ts-expect-error redux-thunk types
102+
store.dispatch(createWorkspace({ scope: WorkspaceScopeKeys.collection }));
103+
104+
const { title, submitName, defaultValue, onComplete } = getAndClearShowPromptMockArgs();
105+
expect(title).toBe('Create New Request Collection');
106+
expect(submitName).toBe('Create');
107+
expect(defaultValue).toBe('My Collection');
108+
const workspaceName = 'name';
109+
await onComplete?.(workspaceName);
110+
const workspaces = await models.workspace.all();
111+
expect(workspaces).toHaveLength(1);
112+
const workspace = workspaces[0];
113+
expect(workspace).toMatchObject({
114+
name: workspaceName,
115+
scope: WorkspaceScopeKeys.collection,
116+
parentId: null,
117+
});
71118
expect(trackSegmentEvent).toHaveBeenCalledWith('Collection Created');
72119
expect(trackEvent).toHaveBeenCalledWith('Workspace', 'Create');
73120
expect(store.getActions()).toEqual([

packages/insomnia-app/app/ui/redux/modules/git.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -135,14 +135,12 @@ const noDocumentFound = (gitRepo: GitRepository) => {
135135
};
136136
};
137137

138-
export type CloneGitRepositoryCallback = (arg0: {
139-
createFsClient: () => git.PromiseFsClient;
140-
}) => void;
141-
142138
/**
143139
* Clone a git repository
144140
* */
145-
export const cloneGitRepository: CloneGitRepositoryCallback = ({ createFsClient }) => {
141+
export const cloneGitRepository = ({ createFsClient }: {
142+
createFsClient: () => git.PromiseFsClient;
143+
}) => {
146144
return dispatch => {
147145
showModal(GitRepositorySettingsModal, {
148146
gitRepository: null,

packages/insomnia-app/app/ui/redux/modules/workspace.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { trackEvent, trackSegmentEvent } from '../../../common/analytics';
55
import { isDesign } from '../../../models/helpers/is-model';
66
import { showPrompt } from '../../components/modals';
77
import { setActiveActivity, setActiveWorkspace } from './global';
8+
import { selectActiveSpace } from '../selectors';
89

910
type OnWorkspaceCreateCallback = (arg0: Workspace) => Promise<void> | void;
1011

@@ -22,13 +23,14 @@ const actuallyCreate = (patch: Partial<Workspace>, onCreate?: OnWorkspaceCreateC
2223
};
2324
};
2425

25-
export type CreateWorkspaceCallback = (arg0: {
26+
export const createWorkspace = ({ scope, onCreate }: {
2627
scope: WorkspaceScope;
2728
onCreate?: OnWorkspaceCreateCallback;
28-
}) => void;
29+
}) => {
30+
return (dispatch, getState) => {
31+
const activeSpace = selectActiveSpace(getState());
32+
const parentId = activeSpace?._id || null;
2933

30-
export const createWorkspace: CreateWorkspaceCallback = ({ scope, onCreate }) => {
31-
return dispatch => {
3234
const design = isDesign({
3335
scope,
3436
});
@@ -47,6 +49,8 @@ export const createWorkspace: CreateWorkspaceCallback = ({ scope, onCreate }) =>
4749
{
4850
name,
4951
scope,
52+
// @ts-expect-error TSCONVERSION the common parentId isn't typed correctly
53+
parentId,
5054
},
5155
onCreate,
5256
),

packages/insomnia-app/app/ui/redux/selectors.ts

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { createSelector } from 'reselect';
22
import * as models from '../../models';
33
import { Space } from '../../models/space';
44
import { UnitTestResult } from '../../models/unit-test-result';
5+
import { Workspace } from '../../models/workspace';
56
// ~~~~~~~~~ //
67
// Selectors //
78
// ~~~~~~~~~ //
@@ -38,21 +39,52 @@ export const selectEntitiesChildrenMap = createSelector(selectEntitiesLists, ent
3839

3940
return parentLookupMap;
4041
});
42+
4143
export const selectSettings = createSelector(selectEntitiesLists, entities => {
4244
// @ts-expect-error -- TSCONVERSION
4345
return entities.settings[0] || models.settings.init();
4446
});
45-
export const selectActiveWorkspace = createSelector(
46-
// @ts-expect-error -- TSCONVERSION
47-
state => selectEntitiesLists(state).workspaces,
47+
48+
export const selectSpaces = createSelector(
4849
// @ts-expect-error -- TSCONVERSION
50+
state => selectEntitiesLists(state).spaces as Space[],
51+
(spaces) => {
52+
return spaces;
53+
},
54+
);
55+
56+
export const selectActiveSpace = createSelector<any, {}, string, Space | undefined>(
4957
state => state.entities,
58+
state => state.global.activeSpaceId,
59+
(entities, activeSpaceId) => {
60+
// @ts-expect-error -- TSCONVERSION
61+
return entities.spaces[activeSpaceId];
62+
},
63+
);
64+
65+
export const selectWorkspacesForActiveSpace = createSelector(
5066
// @ts-expect-error -- TSCONVERSION
67+
state => selectEntitiesLists(state).workspaces as Workspace[],
68+
selectActiveSpace,
69+
(workspaces, activeSpace) => {
70+
const parentId = activeSpace?._id || null;
71+
return workspaces.filter(w => w.parentId === parentId);
72+
},
73+
);
74+
75+
export const selectActiveWorkspace = createSelector(
76+
// @ts-expect-error -- TSCONVERSION
77+
state => selectEntitiesLists(state).workspaces as Workspace[],
78+
selectWorkspacesForActiveSpace,
5179
state => state.global.activeWorkspaceId,
52-
(workspaces, entities, activeWorkspaceId) => {
53-
return entities.workspaces[activeWorkspaceId] || workspaces[0];
80+
(allWorkspaces, workspaces, activeWorkspaceId) => {
81+
const activeWorkspace = workspaces.find(w => w._id === activeWorkspaceId) || workspaces[0];
82+
// This fallback is needed because while a space may not have any workspaces
83+
// The app still _needs_ an active workspace.
84+
return activeWorkspace || allWorkspaces[0];
5485
},
5586
);
87+
5688
export const selectActiveWorkspaceMeta = createSelector(
5789
selectActiveWorkspace,
5890
selectEntitiesLists,
@@ -299,25 +331,6 @@ export const selectActiveUnitTests = createSelector(
299331
},
300332
);
301333

302-
// TODO(TSCONVERSION) type this properly when doing the rest of this file
303-
export const selectSpaces = createSelector<any, {}, Space[]>(
304-
selectEntitiesLists,
305-
(entities) => {
306-
// @ts-expect-error -- TSCONVERSION
307-
return entities.spaces;
308-
},
309-
);
310-
311-
// TODO(TSCONVERSION) type this properly when doing the rest of this file
312-
export const selectActiveSpace = createSelector<any, {}, {}, Space | undefined>(
313-
state => state.entities,
314-
state => state.global.activeSpaceId,
315-
(entities, activeSpaceId) => {
316-
// @ts-expect-error -- TSCONVERSION
317-
return entities.spaces[activeSpaceId];
318-
},
319-
);
320-
321334
export const selectActiveUnitTestSuites = createSelector(
322335
selectEntitiesLists,
323336
selectActiveWorkspace,

0 commit comments

Comments
 (0)