Skip to content

Commit

Permalink
fix export from outside workspace (Kong#6307)
Browse files Browse the repository at this point in the history
* add test

* simplify export requests modal logic

* fix export

* fix type check

* fix test
  • Loading branch information
jackkav authored Aug 18, 2023
1 parent 5ecc3ab commit 8fa8b9b
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 115 deletions.
8 changes: 8 additions & 0 deletions packages/insomnia-smoke-test/tests/smoke/app.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ test('can send requests', async ({ app, page }) => {
await page.getByText('Clipboard').click();
await page.getByRole('button', { name: 'Scan' }).click();
await page.getByRole('dialog').getByRole('button', { name: 'Import' }).click();

await page.locator('.card-menu').click();
await page.getByRole('menuitem', { name: 'Export' }).click();
await page.getByRole('dialog').getByRole('checkbox').nth(1).uncheck();
await page.getByRole('button', { name: 'Export' }).click();
await page.getByText('Which format would you like to export as?').click();
await page.locator('.app').press('Escape');

await page.getByText('CollectionSmoke testsjust now').click();

const curl = 'curl --request POST --url http://mockbin.org/status/200';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ export const WorkspaceCardDropdown: FC<Props> = props => {
)}
{isExportModalOpen && (
<ExportRequestsModal
workspace={workspace}
onHide={() => setIsExportModalOpen(false)}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ export const WorkspaceDropdown: FC = () => {
)}
{isExportModalOpen && (
<ExportRequestsModal
workspace={activeWorkspace}
onHide={() => setIsExportModalOpen(false)}
/>
)}
Expand Down
189 changes: 75 additions & 114 deletions packages/insomnia/src/ui/components/modals/export-requests-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { OverlayContainer } from 'react-aria';
import { useRouteLoaderData } from 'react-router-dom';
import { useFetcher, useParams } from 'react-router-dom';

import { exportRequestsToFile } from '../../../common/export';
import * as models from '../../../models';
import { GrpcRequest, isGrpcRequest } from '../../../models/grpc-request';
import { isRequest, Request } from '../../../models/request';
import { isRequestGroup, RequestGroup } from '../../../models/request-group';
import { RequestGroup } from '../../../models/request-group';
import { isWebSocketRequest, WebSocketRequest } from '../../../models/websocket-request';
import { WorkspaceLoaderData } from '../../routes/workspace';
import { Workspace } from '../../../models/workspace';
import { Child, WorkspaceLoaderData } from '../../routes/workspace';
import { Modal, type ModalHandle, ModalProps } from '../base/modal';
import { ModalBody } from '../base/modal-body';
import { ModalFooter } from '../base/modal-footer';
Expand All @@ -26,59 +27,56 @@ export interface Node {
export interface State {
treeRoot: Node | null;
}
export interface ExportRequestsModalHandle {
show: () => void;
hide: () => void;
}

export const ExportRequestsModal = ({ onHide }: ModalProps) => {
export const ExportRequestsModal = ({ workspace, onHide }: { workspace: Workspace } & ModalProps) => {
const modalRef = useRef<ModalHandle>(null);
const workspaceData = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
const requestTree = workspaceData?.requestTree || [];
const { organizationId, projectId } = useParams() as { organizationId: string; projectId: string };
const workspaceFetcher = useFetcher();
const [state, setState] = useState<State>();

const createNode = useCallback((item: Record<string, any>): Node => {
const children: Node[] = item.children.map((child: Record<string, any>) => createNode(child));
let totalRequests = children
.map(child => child.totalRequests)
.reduce((acc, totalRequests) => acc + totalRequests, 0);
const docIsRequest = isRequest(item.doc) || isWebSocketRequest(item.doc) || isGrpcRequest(item.doc);
if (docIsRequest) {
totalRequests++;
useEffect(() => {
const isIdleAndUninitialized = workspaceFetcher.state === 'idle' && !workspaceFetcher.data;
if (isIdleAndUninitialized) {
workspaceFetcher.load(`/organization/${organizationId}/project/${projectId}/workspace/${workspace._id}`);
}
return {
doc: item.doc,
collapsed: false,
children: children,
totalRequests: totalRequests,
selectedRequests: totalRequests, // Default select all
};
}, []);
}, [organizationId, projectId, workspaceFetcher, workspace._id]);
const workspaceLoaderData = workspaceFetcher?.data as WorkspaceLoaderData;

const children: Node[] = requestTree.map(child => createNode(child));
const totalRequests = children
.map(child => child.totalRequests)
.reduce((acc, totalRequests) => acc + totalRequests, 0);

// @ts-expect-error -- TSCONVERSION missing property
const rootFolder: RequestGroup = {
...models.requestGroup.init(),
_id: 'all',
type: models.requestGroup.type,
name: 'All requests',
parentId: '',
modified: 0,
created: 0,
};
useEffect(() => {
const createTreeNode = (child: Child): Node => {
const docIsRequest = isRequest(child.doc) || isWebSocketRequest(child.doc) || isGrpcRequest(child.doc);
const children = child.children.map((child: Child) => createTreeNode(child));
const totalRequests = +docIsRequest + children.reduce((acc, { totalRequests }) => acc + totalRequests, 0);
return {
doc: child.doc,
collapsed: false,
children,
totalRequests: totalRequests,
selectedRequests: totalRequests, // Default select all
};
};
const requestTree = workspaceLoaderData?.requestTree || [];
const children: Node[] = requestTree.map(child => createTreeNode(child));
setState({
treeRoot: {
doc: {
...models.requestGroup.init(),
_id: 'all',
type: models.requestGroup.type,
name: 'All requests',
parentId: '',
modified: 0,
created: 0,
isPrivate: false,
},
collapsed: false,
children: children,
totalRequests: children.map(child => child.totalRequests).reduce((acc, totalRequests) => acc + totalRequests, 0),
selectedRequests: children.map(child => child.totalRequests).reduce((acc, totalRequests) => acc + totalRequests, 0), // Default select all
},
});
}, [workspaceLoaderData?.requestTree]);

const [state, setState] = useState<State>({
treeRoot: {
doc: rootFolder,
collapsed: false,
children: children,
totalRequests: totalRequests,
selectedRequests: totalRequests, // Default select all
},
});
useEffect(() => {
modalRef.current?.show();
}, []);
Expand All @@ -87,95 +85,53 @@ export const ExportRequestsModal = ({ onHide }: ModalProps) => {
if (docIsRequest && node.selectedRequests === node.totalRequests) {
return [node.doc._id];
}
const requestIds: string[] = [];
for (const child of node.children) {
const reqIds = getSelectedRequestIds(child);
requestIds.push(...reqIds);
}
return requestIds;
return node.children.map(child => getSelectedRequestIds(child)).reduce((acc, reqIds) => [...acc, ...reqIds], []);
};

const setItemSelected = (node: Node, isSelected: boolean, id?: string) => {
if (id == null || node.doc._id === id) {
if (id === null || node.doc._id === id) {
// Switch the flags of all children in this subtree.
for (const child of node.children) {
setItemSelected(child, isSelected);
}
node.children.forEach(child => setItemSelected(child, isSelected));
node.selectedRequests = isSelected ? node.totalRequests : 0;
return true;
}
for (const child of node.children) {
const found = setItemSelected(child, isSelected, id);
if (found) {
node.selectedRequests = node.children
.map(ch => ch.selectedRequests)
.reduce((acc, selected) => acc + selected, 0);
node.selectedRequests = node.children.map(ch => ch.selectedRequests).reduce((acc, selected) => acc + selected, 0);
return true;
}
}
return false;
};
const handleSetItemSelected = (itemId: string, isSelected: boolean) => {
const { treeRoot } = state;
if (treeRoot == null) {
return;
}
const found = setItemSelected(treeRoot, isSelected, itemId);
if (!found) {
return;
}
setState({ treeRoot: { ...treeRoot } });
};
const handleSetRequestGroupCollapsed = (requestGroupId: string, isCollapsed: boolean) => {
const { treeRoot } = state;
if (treeRoot == null) {
return;
}
const found = setRequestGroupCollapsed(treeRoot, isCollapsed, requestGroupId);
if (!found) {
return;
}
setState({ treeRoot: { ...treeRoot } });
};
const setRequestGroupCollapsed = (node: Node, isCollapsed: boolean, requestGroupId: string) => {
if (!isRequestGroup(node.doc)) {
return false;
}

const setRequestGroupCollapsed = (node: Node, isCollapsed: boolean, requestGroupId: string): boolean => {
if (node.doc._id === requestGroupId) {
node.collapsed = isCollapsed;
return true;
}
for (const child of node.children) {
const found = setRequestGroupCollapsed(child, isCollapsed, requestGroupId);
if (found) {
return true;
}
}
return false;
};
const handleExport = () => {
const { treeRoot } = state;
if (treeRoot == null || treeRoot.selectedRequests === 0) {
return;
}
const exportedRequestIds = getSelectedRequestIds(treeRoot);

exportRequestsToFile(exportedRequestIds);
modalRef.current?.hide();
return !!node.children.find(child => setRequestGroupCollapsed(child, isCollapsed, requestGroupId));
};

const { treeRoot } = state;
const isExportDisabled = treeRoot != null ? treeRoot.selectedRequests === 0 : false;
const isExportDisabled = state?.treeRoot?.selectedRequests === 0 || false;
return (
<OverlayContainer>
<OverlayContainer onClick={e => e.stopPropagation()}>
<Modal ref={modalRef} tall onHide={onHide}>
<ModalHeader>Select Requests to Export</ModalHeader>
<ModalBody>
<div className="requests-tree">
<Tree
root={treeRoot}
handleSetRequestGroupCollapsed={handleSetRequestGroupCollapsed}
handleSetItemSelected={handleSetItemSelected}
root={state?.treeRoot}
handleSetRequestGroupCollapsed={(requestGroupId: string, isCollapsed: boolean) => {
if (state?.treeRoot && setRequestGroupCollapsed(state?.treeRoot, isCollapsed, requestGroupId)) {
setState({ treeRoot: state?.treeRoot });
}
}}
handleSetItemSelected={(itemId: string, isSelected: boolean) => {
if (state?.treeRoot && setItemSelected(state?.treeRoot, isSelected, itemId)) {
setState({ treeRoot: state?.treeRoot });
}
}}
/>
</div>
</ModalBody>
Expand All @@ -186,7 +142,12 @@ export const ExportRequestsModal = ({ onHide }: ModalProps) => {
</button>
<button
className="btn"
onClick={handleExport}
onClick={() => {
if (state?.treeRoot && state?.treeRoot.selectedRequests > 0) {
exportRequestsToFile(getSelectedRequestIds(state?.treeRoot));
modalRef.current?.hide();
}
}}
disabled={isExportDisabled}
>
Export
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,9 @@ export const ImportExport: FC<Props> = ({ hideSettingsModal }) => {
defaultWorkspaceId={workspaceId}
/>
)}
{isExportModalOpen && (
{isExportModalOpen && workspaceData?.activeWorkspace && (
<ExportRequestsModal
workspace={workspaceData.activeWorkspace}
onHide={() => setIsExportModalOpen(false)}
/>
)}
Expand Down

0 comments on commit 8fa8b9b

Please sign in to comment.