mirror of
https://github.com/Kong/insomnia.git
synced 2026-05-24 16:51:06 -04:00
Filter requests by folder, URI, method, query string (#797)
* Quick Switch matching for Request URL and Method Previously only Request Name was searched for in Quick Switch window. This adds support for searching Request URL and Method as well. A fuzzyMatchAll function has been added to be able to search different fields in any order, space delimited. * Include request parameters in searchable fields * Allow searching requests by folder paths * More descriptive placeholder for Quick Switch modal search input * Update sidebar filter to match Quick Switch, allowing URL and Query String matching * More descriptive placeholder for sidebar search * Unit tests for fuzzyMatch and fuzzyMatchAll * More unit tests for fuzzyMatch and fuzzyMatchAll * minor refactorings
This commit is contained in:
committed by
Gregory Schier
parent
dc0695de23
commit
bee9973839
@@ -106,3 +106,37 @@ describe('debounce()', () => {
|
||||
expect(resultList).toEqual([['foo', 'bar3']]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fuzzyMatch()', () => {
|
||||
beforeEach(globalBeforeEach);
|
||||
it('can get a positive fuzzy match on a single field', () => {
|
||||
expect(misc.fuzzyMatch('', undefined)).toEqual(true);
|
||||
expect(misc.fuzzyMatch('', 'testing')).toEqual(true);
|
||||
expect(misc.fuzzyMatch('test', 'testing')).toEqual(true);
|
||||
expect(misc.fuzzyMatch('tstg', 'testing')).toEqual(true);
|
||||
});
|
||||
|
||||
it('can get a negative fuzzy match on a single field', () => {
|
||||
expect(misc.fuzzyMatch('foo', undefined)).toEqual(false);
|
||||
expect(misc.fuzzyMatch('foo', 'bar')).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fuzzyMatchAll()', () => {
|
||||
beforeEach(globalBeforeEach);
|
||||
it('can get a positive fuzzy match on multiple fields', () => {
|
||||
expect(misc.fuzzyMatchAll('', [undefined])).toEqual(true);
|
||||
expect(misc.fuzzyMatchAll('', ['testing'])).toEqual(true);
|
||||
expect(misc.fuzzyMatchAll(' ', ['testing'])).toEqual(true);
|
||||
expect(misc.fuzzyMatchAll('test', ['testing'])).toEqual(true);
|
||||
expect(misc.fuzzyMatchAll('tstg', ['testing'])).toEqual(true);
|
||||
expect(misc.fuzzyMatchAll('tstg this ou', ['testing', 'this', 'out'])).toEqual(true);
|
||||
});
|
||||
|
||||
it('can get a negative fuzzy match on multiple fields', () => {
|
||||
expect(misc.fuzzyMatchAll('foo', [undefined])).toEqual(false);
|
||||
expect(misc.fuzzyMatchAll('foo', ['bar'])).toEqual(false);
|
||||
expect(misc.fuzzyMatchAll('wrong this ou', ['testing', 'this', 'out'])).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -251,7 +251,15 @@ export function fuzzyMatch (searchString: string, text: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
return toMatch.test(text.toLowerCase());
|
||||
return toMatch.test((text || '').toLowerCase());
|
||||
}
|
||||
|
||||
export function fuzzyMatchAll (searchString: string, allText: Array<string>): boolean {
|
||||
return searchString
|
||||
.split(' ')
|
||||
.every(searchWord =>
|
||||
allText.some(text =>
|
||||
fuzzyMatch(searchWord, text)));
|
||||
}
|
||||
|
||||
export function getViewportSize (): string | null {
|
||||
|
||||
@@ -2,13 +2,14 @@ import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import autobind from 'autobind-decorator';
|
||||
import classnames from 'classnames';
|
||||
import {buildQueryStringFromParams, joinUrlAndQueryString} from 'insomnia-url';
|
||||
import Button from '../base/button';
|
||||
import Modal from '../base/modal';
|
||||
import ModalHeader from '../base/modal-header';
|
||||
import ModalBody from '../base/modal-body';
|
||||
import MethodTag from '../tags/method-tag';
|
||||
import * as models from '../../../models';
|
||||
import {fuzzyMatch} from '../../../common/misc';
|
||||
import {fuzzyMatchAll} from '../../../common/misc';
|
||||
|
||||
@autobind
|
||||
class RequestSwitcherModal extends PureComponent {
|
||||
@@ -122,24 +123,62 @@ class RequestSwitcherModal extends PureComponent {
|
||||
this._handleChangeValue(e.target.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends path of ancestor groups, delimited by forward slashes
|
||||
* E.g. Folder1/Folder2/Folder3
|
||||
*/
|
||||
_groupOf (requestOrRequestGroup) {
|
||||
const {workspaceChildren} = this.props;
|
||||
const requestGroups = workspaceChildren.filter(d => d.type === models.requestGroup.type);
|
||||
const matchedGroups = requestGroups.filter(g => g._id === requestOrRequestGroup.parentId);
|
||||
const currentGroupName = requestOrRequestGroup.type === models.requestGroup.type && requestOrRequestGroup.name ? `${requestOrRequestGroup.name}` : '';
|
||||
|
||||
if (matchedGroups.length === 0) {
|
||||
return currentGroupName;
|
||||
}
|
||||
|
||||
const parentGroup = this._groupOf(matchedGroups[0]);
|
||||
const parentGroupText = parentGroup ? `${parentGroup}/` : '';
|
||||
const group = `${parentGroupText}${currentGroupName}`;
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
_isMatch (searchStrings) {
|
||||
return (request) => {
|
||||
let finalUrl = request.url;
|
||||
if (request.parameters) {
|
||||
finalUrl = joinUrlAndQueryString(
|
||||
finalUrl,
|
||||
buildQueryStringFromParams(request.parameters));
|
||||
}
|
||||
|
||||
// Match request attributes
|
||||
const matchesAttributes = fuzzyMatchAll(searchStrings,
|
||||
[
|
||||
request.name,
|
||||
finalUrl,
|
||||
request.method,
|
||||
this._groupOf(request)
|
||||
]);
|
||||
|
||||
// Match exact Id
|
||||
const matchesId = request._id === searchStrings;
|
||||
|
||||
return matchesAttributes || matchesId;
|
||||
};
|
||||
}
|
||||
|
||||
async _handleChangeValue (searchString) {
|
||||
const {workspaceChildren, workspaces} = this.props;
|
||||
const {workspaceId, activeRequestParentId} = this.props;
|
||||
|
||||
// OPTIMIZATION: This only filters if we have a filter
|
||||
let matchedRequests = workspaceChildren.filter(d => d.type === models.request.type);
|
||||
let matchedRequests = workspaceChildren
|
||||
.filter(d => d.type === models.request.type);
|
||||
|
||||
if (searchString) {
|
||||
matchedRequests = matchedRequests.filter(r => {
|
||||
const name = r.name.toLowerCase();
|
||||
|
||||
// Fuzzy match searchString to name
|
||||
const matchesName = fuzzyMatch(searchString, name);
|
||||
|
||||
// Match exact Id
|
||||
const matchesId = r._id === searchString;
|
||||
|
||||
return matchesName || matchesId;
|
||||
});
|
||||
matchedRequests = matchedRequests.filter(this._isMatch(searchString));
|
||||
}
|
||||
|
||||
matchedRequests = matchedRequests.sort((a, b) => {
|
||||
@@ -225,7 +264,7 @@ class RequestSwitcherModal extends PureComponent {
|
||||
<div className="form-control form-control--outlined no-margin">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
placeholder="Fuzzy filter by request name, folder, url, method, or query parameters"
|
||||
ref={this._setInputRef}
|
||||
value={searchString}
|
||||
onChange={this._handleChange}
|
||||
|
||||
@@ -52,7 +52,7 @@ class SidebarFilter extends PureComponent {
|
||||
<input
|
||||
ref={this._setInputRef}
|
||||
type="text"
|
||||
placeholder="Filter"
|
||||
placeholder="Filter by name, folder, url"
|
||||
defaultValue={this.props.filter}
|
||||
onChange={this._handleOnChange}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {createSelector} from 'reselect';
|
||||
import {fuzzyMatch} from '../../common/misc';
|
||||
import {fuzzyMatchAll} from '../../common/misc';
|
||||
|
||||
// ~~~~~~~~~ //
|
||||
// Selectors //
|
||||
@@ -112,23 +112,19 @@ export const selectSidebarChildren = createSelector(
|
||||
|
||||
const hasMatchedChildren = child.children.find(c => c.hidden === false);
|
||||
|
||||
// Build the monster string to match on
|
||||
const method = child.doc.method || '';
|
||||
const name = child.doc.name;
|
||||
const toMatch = `${method}❅${name}❅${parentNames.join('❅')}`;
|
||||
// Try to match request attributes
|
||||
const {name, url, method, parameters} = child.doc;
|
||||
|
||||
// Try to match name
|
||||
let hasMatchedName = true;
|
||||
for (const token of sidebarFilter.trim().split(' ')) {
|
||||
// Filter failed. Don't render children
|
||||
if (!fuzzyMatch(token, toMatch)) {
|
||||
hasMatchedName = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const hasMatchedAttributes = fuzzyMatchAll(sidebarFilter, [
|
||||
name,
|
||||
url,
|
||||
method,
|
||||
...(parameters ? parameters.map(p => `${p.name}=${p.value}`) : []),
|
||||
...parentNames
|
||||
]);
|
||||
|
||||
// Update hidden state depending on whether it matched
|
||||
const matched = hasMatchedChildren || hasMatchedName;
|
||||
const matched = hasMatchedChildren || hasMatchedAttributes;
|
||||
child.hidden = !matched;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user