mirror of
https://github.com/Kong/insomnia
synced 2024-11-07 14:19:58 +00:00
PouchDB, import, environments closes #13
This commit is contained in:
parent
fc9d0c41fb
commit
097859aac1
@ -20,6 +20,6 @@ export function showUpdateNamePrompt (requestGroup) {
|
||||
return modals.show(REQUEST_GROUP_RENAME, {defaultValue, requestGroup});
|
||||
}
|
||||
|
||||
export function showEnvironmentEditModal () {
|
||||
return modals.show(ENVIRONMENT_EDITOR);
|
||||
export function showEnvironmentEditModal (requestGroup) {
|
||||
return modals.show(ENVIRONMENT_EDITOR, {requestGroup});
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ class RequestAuthEditor extends Component {
|
||||
|
||||
return (
|
||||
<KeyValueEditor
|
||||
uniquenessKey={request._id}
|
||||
pairs={pairs}
|
||||
maxPairs={1}
|
||||
namePlaceholder="Username"
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React, {Component, PropTypes} from 'react'
|
||||
import DebouncingInput from './DebouncingInput'
|
||||
|
||||
const NAME = 'name';
|
||||
const VALUE = 'value';
|
||||
@ -131,6 +130,13 @@ class KeyValueEditor extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate (nextProps) {
|
||||
return (
|
||||
nextProps.uniquenessKey !== this.props.uniquenessKey ||
|
||||
nextProps.pairs.length !== this.state.pairs.length
|
||||
)
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
this.setState({pairs: nextProps.pairs})
|
||||
}
|
||||
@ -153,7 +159,7 @@ class KeyValueEditor extends Component {
|
||||
type="text"
|
||||
placeholder={this.props.namePlaceholder || 'Name'}
|
||||
ref={`${i}.${NAME}`}
|
||||
value={pair.name}
|
||||
defaultValue={pair.name}
|
||||
onChange={e => this._updatePair(i, {name: e.target.value})}
|
||||
onFocus={e => {this._focusedPair = i; this._focusedField = NAME}}
|
||||
onBlur={e => {this._focusedPair = -1}}
|
||||
@ -165,7 +171,7 @@ class KeyValueEditor extends Component {
|
||||
type="text"
|
||||
placeholder={this.props.valuePlaceholder || 'Value'}
|
||||
ref={`${i}.${VALUE}`}
|
||||
value={pair.value}
|
||||
defaultValue={pair.value}
|
||||
onChange={e => this._updatePair(i, {value: e.target.value})}
|
||||
onFocus={e => {this._focusedPair = i; this._focusedField = VALUE}}
|
||||
onBlur={e => {this._focusedPair = -1}}
|
||||
@ -210,6 +216,7 @@ class KeyValueEditor extends Component {
|
||||
|
||||
KeyValueEditor.propTypes = {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
uniquenessKey: PropTypes.string.isRequired,
|
||||
pairs: PropTypes.array.isRequired,
|
||||
maxPairs: PropTypes.number,
|
||||
namePlaceholder: PropTypes.string,
|
||||
|
@ -21,7 +21,7 @@ class RequestGroupActionsDropdown extends Component {
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button>
|
||||
<button onClick={e => actions.showEnvironmentEditModal(requestGroup)}>
|
||||
<i className="fa fa-code"></i> Environment
|
||||
</button>
|
||||
</li>
|
||||
@ -40,7 +40,8 @@ RequestGroupActionsDropdown.propTypes = {
|
||||
actions: PropTypes.shape({
|
||||
update: PropTypes.func.isRequired,
|
||||
remove: PropTypes.func.isRequired,
|
||||
showUpdateNamePrompt: PropTypes.func.isRequired
|
||||
showUpdateNamePrompt: PropTypes.func.isRequired,
|
||||
showEnvironmentEditModal: PropTypes.func.isRequired
|
||||
}),
|
||||
requestGroup: PropTypes.object
|
||||
};
|
||||
|
@ -1,11 +1,31 @@
|
||||
import fs from 'fs'
|
||||
import React, {Component, PropTypes} from 'react'
|
||||
import {bindActionCreators} from 'redux'
|
||||
import {connect} from 'react-redux'
|
||||
import Dropdown from '../base/Dropdown'
|
||||
import * as RequestGroupActions from '../../actions/requestGroups'
|
||||
import * as db from '../../database'
|
||||
import importData from '../../lib/import'
|
||||
|
||||
class WorkspaceDropdown extends Component {
|
||||
_importDialog () {
|
||||
const dialog = require('electron').remote.dialog;
|
||||
const options = {
|
||||
properties: ['openFile'],
|
||||
filters: [{
|
||||
name: 'Insomnia Imports', extensions: ['json']
|
||||
}]
|
||||
};
|
||||
|
||||
dialog.showOpenDialog(options, paths => {
|
||||
paths.map(path => {
|
||||
fs.readFile(path, 'utf8', (err, data) => {
|
||||
err || importData(data);
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
render () {
|
||||
const {actions, loading, ...other} = this.props;
|
||||
|
||||
@ -23,16 +43,26 @@ class WorkspaceDropdown extends Component {
|
||||
</div>
|
||||
</button>
|
||||
<ul>
|
||||
<li><button onClick={e => db.requestCreate()}>
|
||||
<i className="fa fa-plus-circle"></i> Add Request
|
||||
</button></li>
|
||||
<li><button onClick={e => db.requestGroupCreate()}>
|
||||
<i className="fa fa-folder"></i> Add Request Group
|
||||
</button></li>
|
||||
<li><button onClick={e => actions.showEnvironmentEditModal()}>
|
||||
<i className="fa fa-code"></i> Environments
|
||||
</button></li>
|
||||
<li><button><i className="fa fa-share-square-o"></i> Import/Export</button></li>
|
||||
<li>
|
||||
<button onClick={e => db.requestCreate()}>
|
||||
<i className="fa fa-plus-circle"></i> Add Request
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button onClick={e => db.requestGroupCreate()}>
|
||||
<i className="fa fa-folder"></i> Add Request Group
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button onClick={e => actions.showEnvironmentEditModal()}>
|
||||
<i className="fa fa-code"></i> Environments
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button onClick={e => this._importDialog()}>
|
||||
<i className="fa fa-share-square-o"></i> Import/Export
|
||||
</button>
|
||||
</li>
|
||||
<li><button><i className="fa fa-empty"></i> Toggle Sidebar</button></li>
|
||||
<li><button><i className="fa fa-empty"></i> Delete Workspace</button></li>
|
||||
</ul>
|
||||
|
@ -7,22 +7,39 @@ import Editor from '../base/Editor'
|
||||
import KeyValueEditor from '../base/KeyValueEditor'
|
||||
import * as modalIds from '../../constants/modals'
|
||||
|
||||
class RequestGroupEnvironmentEditModal extends Component {
|
||||
class EnvironmentEditModal extends Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
pairs: []
|
||||
pairs: this._mapDataToPairs(props.requestGroup.environment)
|
||||
}
|
||||
}
|
||||
|
||||
_saveChanges () {
|
||||
this.props.onChange(this.state.pairs);
|
||||
this.props.onChange(this._mapPairsToData(this.state.pairs));
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
_keyValueChange (pairs) {
|
||||
this.setState({pairs});
|
||||
}
|
||||
|
||||
_mapPairsToData (pairs) {
|
||||
return pairs.reduce((prev, curr) => {
|
||||
return Object.assign({}, prev, {[curr.name]: curr.value});
|
||||
}, {});
|
||||
}
|
||||
|
||||
_mapDataToPairs (data) {
|
||||
return Object.keys(data).map(key => ({name: key, value: data[key]}));
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
this.setState({
|
||||
pairs: this._mapDataToPairs(nextProps.requestGroup.environment)
|
||||
})
|
||||
}
|
||||
|
||||
render () {
|
||||
const editorOptions = {
|
||||
mode: 'application/json',
|
||||
@ -36,6 +53,7 @@ class RequestGroupEnvironmentEditModal extends Component {
|
||||
<ModalBody className="grid--v wide pad">
|
||||
<div>
|
||||
<KeyValueEditor onChange={this._keyValueChange.bind(this)}
|
||||
uniquenessKey={this.props.requestGroup._id}
|
||||
pairs={this.state.pairs}
|
||||
namePlaceholder="BASE_URL"
|
||||
valuePlaceholder="https://api.insomnia.com/v1"/>
|
||||
@ -53,13 +71,15 @@ class RequestGroupEnvironmentEditModal extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
RequestGroupEnvironmentEditModal.propTypes = {
|
||||
// requestGroup: PropTypes.object.isRequired,
|
||||
EnvironmentEditModal.propTypes = {
|
||||
requestGroup: PropTypes.shape({
|
||||
environment: PropTypes.object.isRequired
|
||||
}),
|
||||
onChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
RequestGroupEnvironmentEditModal.defaultProps = {
|
||||
EnvironmentEditModal.defaultProps = {
|
||||
id: modalIds.ENVIRONMENT_EDITOR
|
||||
};
|
||||
|
||||
export default RequestGroupEnvironmentEditModal;
|
||||
export default EnvironmentEditModal;
|
@ -10,11 +10,12 @@ import RequestBodyEditor from '../components/RequestBodyEditor'
|
||||
import RequestAuthEditor from '../components/RequestAuthEditor'
|
||||
import RequestUrlBar from '../components/RequestUrlBar'
|
||||
import Sidebar from '../components/Sidebar'
|
||||
import RequestGroupEnvironmentEditModal from '../components/modals/RequestGroupEnvironmentEditModal'
|
||||
import EnvironmentEditModal from '../components/modals/EnvironmentEditModal'
|
||||
|
||||
import * as GlobalActions from '../actions/global'
|
||||
import * as RequestGroupActions from '../actions/requestGroups'
|
||||
import * as RequestActions from '../actions/requests'
|
||||
import * as ModalActions from '../actions/modals'
|
||||
|
||||
import * as db from '../database'
|
||||
|
||||
@ -63,6 +64,7 @@ class App extends Component {
|
||||
<TabPanel className="grid__cell grid__cell--scroll--v">
|
||||
<div className="wide pad">
|
||||
<KeyValueEditor
|
||||
uniquenessKey={activeRequest._id}
|
||||
pairs={activeRequest.params}
|
||||
onChange={params => {db.update(activeRequest, {params})}}
|
||||
/>
|
||||
@ -79,6 +81,7 @@ class App extends Component {
|
||||
<TabPanel className="grid__cell grid__cell--scroll--v">
|
||||
<div className="wide pad">
|
||||
<KeyValueEditor
|
||||
uniquenessKey={activeRequest._id}
|
||||
pairs={activeRequest.headers}
|
||||
onChange={headers => {db.update(activeRequest, {headers})}}
|
||||
/>
|
||||
@ -153,17 +156,25 @@ class App extends Component {
|
||||
return (
|
||||
<div className="grid bg-super-dark tall">
|
||||
<Prompts />
|
||||
{!modals.find(m => m.id === RequestGroupEnvironmentEditModal.defaultProps.id) ? null : (
|
||||
<RequestGroupEnvironmentEditModal
|
||||
onClose={() => actions.hideModal(RequestGroupEnvironmentEditModal.defaultProps.id)}
|
||||
onChange={v => console.log(v)}
|
||||
/>
|
||||
)}
|
||||
{modals.map(m => {
|
||||
if (m.id === EnvironmentEditModal.defaultProps.id) {
|
||||
return (
|
||||
<EnvironmentEditModal
|
||||
key={m.id}
|
||||
requestGroup={m.data.requestGroup}
|
||||
onClose={() => actions.modals.hide(m.id)}
|
||||
onChange={rg => db.update(m.data.requestGroup, {environment: rg.environment})}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})}
|
||||
<Sidebar
|
||||
activateRequest={actions.requests.activate}
|
||||
changeFilter={actions.requests.changeFilter}
|
||||
addRequestToRequestGroup={requestGroup => db.requestCreate({parent: requestGroup._id})}
|
||||
toggleRequestGroup={requestGroup => db.requestGroupToggle(requestGroup)}
|
||||
toggleRequestGroup={requestGroup => db.update(requestGroup, {collapsed: !requestGroup.collapsed})}
|
||||
activeRequest={activeRequest}
|
||||
activeFilter={requests.filter}
|
||||
requestGroups={requestGroups.all}
|
||||
@ -193,6 +204,9 @@ App.propTypes = {
|
||||
update: PropTypes.func.isRequired,
|
||||
toggle: PropTypes.func.isRequired
|
||||
}),
|
||||
modals: PropTypes.shape({
|
||||
hide: PropTypes.func.isRequired
|
||||
}),
|
||||
global: PropTypes.shape({
|
||||
selectTab: PropTypes.func.isRequired
|
||||
})
|
||||
@ -224,6 +238,7 @@ function mapDispatchToProps (dispatch) {
|
||||
return {
|
||||
actions: {
|
||||
global: bindActionCreators(GlobalActions, dispatch),
|
||||
modals: bindActionCreators(ModalActions, dispatch),
|
||||
requestGroups: bindActionCreators(RequestGroupActions, dispatch),
|
||||
requests: bindActionCreators(RequestActions, dispatch)
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import PouchDB from 'pouchdb';
|
||||
import * as methods from '../constants/global';
|
||||
import {generateId} from './util'
|
||||
|
||||
let db = new PouchDB('insomnia.db');
|
||||
|
||||
@ -20,6 +21,10 @@ export function allDocs () {
|
||||
return db.allDocs({include_docs: true});
|
||||
}
|
||||
|
||||
export function get (id) {
|
||||
return db.get(id);
|
||||
}
|
||||
|
||||
export function update (doc, patch = {}) {
|
||||
const updatedDoc = Object.assign(
|
||||
{},
|
||||
@ -31,7 +36,7 @@ export function update (doc, patch = {}) {
|
||||
return db.put(updatedDoc).catch(e => {
|
||||
if (e.status === 409) {
|
||||
console.warn('Retrying document update for', updatedDoc);
|
||||
db.get(doc._id).then(dbDoc => {
|
||||
get(doc._id).then(dbDoc => {
|
||||
update(dbDoc, patch);
|
||||
});
|
||||
}
|
||||
@ -47,7 +52,7 @@ export function remove (doc) {
|
||||
// ~~~~~~~ //
|
||||
|
||||
export function requestCreate (patch = {}) {
|
||||
update(Object.assign(
|
||||
const request = Object.assign(
|
||||
// Defaults
|
||||
{
|
||||
url: '',
|
||||
@ -66,13 +71,17 @@ export function requestCreate (patch = {}) {
|
||||
|
||||
// Required Generated Fields
|
||||
{
|
||||
_id: `rq_${Date.now()}`,
|
||||
_id: generateId('req'),
|
||||
_rev: undefined,
|
||||
type: 'Request',
|
||||
created: Date.now(),
|
||||
modified: Date.now()
|
||||
}
|
||||
));
|
||||
);
|
||||
|
||||
update(request);
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
export function requestDuplicate (request) {
|
||||
@ -84,7 +93,7 @@ export function requestDuplicate (request) {
|
||||
// ~~~~~~~~~~~~~ //
|
||||
|
||||
export function requestGroupCreate (patch = {}) {
|
||||
update(Object.assign(
|
||||
const requestGroup = Object.assign(
|
||||
// Default Fields
|
||||
{
|
||||
collapsed: false,
|
||||
@ -98,17 +107,17 @@ export function requestGroupCreate (patch = {}) {
|
||||
|
||||
// Required Generated Fields
|
||||
{
|
||||
_id: `rg_${Date.now()}`,
|
||||
_id: generateId('grp'),
|
||||
_rev: undefined,
|
||||
type: 'RequestGroup',
|
||||
created: Date.now(),
|
||||
modified: Date.now()
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
export function requestGroupToggle (requestGroup, patch = {}) {
|
||||
return update(requestGroup, patch);
|
||||
);
|
||||
|
||||
update(requestGroup);
|
||||
|
||||
return requestGroup;
|
||||
}
|
||||
|
||||
export {changes};
|
||||
|
11
app/database/util.js
Normal file
11
app/database/util.js
Normal file
@ -0,0 +1,11 @@
|
||||
const CHARS = '023456789abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ'.split('');
|
||||
|
||||
export function generateId (prefix) {
|
||||
let id = `${prefix}_${Date.now()}-`;
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
id += CHARS[Math.floor(Math.random() * CHARS.length)];
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
70
app/lib/import.js
Normal file
70
app/lib/import.js
Normal file
@ -0,0 +1,70 @@
|
||||
import * as db from '../database'
|
||||
|
||||
const TYPE_REQUEST = 'request';
|
||||
const TYPE_REQUEST_GROUP = 'request_group';
|
||||
const FORMAT_MAP = {
|
||||
'json': 'application/json'
|
||||
};
|
||||
|
||||
function importRequestGroup (iRequestGroup, exportFormat) {
|
||||
if (exportFormat === 1) {
|
||||
const requestGroup = db.requestGroupCreate({
|
||||
name: iRequestGroup.name,
|
||||
environment: (iRequestGroup.environments || {}).base || {}
|
||||
});
|
||||
|
||||
// Sometimes (maybe all the time, I can't remember) requests will be nested
|
||||
if (iRequestGroup.hasOwnProperty('requests')) {
|
||||
iRequestGroup.requests.map(
|
||||
r => importRequest(r, requestGroup._id, exportFormat)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function importRequest (iRequest, parent, exportFormat) {
|
||||
if (exportFormat === 1) {
|
||||
let auth = {};
|
||||
if (iRequest.authentication.username) {
|
||||
auth = {
|
||||
username: iRequest.authentication.username,
|
||||
password: iRequest.authentication.password
|
||||
}
|
||||
}
|
||||
|
||||
db.requestCreate({
|
||||
name: iRequest.name,
|
||||
url: iRequest.url,
|
||||
method: iRequest.method,
|
||||
body: iRequest.body,
|
||||
headers: iRequest.headers || [],
|
||||
params: iRequest.params || [],
|
||||
contentType: FORMAT_MAP[iRequest.__insomnia.format] || 'text/plain',
|
||||
authentication: auth,
|
||||
parent: parent
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default function (txt, callback) {
|
||||
let data;
|
||||
|
||||
try {
|
||||
data = JSON.parse(txt);
|
||||
} catch (e) {
|
||||
return callback(new Error('Invalid Insomnia export'));
|
||||
}
|
||||
|
||||
|
||||
if (!data.hasOwnProperty('_type') || !data.hasOwnProperty('items')) {
|
||||
return callback(new Error('Invalid Insomnia export'));
|
||||
}
|
||||
|
||||
data.items.filter(i => i._type === TYPE_REQUEST_GROUP).map(
|
||||
rg => importRequestGroup(rg, data.__export_format)
|
||||
);
|
||||
|
||||
data.items.filter(i => i._type === TYPE_REQUEST).map(
|
||||
r => importRequest(r, data.__export_format)
|
||||
);
|
||||
}
|
@ -1,14 +1,20 @@
|
||||
import networkRequest from 'request'
|
||||
import render from './render'
|
||||
import * as db from '../database'
|
||||
|
||||
export default function (request, callback) {
|
||||
function makeRequest (request, callback) {
|
||||
const config = {
|
||||
url: request.url,
|
||||
method: request.method,
|
||||
body: request.body,
|
||||
headers: {}
|
||||
};
|
||||
|
||||
if (request.url.indexOf('://') === -1) {
|
||||
config.url = request.url;
|
||||
} else {
|
||||
config.url = `https://${request.url}`;
|
||||
}
|
||||
|
||||
if (request.authentication.username) {
|
||||
config.auth = {
|
||||
user: request.authentication.username,
|
||||
@ -23,7 +29,7 @@ export default function (request, callback) {
|
||||
config.headers[header.name] = header.value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO: this is just a POC. It breaks in a lot of cases
|
||||
config.url += request.params.map((p, i) => {
|
||||
const name = encodeURIComponent(p.name);
|
||||
@ -31,12 +37,8 @@ export default function (request, callback) {
|
||||
return `${i === 0 ? '?' : '&'}${name}=${value}`;
|
||||
}).join('');
|
||||
|
||||
// SNEAKY HACK: Render nested object by converting it to JSON then rendering
|
||||
const context = {template_id: 'tem_WWq2w9uJNR6Pqk8APkvsS3'};
|
||||
const template = JSON.stringify(config);
|
||||
const renderedConfig = JSON.parse(render(template, context));
|
||||
|
||||
networkRequest(renderedConfig, function (err, response) {
|
||||
networkRequest(config, function (err, response) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
} else {
|
||||
@ -52,3 +54,17 @@ export default function (request, callback) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default function (originalRequest, callback) {
|
||||
// SNEAKY HACK: Render nested object by converting it to JSON then rendering
|
||||
const template = JSON.stringify(originalRequest);
|
||||
const request = JSON.parse(render(template, context));
|
||||
|
||||
if (request.parent) {
|
||||
db.get(request.parent).then(
|
||||
requestGroup => makeRequest(config, callback, requestGroup.environment)
|
||||
);
|
||||
} else {
|
||||
makeRequest(request, callback)
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,6 @@
|
||||
"babel-preset-react": "^6.5.0",
|
||||
"concurrently": "^2.0.0",
|
||||
"css-loader": "^0.23.1",
|
||||
"electron-prebuilt": "^0.37.2",
|
||||
"express": "latest",
|
||||
"file-loader": "^0.8.5",
|
||||
"jest": "^0.1.40",
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
const express = require('express');
|
||||
const webpack = require('webpack');
|
||||
const config = require('./dev.electron.config.js');
|
||||
const config = require('./dev.config.js');
|
||||
|
||||
const app = express();
|
||||
const compiler = webpack(config);
|
||||
|
Loading…
Reference in New Issue
Block a user