mirror of
https://github.com/Kong/insomnia
synced 2024-11-07 22:30:15 +00:00
Track request versions and restore with response (#305)
This commit is contained in:
parent
ff58ff7e51
commit
93b16cc107
@ -7,6 +7,7 @@ import * as _cookieJar from './cookie-jar';
|
||||
import * as _requestGroup from './request-group';
|
||||
import * as _requestGroupMeta from './request-group-meta';
|
||||
import * as _request from './request';
|
||||
import * as _requestVersion from './request-version';
|
||||
import * as _requestMeta from './request-meta';
|
||||
import * as _response from './response';
|
||||
import * as _oAuth2Token from './o-auth-2-token';
|
||||
@ -21,6 +22,7 @@ export const cookieJar = _cookieJar;
|
||||
export const requestGroup = _requestGroup;
|
||||
export const requestGroupMeta = _requestGroupMeta;
|
||||
export const request = _request;
|
||||
export const requestVersion = _requestVersion;
|
||||
export const requestMeta = _requestMeta;
|
||||
export const response = _response;
|
||||
export const oAuth2Token = _oAuth2Token;
|
||||
@ -35,6 +37,7 @@ const _models = {
|
||||
[requestGroup.type]: requestGroup,
|
||||
[requestGroupMeta.type]: requestGroupMeta,
|
||||
[request.type]: request,
|
||||
[requestVersion.type]: requestVersion,
|
||||
[requestMeta.type]: requestMeta,
|
||||
[response.type]: response,
|
||||
[oAuth2Token.type]: oAuth2Token
|
||||
|
117
app/models/request-version.js
Normal file
117
app/models/request-version.js
Normal file
@ -0,0 +1,117 @@
|
||||
import zlib from 'zlib';
|
||||
import deepEqual from 'deep-equal';
|
||||
import * as models from './index';
|
||||
import * as db from '../common/database';
|
||||
export const name = 'Request Version';
|
||||
export const type = 'RequestVersion';
|
||||
export const prefix = 'rvr';
|
||||
export const canDuplicate = false;
|
||||
|
||||
const FIELDS_TO_IGNORE_IN_REQUEST_DIFF = [
|
||||
'_id',
|
||||
'type',
|
||||
'created',
|
||||
'modified',
|
||||
'metaSortKey',
|
||||
'description',
|
||||
'name'
|
||||
];
|
||||
|
||||
export function init () {
|
||||
return {
|
||||
compressedRequest: null
|
||||
};
|
||||
}
|
||||
|
||||
export function migrate (doc) {
|
||||
return doc;
|
||||
}
|
||||
|
||||
export function getById (id) {
|
||||
return db.get(type, id);
|
||||
}
|
||||
|
||||
export async function create (request) {
|
||||
if (!request.type === models.request.type) {
|
||||
throw new Error(`New ${type} was not given a valid ${models.request.type} instance`);
|
||||
}
|
||||
|
||||
const parentId = request._id;
|
||||
const latestRequestVersion = await getLatestByParentId(parentId);
|
||||
const latestRequest = latestRequestVersion
|
||||
? await _decompressRequest(latestRequestVersion.compressedRequest)
|
||||
: null;
|
||||
|
||||
const hasChanged = _diffRequests(latestRequest, request);
|
||||
if (hasChanged) {
|
||||
// Create a new version if the request has been modified
|
||||
const compressedRequest = await _compressRequest(request);
|
||||
return db.docCreate(type, {parentId, compressedRequest});
|
||||
} else {
|
||||
// Re-use the latest version if not modified since
|
||||
return latestRequestVersion;
|
||||
}
|
||||
}
|
||||
|
||||
export function getLatestByParentId (parentId) {
|
||||
return db.getMostRecentlyModified(type, {parentId});
|
||||
}
|
||||
|
||||
export async function restore (requestVersionId) {
|
||||
const requestVersion = await getById(requestVersionId);
|
||||
|
||||
// Older responses won't have versions saved with them
|
||||
if (!requestVersion) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const request = await _decompressRequest(requestVersion.compressedRequest);
|
||||
return models.request.update(request);
|
||||
}
|
||||
|
||||
function _diffRequests (rOld, rNew) {
|
||||
if (!rOld) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const key of Object.keys(rOld)) {
|
||||
// Skip fields that aren't useful
|
||||
if (FIELDS_TO_IGNORE_IN_REQUEST_DIFF.includes(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!deepEqual(rOld[key], rNew[key])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function _compressRequest (request) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const str = JSON.stringify(request);
|
||||
zlib.gzip(str, {}, (err, buffer) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
const encoded = buffer.toString('base64');
|
||||
resolve(encoded);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function _decompressRequest (requestJson) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const buffer = Buffer.from(requestJson, 'base64');
|
||||
zlib.gunzip(buffer, {}, (err, jsonStr) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
const obj = JSON.parse(jsonStr);
|
||||
resolve(obj);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import * as db from '../common/database';
|
||||
import {MAX_RESPONSES} from '../common/constants';
|
||||
import * as models from './index';
|
||||
|
||||
export const name = 'Response';
|
||||
export const type = 'Response';
|
||||
@ -20,6 +21,7 @@ export function init () {
|
||||
body: '',
|
||||
encoding: 'utf8', // Legacy format
|
||||
error: '',
|
||||
requestVersionId: null,
|
||||
|
||||
// Things from the request
|
||||
settingStoreCookies: null,
|
||||
@ -63,6 +65,11 @@ export async function create (patch = {}) {
|
||||
|
||||
const {parentId} = patch;
|
||||
|
||||
// Create request version snapshot
|
||||
const request = await models.request.getById(parentId);
|
||||
const requestVersion = request ? await models.requestVersion.create(request) : null;
|
||||
patch.requestVersionId = requestVersion ? requestVersion._id : null;
|
||||
|
||||
// Delete all other responses before creating the new one
|
||||
const allResponses = await db.findMostRecentlyModified(type, {parentId}, MAX_RESPONSES);
|
||||
const recentIds = allResponses.map(r => r._id);
|
||||
|
@ -11,6 +11,7 @@
|
||||
"dependencies": {
|
||||
"electron-context-menu": "0.9.0",
|
||||
"electron-squirrel-startup": "1.0.0",
|
||||
"deep-equal": "1.0.1",
|
||||
"hkdf": "0.0.2",
|
||||
"httpsnippet": "1.16.5",
|
||||
"insomnia-importers": "1.3.8",
|
||||
|
@ -64,6 +64,8 @@ class ResponseHistoryDropdown extends PureComponent {
|
||||
renderDropdownItem (response, i) {
|
||||
const {activeResponseId} = this.props;
|
||||
const active = response._id === activeResponseId;
|
||||
const message = 'Request will not be restored with this response because ' +
|
||||
'it was created before this ability was added';
|
||||
return (
|
||||
<DropdownItem key={response._id}
|
||||
disabled={active}
|
||||
@ -76,6 +78,7 @@ class ResponseHistoryDropdown extends PureComponent {
|
||||
statusMessage={response.statusMessage || 'Error'}/>
|
||||
<TimeTag milliseconds={response.elapsedTime} small/>
|
||||
<SizeTag bytes={response.bytesRead} small/>
|
||||
{!response.requestVersionId && <i className="icon fa fa-info-circle" title={message}/>}
|
||||
</DropdownItem>
|
||||
);
|
||||
}
|
||||
|
@ -472,8 +472,26 @@ class App extends PureComponent {
|
||||
this.props.handleStopLoading(requestId);
|
||||
}
|
||||
|
||||
_handleSetActiveResponse (requestId, activeResponseId) {
|
||||
this._updateRequestMetaByParentId(requestId, {activeResponseId});
|
||||
async _handleSetActiveResponse (requestId, activeResponseId = null) {
|
||||
await this._updateRequestMetaByParentId(requestId, {activeResponseId});
|
||||
|
||||
let response;
|
||||
if (activeResponseId) {
|
||||
response = await models.response.getById(activeResponseId);
|
||||
} else {
|
||||
response = await models.response.getLatestForRequest(requestId);
|
||||
}
|
||||
|
||||
const requestVersionId = response ? response.requestVersionId : 'n/a';
|
||||
const request = await models.requestVersion.restore(requestVersionId);
|
||||
|
||||
if (request) {
|
||||
// Refresh app to reflect changes. Using timeout because we need to
|
||||
// wait for the request update to propagate.
|
||||
setTimeout(() => this._wrapper._forceRequestPaneRefresh(), 500);
|
||||
} else {
|
||||
// Couldn't restore request. That's okay
|
||||
}
|
||||
}
|
||||
|
||||
_requestCreateForWorkspace () {
|
||||
|
5
package-lock.json
generated
5
package-lock.json
generated
@ -1755,6 +1755,11 @@
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
|
||||
},
|
||||
"deep-equal": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
|
||||
"integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU="
|
||||
},
|
||||
"deep-extend": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz",
|
||||
|
@ -112,6 +112,7 @@
|
||||
"classnames": "2.2.5",
|
||||
"clone": "2.1.0",
|
||||
"codemirror": "5.24.2",
|
||||
"deep-equal": "1.0.1",
|
||||
"electron-context-menu": "0.9.0",
|
||||
"electron-squirrel-startup": "1.0.0",
|
||||
"highlight.js": "9.12.0",
|
||||
|
Loading…
Reference in New Issue
Block a user