mirror of
https://github.com/Kong/insomnia
synced 2024-11-07 22:30:15 +00:00
Minor UX improvements to HAR export
This commit is contained in:
parent
ccdd0c0a8b
commit
ecfce11d7b
@ -13,6 +13,7 @@ import type {Response as ResponseModel} from '../models/response';
|
||||
import {getAuthHeader} from '../network/authentication';
|
||||
import {getAppVersion} from './constants';
|
||||
import {getSetCookieHeaders} from './misc';
|
||||
import {RenderError} from '../templating/index';
|
||||
|
||||
export type HarCookie = {
|
||||
name: string,
|
||||
@ -264,8 +265,16 @@ export async function exportHarRequest (requestId: string, environmentId: string
|
||||
}
|
||||
|
||||
export async function exportHarWithRequest (request: Request, environmentId: string, addContentLength: boolean = false): Promise<HarRequest | null> {
|
||||
const renderedRequest = await getRenderedRequest(request, environmentId);
|
||||
return await exportHarWithRenderedRequest(renderedRequest, addContentLength);
|
||||
try {
|
||||
const renderedRequest = await getRenderedRequest(request, environmentId);
|
||||
return exportHarWithRenderedRequest(renderedRequest, addContentLength);
|
||||
} catch (err) {
|
||||
if (err instanceof RenderError) {
|
||||
throw new Error(`Failed to render "${request.name}:${err.path}"\n ${err.message}`);
|
||||
} else {
|
||||
throw new Error(`Failed to export request "${request.name}"\n ${err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function exportHarWithRenderedRequest (renderedRequest: RenderedRequest, addContentLength: boolean = false): Promise<HarRequest> {
|
||||
@ -328,8 +337,7 @@ function getReponseCookies (response: ResponseModel): Array<HarCookie> {
|
||||
}
|
||||
|
||||
return mapCookie(cookie);
|
||||
})
|
||||
.filter(Boolean);
|
||||
}).filter(Boolean);
|
||||
}
|
||||
|
||||
function mapCookie (cookie: Cookie): HarCookie {
|
||||
|
@ -20,6 +20,10 @@ class AskModal extends PureComponent {
|
||||
this.modal = m;
|
||||
}
|
||||
|
||||
_setYesButtonRef (n) {
|
||||
this.yesButton = n;
|
||||
}
|
||||
|
||||
_handleYes () {
|
||||
this.hide();
|
||||
this._doneCallback && this._doneCallback(true);
|
||||
@ -49,6 +53,10 @@ class AskModal extends PureComponent {
|
||||
|
||||
this.modal.show();
|
||||
|
||||
setTimeout(() => {
|
||||
this.yesButton && this.yesButton.focus();
|
||||
}, 100);
|
||||
|
||||
return new Promise(resolve => {
|
||||
this._promiseCallback = resolve;
|
||||
});
|
||||
@ -68,7 +76,7 @@ class AskModal extends PureComponent {
|
||||
<button className="btn" onClick={this._handleNo}>
|
||||
No
|
||||
</button>
|
||||
<button className="btn" onClick={this._handleYes}>
|
||||
<button ref={this._setYesButtonRef} className="btn" onClick={this._handleYes}>
|
||||
Yes
|
||||
</button>
|
||||
</div>
|
||||
|
@ -46,7 +46,7 @@ class ErrorModal extends PureComponent {
|
||||
const {error, message, title, addCancel} = this.state;
|
||||
|
||||
return (
|
||||
<Modal ref={this._setModalRef} closeOnKeyCodes={[13]}>
|
||||
<Modal ref={this._setModalRef}>
|
||||
<ModalHeader>{title || 'Uh Oh!'}</ModalHeader>
|
||||
<ModalBody className="wide pad">
|
||||
{message ? (
|
||||
|
101
app/ui/components/modals/select-modal.js
Normal file
101
app/ui/components/modals/select-modal.js
Normal file
@ -0,0 +1,101 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import autobind from 'autobind-decorator';
|
||||
import Modal from '../base/modal';
|
||||
import ModalBody from '../base/modal-body';
|
||||
import ModalHeader from '../base/modal-header';
|
||||
import ModalFooter from '../base/modal-footer';
|
||||
|
||||
type Props = {};
|
||||
|
||||
type State = {
|
||||
title: string,
|
||||
options: Array<{name: string, value: string}>,
|
||||
value: string,
|
||||
message: string
|
||||
};
|
||||
|
||||
@autobind
|
||||
class SelectModal extends React.PureComponent<Props, State> {
|
||||
modal: ?Modal;
|
||||
doneButton: ?HTMLSelectElement;
|
||||
_doneCallback: ?Function;
|
||||
|
||||
constructor (props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
title: '',
|
||||
options: [],
|
||||
message: '',
|
||||
value: ''
|
||||
};
|
||||
}
|
||||
|
||||
_setModalRef (m: ?Modal) {
|
||||
this.modal = m;
|
||||
}
|
||||
|
||||
_setDoneButtonRef (n: ?HTMLSelectElement) {
|
||||
this.doneButton = n;
|
||||
}
|
||||
|
||||
_handleDone () {
|
||||
this.hide();
|
||||
this._doneCallback && this._doneCallback(this.state.value);
|
||||
}
|
||||
|
||||
_handleSelectChange (e: SyntheticEvent<HTMLInputElement>) {
|
||||
this.setState({value: e.currentTarget.value});
|
||||
}
|
||||
|
||||
hide () {
|
||||
this.modal && this.modal.hide();
|
||||
}
|
||||
|
||||
show (data: Object = {}) {
|
||||
const {
|
||||
title,
|
||||
message,
|
||||
options,
|
||||
value,
|
||||
onDone
|
||||
} = data;
|
||||
|
||||
this._doneCallback = onDone;
|
||||
|
||||
this.setState({title, message, options, value});
|
||||
|
||||
this.modal && this.modal.show();
|
||||
setTimeout(() => {
|
||||
this.doneButton && this.doneButton.focus();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
render () {
|
||||
const {message, title, options, value} = this.state;
|
||||
|
||||
return (
|
||||
<Modal noEscape ref={this._setModalRef}>
|
||||
<ModalHeader>{title || 'Confirm?'}</ModalHeader>
|
||||
<ModalBody className="wide pad">
|
||||
<p>{message}</p>
|
||||
<div className="form-control form-control--outlined">
|
||||
<select onChange={this._handleSelectChange} value={value}>
|
||||
{options.map(({name, value}) => (
|
||||
<option key={value} value={value}>{name}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<button ref={this._setDoneButtonRef} className="btn" onClick={this._handleDone}>
|
||||
Done
|
||||
</button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SelectModal;
|
@ -22,6 +22,7 @@ import PaymentNotificationModal from './modals/payment-notification-modal';
|
||||
import NunjucksModal from './modals/nunjucks-modal';
|
||||
import PromptModal from './modals/prompt-modal';
|
||||
import AskModal from './modals/ask-modal';
|
||||
import SelectModal from './modals/select-modal';
|
||||
import RequestCreateModal from './modals/request-create-modal';
|
||||
import RequestPane from './request-pane';
|
||||
import RequestSwitcherModal from './modals/request-switcher-modal';
|
||||
@ -403,6 +404,7 @@ class Wrapper extends React.PureComponent<Props, State> {
|
||||
<ChangelogModal ref={registerModal}/>
|
||||
<LoginModal ref={registerModal}/>
|
||||
<AskModal ref={registerModal}/>
|
||||
<SelectModal ref={registerModal}/>
|
||||
<RequestCreateModal ref={registerModal}/>
|
||||
<PaymentNotificationModal ref={registerModal}/>
|
||||
<FilterHelpModal ref={registerModal}/>
|
||||
|
@ -13,6 +13,8 @@ import {showModal} from '../../components/modals';
|
||||
import PaymentNotificationModal from '../../components/modals/payment-notification-modal';
|
||||
import LoginModal from '../../components/modals/login-modal';
|
||||
import * as models from '../../../models';
|
||||
import SelectModal from '../../components/modals/select-modal';
|
||||
import {showError} from '../../components/modals/index';
|
||||
|
||||
const LOCALSTORAGE_PREFIX = `insomnia::meta`;
|
||||
|
||||
@ -180,79 +182,108 @@ export function importUri (workspaceId, uri) {
|
||||
}
|
||||
|
||||
export function exportFile (workspaceId = null) {
|
||||
return async dispatch => {
|
||||
return dispatch => {
|
||||
dispatch(loadStart());
|
||||
|
||||
const workspace = await models.workspace.getById(workspaceId);
|
||||
const VALUE_JSON = 'json';
|
||||
const VALUE_HAR = 'har';
|
||||
|
||||
// Check if we want to export private environments
|
||||
let environments;
|
||||
if (workspace) {
|
||||
const parentEnv = await models.environment.getOrCreateForWorkspace(workspace);
|
||||
environments = [
|
||||
parentEnv,
|
||||
...await models.environment.findByParentId(parentEnv._id)
|
||||
];
|
||||
} else {
|
||||
environments = await models.environment.all();
|
||||
}
|
||||
|
||||
let exportPrivateEnvironments = false;
|
||||
const privateEnvironments = environments.filter(e => e.isPrivate);
|
||||
if (privateEnvironments.length) {
|
||||
const names = privateEnvironments.map(e => e.name).join(', ');
|
||||
exportPrivateEnvironments = await showModal(AskModal, {
|
||||
title: 'Export Private Environments?',
|
||||
message: `Do you want to include private environments (${names}) in your export?`
|
||||
});
|
||||
}
|
||||
|
||||
const date = moment().format('YYYY-MM-DD');
|
||||
const name = (workspace ? workspace.name : 'Insomnia All').replace(/ /g, '-');
|
||||
const lastDir = window.localStorage.getItem('insomnia.lastExportPath');
|
||||
const dir = lastDir || electron.remote.app.getPath('desktop');
|
||||
|
||||
const options = {
|
||||
title: 'Export Insomnia Data',
|
||||
buttonLabel: 'Export',
|
||||
defaultPath: path.join(dir, `${name}_${date}`),
|
||||
filters: [{
|
||||
name: 'Insomnia Export', extensions: ['json']
|
||||
}, {
|
||||
name: 'HTTP Archive 1.2', extensions: ['har']
|
||||
}]
|
||||
};
|
||||
|
||||
electron.remote.dialog.showSaveDialog(options, async filename => {
|
||||
if (!filename) {
|
||||
trackEvent('Export', 'Cancel');
|
||||
// It was cancelled, so let's bail out
|
||||
showModal(SelectModal, {
|
||||
title: 'Select Export Type',
|
||||
options: [
|
||||
{name: 'Insomnia – Sharable with other Insomnia users', value: VALUE_JSON},
|
||||
{name: 'HAR – HTTP Archive Format', value: VALUE_HAR}
|
||||
],
|
||||
message: 'Which format would you like to export as?',
|
||||
onCancel: () => {
|
||||
dispatch(loadStop());
|
||||
return;
|
||||
}
|
||||
},
|
||||
onDone: async selectedFormat => {
|
||||
const workspace = await
|
||||
models.workspace.getById(workspaceId);
|
||||
|
||||
let json;
|
||||
if (path.extname(filename) === '.har') {
|
||||
json = await importUtils.exportHAR(workspace, exportPrivateEnvironments);
|
||||
} else {
|
||||
json = await importUtils.exportJSON(workspace, exportPrivateEnvironments);
|
||||
}
|
||||
|
||||
// Remember last exported path
|
||||
window.localStorage.setItem(
|
||||
'insomnia.lastExportPath',
|
||||
path.dirname(filename)
|
||||
);
|
||||
|
||||
fs.writeFile(filename, json, {}, err => {
|
||||
if (err) {
|
||||
console.warn('Export failed', err);
|
||||
trackEvent('Export', 'Failure');
|
||||
return;
|
||||
// Check if we want to export private environments
|
||||
let environments;
|
||||
if (workspace) {
|
||||
const parentEnv = await models.environment.getOrCreateForWorkspace(workspace);
|
||||
environments = [
|
||||
parentEnv,
|
||||
...await models.environment.findByParentId(parentEnv._id)
|
||||
];
|
||||
} else {
|
||||
environments = await models.environment.all();
|
||||
}
|
||||
trackEvent('Export', 'Success');
|
||||
dispatch(loadStop());
|
||||
});
|
||||
|
||||
let exportPrivateEnvironments = false;
|
||||
const privateEnvironments = environments.filter(e => e.isPrivate);
|
||||
if (privateEnvironments.length) {
|
||||
const names = privateEnvironments.map(e => e.name).join(', ');
|
||||
exportPrivateEnvironments = await showModal(AskModal, {
|
||||
title: 'Export Private Environments?',
|
||||
message: `Do you want to include private environments (${names}) in your export?`
|
||||
});
|
||||
}
|
||||
|
||||
const date = moment().format('YYYY-MM-DD');
|
||||
const name = (workspace ? workspace.name : 'Insomnia All').replace(/ /g, '-');
|
||||
const lastDir = window.localStorage.getItem('insomnia.lastExportPath');
|
||||
const dir = lastDir || electron.remote.app.getPath('desktop');
|
||||
|
||||
const options = {
|
||||
title: 'Export Insomnia Data',
|
||||
buttonLabel: 'Export',
|
||||
defaultPath: path.join(dir, `${name}_${date}`),
|
||||
filters: []
|
||||
};
|
||||
|
||||
if (selectedFormat === VALUE_HAR) {
|
||||
options.filters = [{name: 'HTTP Archive 1.2', extensions: ['har', 'har.json', 'json']}];
|
||||
} else {
|
||||
options.filters = [{name: 'Insomnia Export', extensions: ['json']}];
|
||||
}
|
||||
|
||||
electron.remote.dialog.showSaveDialog(options, async filename => {
|
||||
if (!filename) {
|
||||
trackEvent('Export', 'Cancel');
|
||||
// It was cancelled, so let's bail out
|
||||
dispatch(loadStop());
|
||||
return;
|
||||
}
|
||||
|
||||
let json;
|
||||
try {
|
||||
if (selectedFormat === VALUE_HAR) {
|
||||
json = await importUtils.exportHAR(workspace, exportPrivateEnvironments);
|
||||
} else {
|
||||
json = await importUtils.exportJSON(workspace, exportPrivateEnvironments);
|
||||
}
|
||||
} catch (err) {
|
||||
showError({
|
||||
title: 'Export Failed',
|
||||
error: err,
|
||||
message: 'Export failed due to an unexpected error'
|
||||
});
|
||||
dispatch(loadStop());
|
||||
return;
|
||||
}
|
||||
|
||||
// Remember last exported path
|
||||
window.localStorage.setItem(
|
||||
'insomnia.lastExportPath',
|
||||
path.dirname(filename)
|
||||
);
|
||||
|
||||
fs.writeFile(filename, json, {}, err => {
|
||||
if (err) {
|
||||
console.warn('Export failed', err);
|
||||
trackEvent('Export', 'Failure');
|
||||
return;
|
||||
}
|
||||
trackEvent('Export', 'Success');
|
||||
dispatch(loadStop());
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
38
package-lock.json
generated
38
package-lock.json
generated
@ -5,9 +5,9 @@
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "7.0.43",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-7.0.43.tgz",
|
||||
"integrity": "sha512-7scYwwfHNppXvH/9JzakbVxk0o0QUILVk1Lv64GRaxwPuGpnF1QBiwdvhDpLcymb8BpomQL3KYoWKq3wUdDMhQ==",
|
||||
"version": "7.0.46",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-7.0.46.tgz",
|
||||
"integrity": "sha512-u+JAi1KtmaUoU/EHJkxoiuvzyo91FCE41Z9TZWWcOUU3P8oUdlDLdrGzCGWySPgbRMD17B0B+1aaJLYI9egQ6A==",
|
||||
"dev": true
|
||||
},
|
||||
"7zip": {
|
||||
@ -2997,14 +2997,14 @@
|
||||
"dev": true
|
||||
},
|
||||
"electron": {
|
||||
"version": "1.7.8",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-1.7.8.tgz",
|
||||
"integrity": "sha1-J7eRpolRcafVKZG5lELNvRCjU50=",
|
||||
"version": "1.7.9",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-1.7.9.tgz",
|
||||
"integrity": "sha1-rdVOn4+D7QL2UZ7BATX2mLGTNs8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "7.0.43",
|
||||
"@types/node": "7.0.46",
|
||||
"electron-download": "3.3.0",
|
||||
"extract-zip": "1.6.5"
|
||||
"extract-zip": "1.6.6"
|
||||
}
|
||||
},
|
||||
"electron-builder": {
|
||||
@ -4057,24 +4057,24 @@
|
||||
}
|
||||
},
|
||||
"extract-zip": {
|
||||
"version": "1.6.5",
|
||||
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.5.tgz",
|
||||
"integrity": "sha1-maBnNbbqIOqbcF13ms/8yHz/BEA=",
|
||||
"version": "1.6.6",
|
||||
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.6.tgz",
|
||||
"integrity": "sha1-EpDt6NINCHK0Kf0/NRyhKOxe+Fw=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"concat-stream": "1.6.0",
|
||||
"debug": "2.2.0",
|
||||
"debug": "2.6.9",
|
||||
"mkdirp": "0.5.0",
|
||||
"yauzl": "2.4.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
|
||||
"integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "0.7.1"
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"mkdirp": {
|
||||
@ -4085,12 +4085,6 @@
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
|
||||
"integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -181,7 +181,7 @@
|
||||
"concurrently": "^2.0.0",
|
||||
"cross-env": "^2.0.0",
|
||||
"css-loader": "^0.26.2",
|
||||
"electron": "^1.7.8",
|
||||
"electron": "^1.7.9",
|
||||
"electron-builder": "^10.17.3",
|
||||
"electron-rebuild": "^1.6.0",
|
||||
"eslint": "^3.16.1",
|
||||
|
Loading…
Reference in New Issue
Block a user