mirror of
https://github.com/Kong/insomnia
synced 2024-11-07 22:30:15 +00:00
Add auto-download, fix curl pasting and code gen
This commit is contained in:
parent
883866a76a
commit
065a31a9a2
@ -167,7 +167,7 @@ describe('actuallySend()', () => {
|
||||
);
|
||||
|
||||
expect(mock.basePath).toBe('http://localhost:80');
|
||||
expect(response.error).toBe('');
|
||||
expect(response.error).toBe(undefined);
|
||||
expect(response.url).toBe('http://localhost/?foo%20bar=hello%26world');
|
||||
expect(response.body).toBe(new Buffer('response body').toString('base64'));
|
||||
expect(response.statusCode).toBe(200);
|
||||
@ -208,7 +208,7 @@ describe('actuallySend()', () => {
|
||||
);
|
||||
|
||||
expect(mock.basePath).toBe('http://localhost:80');
|
||||
expect(response.error).toBe('');
|
||||
expect(response.error).toBe(undefined);
|
||||
expect(response.url).toBe('http://localhost/');
|
||||
expect(response.body).toBe(new Buffer('response body').toString('base64'));
|
||||
expect(response.statusCode).toBe(200);
|
||||
@ -260,7 +260,7 @@ describe('actuallySend()', () => {
|
||||
);
|
||||
|
||||
expect(mock.basePath).toBe('http://localhost:80');
|
||||
expect(response.error).toBe('');
|
||||
expect(response.error).toBe(undefined);
|
||||
expect(response.url).toBe('http://localhost/');
|
||||
expect(response.body).toBe(new Buffer('response body').toString('base64'));
|
||||
expect(response.statusCode).toBe(200);
|
||||
|
@ -118,17 +118,15 @@ export function _buildRequestConfig (renderedRequest, patch = {}) {
|
||||
}
|
||||
|
||||
export function _actuallySend (renderedRequest, workspace, settings, familyIndex = 0) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
return new Promise(async resolve => {
|
||||
async function handleError (err, prefix = '') {
|
||||
await models.response.create({
|
||||
resolve({
|
||||
url: renderedRequest.url,
|
||||
parentId: renderedRequest._id,
|
||||
elapsedTime: 0,
|
||||
statusMessage: 'Error',
|
||||
error: prefix ? `${prefix}: ${err.message}` : err.message
|
||||
});
|
||||
|
||||
reject(err);
|
||||
}
|
||||
|
||||
// Detect and set the proxy based on the request protocol
|
||||
@ -215,10 +213,10 @@ export function _actuallySend (renderedRequest, workspace, settings, familyIndex
|
||||
if (isNetworkRelatedError && nextFamilyIndex < FAMILY_FALLBACKS.length) {
|
||||
const family = FAMILY_FALLBACKS[nextFamilyIndex];
|
||||
console.log(`-- Falling back to family ${family} --`);
|
||||
_actuallySend(
|
||||
|
||||
return _actuallySend(
|
||||
renderedRequest, workspace, settings, nextFamilyIndex
|
||||
).then(resolve, reject);
|
||||
return;
|
||||
}
|
||||
|
||||
let message = err.toString();
|
||||
@ -227,14 +225,12 @@ export function _actuallySend (renderedRequest, workspace, settings, familyIndex
|
||||
message += `Code: ${err.code}`;
|
||||
}
|
||||
|
||||
await models.response.create({
|
||||
return resolve({
|
||||
url: config.url,
|
||||
parentId: renderedRequest._id,
|
||||
statusMessage: 'Error',
|
||||
error: message
|
||||
});
|
||||
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
// handle response headers
|
||||
@ -272,7 +268,7 @@ export function _actuallySend (renderedRequest, workspace, settings, familyIndex
|
||||
}
|
||||
|
||||
const bodyEncoding = 'base64';
|
||||
const responsePatch = {
|
||||
return resolve({
|
||||
parentId: renderedRequest._id,
|
||||
statusCode: networkResponse.statusCode,
|
||||
statusMessage: networkResponse.statusMessage,
|
||||
@ -283,9 +279,7 @@ export function _actuallySend (renderedRequest, workspace, settings, familyIndex
|
||||
body: networkResponse.body.toString(bodyEncoding),
|
||||
encoding: bodyEncoding,
|
||||
headers: headers
|
||||
};
|
||||
|
||||
models.response.create(responsePatch).then(resolve, reject);
|
||||
});
|
||||
};
|
||||
|
||||
const requestStartTime = Date.now();
|
||||
@ -295,15 +289,13 @@ export function _actuallySend (renderedRequest, workspace, settings, familyIndex
|
||||
cancelRequestFunction = async () => {
|
||||
req.abort();
|
||||
|
||||
await models.response.create({
|
||||
resolve({
|
||||
url: config.url,
|
||||
parentId: renderedRequest._id,
|
||||
elapsedTime: Date.now() - requestStartTime,
|
||||
statusMessage: 'Cancelled',
|
||||
error: 'The request was cancelled'
|
||||
});
|
||||
|
||||
return reject(new Error('Cancelled'));
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -321,7 +313,7 @@ export async function send (requestId, environmentId) {
|
||||
renderedRequest = await getRenderedRequest(request, environmentId);
|
||||
} catch (e) {
|
||||
// Failed to render. Must be the user's fault
|
||||
return await models.response.create({
|
||||
return resolve({
|
||||
parentId: request._id,
|
||||
statusCode: STATUS_CODE_RENDER_FAILED,
|
||||
error: e.message
|
||||
@ -333,5 +325,5 @@ export async function send (requestId, environmentId) {
|
||||
const workspace = ancestors.find(doc => doc.type === models.workspace.type);
|
||||
|
||||
// Render succeeded so we're good to go!
|
||||
return await _actuallySend(renderedRequest, workspace, settings);
|
||||
return _actuallySend(renderedRequest, workspace, settings);
|
||||
}
|
||||
|
@ -71,6 +71,7 @@ class RequestPane extends PureComponent {
|
||||
editorKeyMap,
|
||||
editorLineWrapping,
|
||||
handleSend,
|
||||
handleSendAndDownload,
|
||||
forceRefreshCounter,
|
||||
useBulkHeaderEditor,
|
||||
handleGenerateCode,
|
||||
@ -152,6 +153,7 @@ class RequestPane extends PureComponent {
|
||||
onUrlPaste={importRequest}
|
||||
handleGenerateCode={handleGenerateCode}
|
||||
handleSend={handleSend}
|
||||
handleSendAndDownload={handleSendAndDownload}
|
||||
url={request.url}
|
||||
/>
|
||||
</header>
|
||||
@ -279,6 +281,7 @@ class RequestPane extends PureComponent {
|
||||
RequestPane.propTypes = {
|
||||
// Functions
|
||||
handleSend: PropTypes.func.isRequired,
|
||||
handleSendAndDownload: PropTypes.func.isRequired,
|
||||
handleCreateRequest: PropTypes.func.isRequired,
|
||||
handleGenerateCode: PropTypes.func.isRequired,
|
||||
updateRequest: PropTypes.func.isRequired,
|
||||
|
@ -1,23 +1,28 @@
|
||||
import React, {Component, PropTypes} from 'react';
|
||||
import classnames from 'classnames';
|
||||
import {remote} from 'electron';
|
||||
import {DEBOUNCE_MILLIS, isMac} from '../../common/constants';
|
||||
import {Dropdown, DropdownButton, DropdownItem, DropdownDivider, DropdownHint} from './base/dropdown';
|
||||
import {trackEvent} from '../../analytics';
|
||||
import MethodDropdown from './dropdowns/MethodDropdown';
|
||||
import PromptModal from './modals/PromptModal';
|
||||
import {showModal} from './modals/index';
|
||||
import PromptButton from './base/PromptButton';
|
||||
|
||||
|
||||
class RequestUrlBar extends Component {
|
||||
state = {
|
||||
currentInterval: null,
|
||||
currentTimeout: null,
|
||||
downloadPath: null
|
||||
};
|
||||
|
||||
_urlChangeDebounceTimeout = null;
|
||||
|
||||
_handleFormSubmit = e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.props.handleSend();
|
||||
|
||||
this._handleSend();
|
||||
};
|
||||
|
||||
_handleMethodChange = method => {
|
||||
@ -28,15 +33,28 @@ class RequestUrlBar extends Component {
|
||||
_handleUrlChange = e => {
|
||||
const url = e.target.value;
|
||||
|
||||
clearTimeout(this._timeout);
|
||||
this._timeout = setTimeout(() => {
|
||||
clearTimeout(this._urlChangeDebounceTimeout);
|
||||
this._urlChangeDebounceTimeout = setTimeout(() => {
|
||||
this.props.onUrlChange(url);
|
||||
}, DEBOUNCE_MILLIS);
|
||||
};
|
||||
|
||||
_handleUrlPaste = e => {
|
||||
/*
|
||||
* Prevent the change handler from being called. Note that this is in a timeout
|
||||
* because we want it to happen after the onChange callback. If it happens before,
|
||||
* then the change will overwrite anything that we do.
|
||||
*
|
||||
* Also, note that there is still a potential race condition here if, for some reason,
|
||||
* the onChange callback is not called before DEBOUNCE_MILLIS is over. This is extremely
|
||||
* unlikely since it should happen in the same tick.
|
||||
*/
|
||||
const text = e.clipboardData.getData('text/plain');
|
||||
this.props.onUrlPaste(text);
|
||||
setTimeout(() => {
|
||||
// Clear any update timeouts that may have happened since we started waiting
|
||||
clearTimeout(this._urlChangeDebounceTimeout);
|
||||
this.props.onUrlPaste(text);
|
||||
}, DEBOUNCE_MILLIS);
|
||||
};
|
||||
|
||||
_handleGenerateCode = () => {
|
||||
@ -44,6 +62,27 @@ class RequestUrlBar extends Component {
|
||||
trackEvent('Request', 'Generate Code', 'Send Action');
|
||||
};
|
||||
|
||||
_handleSetDownloadLocation = () => {
|
||||
const options = {
|
||||
title: 'Select Download Location',
|
||||
buttonLabel: 'Select',
|
||||
properties: ['openDirectory'],
|
||||
};
|
||||
|
||||
remote.dialog.showOpenDialog(options, paths => {
|
||||
if (!paths || paths.length === 0) {
|
||||
trackEvent('Response', 'Download Select Cancel');
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({downloadPath: paths[0]});
|
||||
});
|
||||
};
|
||||
|
||||
_handleClearDownloadLocation = () => {
|
||||
this.setState({downloadPath: null});
|
||||
};
|
||||
|
||||
_handleKeyDown = e => {
|
||||
if (!this._input) {
|
||||
return;
|
||||
@ -63,7 +102,13 @@ class RequestUrlBar extends Component {
|
||||
// XXX this._handleStopInterval(); XXX
|
||||
|
||||
this._handleStopTimeout();
|
||||
this.props.handleSend();
|
||||
|
||||
const {downloadPath} = this.state;
|
||||
if (downloadPath) {
|
||||
this.props.handleSendAndDownload(downloadPath);
|
||||
} else {
|
||||
this.props.handleSend();
|
||||
}
|
||||
};
|
||||
|
||||
_handleSendAfterDelay = async () => {
|
||||
@ -130,16 +175,14 @@ class RequestUrlBar extends Component {
|
||||
|
||||
componentDidMount () {
|
||||
document.body.addEventListener('keydown', this._handleKeyDown);
|
||||
document.body.addEventListener('keyup', this._handleKeyUp);
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
document.body.removeEventListener('keydown', this._handleKeyDown);
|
||||
document.body.removeEventListener('keyup', this._handleKeyUp);
|
||||
}
|
||||
|
||||
renderSendButton () {
|
||||
const {currentInterval, currentTimeout} = this.state;
|
||||
const {currentInterval, currentTimeout, downloadPath} = this.state;
|
||||
|
||||
let cancelButton = null;
|
||||
if (currentInterval) {
|
||||
@ -169,7 +212,7 @@ class RequestUrlBar extends Component {
|
||||
<DropdownButton className="urlbar__send-btn"
|
||||
onClick={this._handleClickSend}
|
||||
type="submit">
|
||||
Send
|
||||
{downloadPath ? "Download" : "Send"}
|
||||
</DropdownButton>
|
||||
<DropdownDivider>Basic</DropdownDivider>
|
||||
<DropdownItem type="submit">
|
||||
@ -186,6 +229,18 @@ class RequestUrlBar extends Component {
|
||||
<DropdownItem onClick={this._handleSendOnInterval}>
|
||||
<i className="fa fa-repeat"/> Repeat on Interval
|
||||
</DropdownItem>
|
||||
{downloadPath ? (
|
||||
<DropdownItem stayOpenAfterClick={true}
|
||||
buttonClass={PromptButton}
|
||||
addIcon={true}
|
||||
onClick={this._handleClearDownloadLocation}>
|
||||
<i className="fa fa-stop-circle"/> Stop Auto-Download
|
||||
</DropdownItem>
|
||||
) : (
|
||||
<DropdownItem onClick={this._handleSetDownloadLocation}>
|
||||
<i className="fa fa-download"/> Download After Send
|
||||
</DropdownItem>
|
||||
)}
|
||||
</Dropdown>
|
||||
)
|
||||
}
|
||||
@ -220,6 +275,7 @@ class RequestUrlBar extends Component {
|
||||
|
||||
RequestUrlBar.propTypes = {
|
||||
handleSend: PropTypes.func.isRequired,
|
||||
handleSendAndDownload: PropTypes.func.isRequired,
|
||||
onUrlChange: PropTypes.func.isRequired,
|
||||
onUrlPaste: PropTypes.func.isRequired,
|
||||
onMethodChange: PropTypes.func.isRequired,
|
||||
|
@ -119,6 +119,13 @@ class Wrapper extends Component {
|
||||
handleSendRequestWithEnvironment(activeRequestId, activeEnvironmentId);
|
||||
};
|
||||
|
||||
_handleSendAndDownloadRequestWithActiveEnvironment = filename => {
|
||||
const {activeRequest, activeEnvironment, handleSendAndDownloadRequestWithEnvironment} = this.props;
|
||||
const activeRequestId = activeRequest ? activeRequest._id : 'n/a';
|
||||
const activeEnvironmentId = activeEnvironment ? activeEnvironment._id : 'n/a';
|
||||
handleSendAndDownloadRequestWithEnvironment(activeRequestId, activeEnvironmentId, filename);
|
||||
};
|
||||
|
||||
_handleSetPreviewMode = previewMode => {
|
||||
const activeRequest = this.props.activeRequest;
|
||||
const activeRequestId = activeRequest ? activeRequest._id : 'n/a';
|
||||
@ -162,6 +169,7 @@ class Wrapper extends Component {
|
||||
handleStartDragPane,
|
||||
handleStartDragSidebar,
|
||||
handleSetSidebarFilter,
|
||||
handleGenerateCodeForActiveRequest,
|
||||
handleGenerateCode,
|
||||
isLoading,
|
||||
loadStartTime,
|
||||
@ -234,7 +242,7 @@ class Wrapper extends Component {
|
||||
environmentId={activeEnvironment ? activeEnvironment._id : 'n/a'}
|
||||
workspace={activeWorkspace}
|
||||
handleCreateRequest={handleCreateRequestForWorkspace}
|
||||
handleGenerateCode={handleGenerateCode}
|
||||
handleGenerateCode={handleGenerateCodeForActiveRequest}
|
||||
updateRequest={this._handleUpdateRequest}
|
||||
updateRequestBody={this._handleUpdateRequestBody}
|
||||
updateRequestUrl={this._handleUpdateRequestUrl}
|
||||
@ -248,6 +256,7 @@ class Wrapper extends Component {
|
||||
updateSettingsUseBulkHeaderEditor={this._handleUpdateSettingsUseBulkHeaderEditor}
|
||||
forceRefreshCounter={this.state.forceRefreshRequestPaneCounter}
|
||||
handleSend={this._handleSendRequestWithActiveEnvironment}
|
||||
handleSendAndDownload={this._handleSendAndDownloadRequestWithActiveEnvironment}
|
||||
/>
|
||||
|
||||
<div className="drag drag--pane">
|
||||
@ -339,6 +348,7 @@ Wrapper.propTypes = {
|
||||
handleDuplicateRequest: PropTypes.func.isRequired,
|
||||
handleDuplicateRequestGroup: PropTypes.func.isRequired,
|
||||
handleCreateRequestGroup: PropTypes.func.isRequired,
|
||||
handleGenerateCodeForActiveRequest: PropTypes.func.isRequired,
|
||||
handleGenerateCode: PropTypes.func.isRequired,
|
||||
handleCreateRequestForWorkspace: PropTypes.func.isRequired,
|
||||
handleSetRequestPaneRef: PropTypes.func.isRequired,
|
||||
@ -353,6 +363,7 @@ Wrapper.propTypes = {
|
||||
handleResetDragPane: PropTypes.func.isRequired,
|
||||
handleSetRequestGroupCollapsed: PropTypes.func.isRequired,
|
||||
handleSendRequestWithEnvironment: PropTypes.func.isRequired,
|
||||
handleSendAndDownloadRequestWithEnvironment: PropTypes.func.isRequired,
|
||||
|
||||
// Properties
|
||||
loadStartTime: PropTypes.number.isRequired,
|
||||
|
@ -66,7 +66,7 @@ class PromptButton extends Component {
|
||||
if (state === STATE_ASK && addIcon) {
|
||||
innerMsg = (
|
||||
<span className="danger">
|
||||
<i className="fa fa-exclamation-circle"></i> {CONFIRM_MESSAGE}
|
||||
<i className="fa fa-exclamation-circle"/> {CONFIRM_MESSAGE}
|
||||
</span>
|
||||
)
|
||||
} else if (state === STATE_ASK) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, {Component, PropTypes} from 'react';
|
||||
import fs from 'fs';
|
||||
import {ipcRenderer} from 'electron';
|
||||
import ReactDOM from 'react-dom';
|
||||
import {connect} from 'react-redux';
|
||||
@ -25,6 +26,8 @@ import GenerateCodeModal from '../components/modals/GenerateCodeModal';
|
||||
import WorkspaceSettingsModal from '../components/modals/WorkspaceSettingsModal';
|
||||
import * as network from '../../common/network';
|
||||
import {debounce} from '../../common/misc';
|
||||
import * as mime from 'mime-types';
|
||||
import * as path from 'path';
|
||||
|
||||
const KEY_ENTER = 13;
|
||||
const KEY_COMMA = 188;
|
||||
@ -158,8 +161,12 @@ class App extends Component {
|
||||
this._handleSetActiveRequest(newRequest._id)
|
||||
};
|
||||
|
||||
_handleGenerateCode = () => {
|
||||
showModal(GenerateCodeModal, this.props.activeRequest);
|
||||
_handleGenerateCodeForActiveRequest = () => {
|
||||
this._handleGenerateCode(this.props.activeRequest);
|
||||
};
|
||||
|
||||
_handleGenerateCode = request => {
|
||||
showModal(GenerateCodeModal, request);
|
||||
};
|
||||
|
||||
_updateRequestGroupMetaByParentId = async (requestGroupId, patch) => {
|
||||
@ -233,6 +240,51 @@ class App extends Component {
|
||||
this._updateRequestMetaByParentId(requestId, {responseFilter});
|
||||
};
|
||||
|
||||
_handleSendAndDownloadRequestWithEnvironment = async (requestId, environmentId, dir) => {
|
||||
const request = await models.request.getById(requestId);
|
||||
if (!request) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE: Since request is by far the most popular event, we will throttle
|
||||
// it so that we only track it if the request has changed since the last one
|
||||
const key = request._id;
|
||||
if (this._sendRequestTrackingKey !== key) {
|
||||
trackEvent('Request', 'Send and Download');
|
||||
trackLegacyEvent('Request Send');
|
||||
this._sendRequestTrackingKey = key;
|
||||
}
|
||||
|
||||
// Start loading
|
||||
this.props.handleStartLoading(requestId);
|
||||
|
||||
try {
|
||||
const responsePatch = await network.send(requestId, environmentId);
|
||||
if (responsePatch.statusCode >= 200 && responsePatch.statusCode < 300) {
|
||||
const extension = mime.extension(responsePatch.contentType) || '';
|
||||
const name = request.name.replace(/\s/g, '-').toLowerCase();
|
||||
const filename = path.join(dir, `${name}.${extension}`);
|
||||
const partialResponse = Object.assign({}, responsePatch, {
|
||||
contentType: "text/plain",
|
||||
body: `Saved to ${filename}`,
|
||||
encoding: 'utf8',
|
||||
});
|
||||
await models.response.create(partialResponse);
|
||||
fs.writeFile(filename, responsePatch.body, responsePatch.encoding)
|
||||
} else {
|
||||
await models.response.create(responsePatch);
|
||||
}
|
||||
} catch (e) {
|
||||
// It's OK
|
||||
}
|
||||
|
||||
// Unset active response because we just made a new one
|
||||
await this._updateRequestMetaByParentId(requestId, {activeResponseId: null});
|
||||
|
||||
// Stop loading
|
||||
this.props.handleStopLoading(requestId);
|
||||
};
|
||||
|
||||
_handleSendRequestWithEnvironment = async (requestId, environmentId) => {
|
||||
const request = await models.request.getById(requestId);
|
||||
if (!request) {
|
||||
@ -251,7 +303,8 @@ class App extends Component {
|
||||
this.props.handleStartLoading(requestId);
|
||||
|
||||
try {
|
||||
await network.send(requestId, environmentId);
|
||||
const responsePatch = await network.send(requestId, environmentId);
|
||||
await models.response.create(responsePatch);
|
||||
} catch (e) {
|
||||
// It's OK
|
||||
}
|
||||
@ -450,9 +503,11 @@ class App extends Component {
|
||||
handleDuplicateRequestGroup={this._requestGroupDuplicate}
|
||||
handleCreateRequestGroup={this._requestGroupCreate}
|
||||
handleGenerateCode={this._handleGenerateCode}
|
||||
handleGenerateCodeForActiveRequest={this._handleGenerateCodeForActiveRequest}
|
||||
handleSetResponsePreviewMode={this._handleSetResponsePreviewMode}
|
||||
handleSetResponseFilter={this._handleSetResponseFilter}
|
||||
handleSendRequestWithEnvironment={this._handleSendRequestWithEnvironment}
|
||||
handleSendAndDownloadRequestWithEnvironment={this._handleSendAndDownloadRequestWithEnvironment}
|
||||
handleSetActiveResponse={this._handleSetActiveResponse}
|
||||
handleSetActiveRequest={this._handleSetActiveRequest}
|
||||
handleSetActiveEnvironment={this._handleSetActiveEnvironment}
|
||||
|
Loading…
Reference in New Issue
Block a user