Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mock feature feedback #7089

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/insomnia/src/models/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,10 @@ export function getById(id: string): Promise<Request | null> {
return db.get(type, id);
}

export function getByParentId(parentId: string) {
return db.getWhere<Request>(type, { parentId: parentId });
}

export function findByParentId(parentId: string) {
return db.find<Request>(type, { parentId: parentId });
}
Expand Down
154 changes: 151 additions & 3 deletions packages/insomnia/src/ui/components/mocks/mock-response-pane.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import fs from 'fs';
import * as Har from 'har-format';
import React, { Fragment, useCallback, useEffect, useState } from 'react';
import { useRouteLoaderData } from 'react-router-dom';
import { useInterval } from 'react-use';

import { getCurrentSessionId } from '../../../account/session';
import { getMockServiceURL, PREVIEW_MODE_SOURCE } from '../../../common/constants';
import { getMockServiceURL, getPreviewModeName, PREVIEW_MODE_FRIENDLY, PREVIEW_MODES, PreviewMode } from '../../../common/constants';
import { exportHarCurrentRequest } from '../../../common/har';
import { ResponseTimelineEntry } from '../../../main/network/libcurl-promise';
import * as models from '../../../models';
import { MockRoute } from '../../../models/mock-route';
import { MockServer } from '../../../models/mock-server';
import { Response } from '../../../models/response';
import { jsonPrettify } from '../../../utils/prettify/json';
import { MockRouteLoaderData } from '../../routes/mock-route';
import { useRootLoaderData } from '../../routes/root';
import { Dropdown, DropdownButton, DropdownItem, DropdownSection, ItemContent } from '../base/dropdown';
import { TabItem, Tabs } from '../base/tabs';
import { CodeEditor } from '../codemirror/code-editor';
import { getTimeFromNow } from '../time-from-now';
Expand Down Expand Up @@ -39,6 +44,7 @@ export const MockResponsePane = () => {
const { mockServer, mockRoute, activeResponse } = useRouteLoaderData(':mockRouteId') as MockRouteLoaderData;
const { settings } = useRootLoaderData();
const [timeline, setTimeline] = useState<ResponseTimelineEntry[]>([]);
const [previewMode, setPreviewMode] = useState<PreviewMode>(PREVIEW_MODE_FRIENDLY);

useEffect(() => {
const fn = async () => {
Expand All @@ -52,7 +58,16 @@ export const MockResponsePane = () => {

return (
<Tabs aria-label="Mock response">
<TabItem key="preview" title="Preview">
<TabItem
key="preview"
title={activeResponse ?
<PreviewModeDropdown
activeResponse={activeResponse}
previewMode={previewMode}
setPreviewMode={setPreviewMode}
/> :
'Preview'}
>
{activeResponse && <ResponseViewer
key={activeResponse._id}
bytes={Math.max(activeResponse.bytesContent, activeResponse.bytesRead)}
Expand All @@ -65,7 +80,7 @@ export const MockResponsePane = () => {
filter={''}
filterHistory={[]}
getBody={() => models.response.getBodyBuffer(activeResponse)}
previewMode={PREVIEW_MODE_SOURCE}
previewMode={previewMode}
responseId={activeResponse._id}
updateFilter={activeResponse.error ? undefined : () => { }}
url={activeResponse.url}
Expand Down Expand Up @@ -166,3 +181,136 @@ const HistoryViewWrapperComponentFactory = ({ mockServer, mockRoute }: { mockSer
</div>
);
};

const PreviewModeDropdown = ({ activeResponse, previewMode, setPreviewMode }: { activeResponse: Response; previewMode: PreviewMode; setPreviewMode: (mode: PreviewMode) => void }) => {
return (
<Dropdown
aria-label='Preview Mode Dropdown'
triggerButton={
<DropdownButton className="tall !text-[--hl]">
{getPreviewModeName(previewMode)}
<i className="fa fa-caret-down space-left" />
</DropdownButton>
}
>
<DropdownSection
aria-label='Preview Mode Section'
title="Preview Mode"
>
{PREVIEW_MODES.map(mode =>
<DropdownItem
key={mode}
aria-label={getPreviewModeName(mode, true)}
>
<ItemContent
icon={previewMode === mode ? 'check' : 'empty'}
label={getPreviewModeName(mode, true)}
onClick={() => setPreviewMode(mode)}
/>
</DropdownItem>
)}
</DropdownSection>
<DropdownSection
aria-label='Action Section'
title="Action"
>
<DropdownItem aria-label='Copy raw response'>
<ItemContent
icon="copy"
label="Copy raw response"
onClick={async () => {
const bodyBuffer = await models.response.getBodyBuffer(activeResponse);
bodyBuffer && window.clipboard.writeText(bodyBuffer.toString('utf8'));
}}
/>
</DropdownItem>
<DropdownItem aria-label='Export raw response'>
<ItemContent
icon="save"
label="Export raw response"
onClick={async () => {
const bodyBuffer = await models.response.getBodyBuffer(activeResponse);
const { canceled, filePath } = await window.dialog.showSaveDialog({
title: 'Save Full Response',
buttonLabel: 'Save',
defaultPath: `response-${Date.now()}.txt`,
});

if (canceled || !filePath || !bodyBuffer) {
return;
}
fs.promises.writeFile(filePath, bodyBuffer.toString('utf8'));
}}
/>
</DropdownItem>
<DropdownItem aria-label='Export prettified response'>
{activeResponse.contentType.includes('json') &&
<ItemContent
icon="save"
label="Export prettified response"
onClick={async () => {
const bodyBuffer = await models.response.getBodyBuffer(activeResponse);
const { canceled, filePath } = await window.dialog.showSaveDialog({
title: 'Save Full Response',
buttonLabel: 'Save',
defaultPath: `response-${Date.now()}.txt`,
});

if (canceled || !filePath || !bodyBuffer) {
return;
}
fs.promises.writeFile(filePath, jsonPrettify(bodyBuffer.toString('utf8')));
}}
/>
}
</DropdownItem>
<DropdownItem aria-label='Export HTTP debug'>
<ItemContent
icon="bug"
label="Export HTTP debug"
onClick={async () => {
const { canceled, filePath } = await window.dialog.showSaveDialog({
title: 'Save Full Response',
buttonLabel: 'Save',
defaultPath: `response-${Date.now()}.txt`,
});

if (canceled || !filePath) {
return;
}
const timeline = models.response.getTimeline(activeResponse);
const headers = timeline
.filter(v => v.name === 'HeaderIn')
.map(v => v.value)
.join('');

fs.promises.writeFile(filePath, headers);
}}
/>
</DropdownItem>
<DropdownItem aria-label='Export as HAR'>
<ItemContent
icon="save"
label="Export as HAR"
onClick={async () => {
const activeRequest = await models.request.getById(activeResponse.parentId);
const { canceled, filePath } = await window.dialog.showSaveDialog({
title: 'Save Full Response',
buttonLabel: 'Save',
defaultPath: `response-${Date.now()}.txt`,
});

if (canceled || !filePath || !activeRequest) {
return;
}
const data = await exportHarCurrentRequest(activeRequest, activeResponse);
const har = JSON.stringify(data, null, '\t');

fs.promises.writeFile(filePath, har);
}}
/>
</DropdownItem>
</DropdownSection>
</Dropdown>
);
};
141 changes: 135 additions & 6 deletions packages/insomnia/src/ui/components/mocks/mock-url-bar.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,68 @@
import React, { useState } from 'react';
import React, { useRef, useState } from 'react';
import { Button } from 'react-aria-components';
import { useRouteLoaderData } from 'react-router-dom';
import { useInterval } from 'react-use';
import styled from 'styled-components';

import { getMockServiceURL, HTTP_METHODS } from '../../../common/constants';
import * as models from '../../../models';
import { useTimeoutWhen } from '../../hooks/useTimeoutWhen';
import { MockRouteLoaderData, useMockRoutePatcher } from '../../routes/mock-route';
import { Dropdown, DropdownButton, DropdownItem, ItemContent } from '../base/dropdown';
import { useRootLoaderData } from '../../routes/root';
import { Dropdown, DropdownButton, DropdownHandle, DropdownItem, DropdownSection, ItemContent } from '../base/dropdown';
import { OneLineEditorHandle } from '../codemirror/one-line-editor';
import { Icon } from '../icon';
import { showModal } from '../modals';
import { useDocBodyKeyboardShortcuts } from '../keydown-binder';
import { showModal, showPrompt } from '../modals';
import { AlertModal } from '../modals/alert-modal';
import { GenerateCodeModal } from '../modals/generate-code-modal';
const StyledDropdownButton = styled(DropdownButton)({
'&:hover:not(:disabled)': {
backgroundColor: 'var(--color-surprise)',
},

'&:focus:not(:disabled)': {
backgroundColor: 'var(--color-surprise)',
},
});
export const MockUrlBar = ({ onPathUpdate, onSend }: { onPathUpdate: (path: string) => void; onSend: (path: string) => void }) => {
const { mockServer, mockRoute } = useRouteLoaderData(':mockRouteId') as MockRouteLoaderData;
const { settings } = useRootLoaderData();
const { hotKeyRegistry } = settings;
const patchMockRoute = useMockRoutePatcher();
const [pathInput, setPathInput] = useState<string>(mockRoute.name);
const mockbinUrl = mockServer.useInsomniaCloud ? getMockServiceURL() : mockServer.url;
const methodDropdownRef = useRef<DropdownHandle>(null);
const dropdownRef = useRef<DropdownHandle>(null);
const inputRef = useRef<OneLineEditorHandle>(null);
const [currentInterval, setCurrentInterval] = useState<number | null>(null);
const [currentTimeout, setCurrentTimeout] = useState<number | undefined>(undefined);
const send = () => {
setCurrentTimeout(undefined);
onSend(pathInput);
};
useInterval(send, currentInterval ? currentInterval : null);
useTimeoutWhen(send, currentTimeout, !!currentTimeout);
useDocBodyKeyboardShortcuts({
request_focusUrl: () => {
inputRef.current?.selectAll();
},
request_send: () => {
if (mockRoute.name) {
send();
}
},
request_toggleHttpMethodMenu: () => {
methodDropdownRef.current?.toggle();
},
request_showOptions: () => {
dropdownRef.current?.toggle(true);
},
});
const isCancellable = currentInterval || currentTimeout;
return (<div className='w-full flex justify-between urlbar'>
<Dropdown
ref={methodDropdownRef}
className="method-dropdown"
triggerButton={
<DropdownButton className="pad-right pad-left vertically-center hover:bg-[--color-surprise] focus:bg-[--color-surprise]">
Expand Down Expand Up @@ -62,11 +109,93 @@ export const MockUrlBar = ({ onPathUpdate, onSend }: { onPathUpdate: (path: stri
<Icon icon="copy" />
</Button>
<Button
className="px-5 ml-1 text-[--color-font-surprise] bg-[--color-surprise] hover:bg-opacity-90 rounded-sm"
onPress={() => onSend(pathInput)}
className="px-5 ml-1 text-[--color-font-surprise] bg-[--color-surprise] hover:bg-opacity-90 rounded-l-sm"
onPress={() => {
if (isCancellable) {
setCurrentInterval(null);
setCurrentTimeout(undefined);
return;
}
onSend(pathInput);
}}
>
Test
{isCancellable ? 'Stop' : 'Test'}
</Button>
<Dropdown
key="dropdown"
className="tall"
ref={dropdownRef}
aria-label="Request Options"
closeOnSelect={false}
triggerButton={
<StyledDropdownButton
className="urlbar__send-context rounded-r-sm"
style={{
borderTopRightRadius: '0.125rem',
borderBottomRightRadius: '0.125rem',
}}
removeBorderRadius={true}
>
<i className="fa fa-caret-down" />
</StyledDropdownButton>
}
>
<DropdownSection
aria-label="Basic Section"
title="Basic"
>
<DropdownItem aria-label="send-now">
<ItemContent icon="arrow-circle-o-right" label="Send Now" hint={hotKeyRegistry.request_send} onClick={send} />
</DropdownItem>
<DropdownItem aria-label='Generate Client Code'>
<ItemContent
icon="code"
label="Generate Client Code"
onClick={async () => {
const request = await models.request.getByParentId(mockRoute._id);
request && showModal(GenerateCodeModal, { request });
}}
/>
</DropdownItem>
</DropdownSection>
<DropdownSection
aria-label="Advanced Section"
title="Advanced"
>
<DropdownItem aria-label='Send After Delay'>
<ItemContent
icon="clock-o"
label="Send After Delay"
onClick={() => showPrompt({
inputType: 'decimal',
title: 'Send After Delay',
label: 'Delay in seconds',
defaultValue: '3',
onComplete: seconds => {
setCurrentTimeout(+seconds * 1000);
},
})}
/>
</DropdownItem>
<DropdownItem aria-label='Repeat on Interval'>
<ItemContent
icon="repeat"
label="Repeat on Interval"
onClick={() => showPrompt({
inputType: 'decimal',
title: 'Send on Interval',
label: 'Interval in seconds',
defaultValue: '3',
submitName: 'Start',
onComplete: seconds => {
setCurrentInterval(+seconds * 1000);
},
})}
/>
</DropdownItem>
</DropdownSection>

</Dropdown>
</div>
</div>);
};
Loading
Loading