Skip to content

Commit

Permalink
Improve search dropdowns
Browse files Browse the repository at this point in the history
- Sort services and operations operations (case insensitive)
- Filter options based on contains instead of starts with
  • Loading branch information
tiffon committed Sep 25, 2017
1 parent 5162309 commit ae5702f
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 6 deletions.
15 changes: 9 additions & 6 deletions src/components/SearchTracePage/SearchDropdownInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Dropdown } from 'semantic-ui-react';

import regexpEscape from '../../utils/regexp-escape';

/**
* We have to wrap the semantic ui component becuase it doesn't perform well
* when there are 200+ suggestions.
Expand All @@ -33,21 +35,22 @@ export default class SearchDropdownInput extends Component {
constructor(props) {
super(props);
this.state = {
items: props.items,
currentItems: props.items.slice(0, props.maxResults),
};
this.onSearch = this.onSearch.bind(this);
}
componentWillReceiveProps(nextProps) {
if (this.props.items.map(i => i.text).join(',') !== nextProps.items.map(i => i.text).join(',')) {
this.setState({
items: nextProps.items,
currentItems: nextProps.items.slice(0, nextProps.maxResults),
});
}
}
onSearch(items, v) {
const { maxResults } = this.props;
return this.state.items.filter(i => i.text.startsWith(v)).slice(0, maxResults);
onSearch(_, searchText) {
const { items, maxResults } = this.props;
const rxStr = regexpEscape(searchText);
const rx = new RegExp(rxStr, 'i');
return items.filter(v => rx.test(v.text)).slice(0, maxResults);
}
render() {
const { input: { value, onChange } } = this.props;
Expand All @@ -56,7 +59,7 @@ export default class SearchDropdownInput extends Component {
<Dropdown
value={value}
text={value}
search={(items, v) => this.onSearch(items, v)}
search={this.onSearch}
onChange={(e, { value: newValue }) => onChange(newValue)}
options={currentItems}
selection
Expand Down
7 changes: 7 additions & 0 deletions src/reducers/services.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import { handleActions } from 'redux-actions';

import { fetchServices, fetchServiceOperations as fetchOps } from '../actions/jaeger-api';
import { baseStringComparator } from '../utils/sort';

const initialState = {
services: [],
Expand All @@ -35,6 +36,9 @@ function fetchStarted(state) {

function fetchServicesDone(state, { payload }) {
const services = payload.data;
if (Array.isArray(services)) {
services.sort(baseStringComparator);
}
return { ...state, services, error: null, loading: false };
}

Expand All @@ -49,6 +53,9 @@ function fetchOpsStarted(state, { meta: { serviceName } }) {

function fetchOpsDone(state, { meta, payload }) {
const { data: operations } = payload;
if (Array.isArray(operations)) {
operations.sort(baseStringComparator);
}
const operationsForService = { ...state.operationsForService, [meta.serviceName]: operations };
return { ...state, operationsForService };
}
Expand Down
23 changes: 23 additions & 0 deletions src/utils/regexp-escape.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

export default function regexpEscape(s) {
return s.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
}
33 changes: 33 additions & 0 deletions src/utils/regexp-escape.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) 2017 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import regexpEscape from './regexp-escape';

describe('regexp-escape', () => {
const chars = '-/\\^$*+?.()|[]{}'.split('');
chars.forEach(c => {
it(`escapes "${c}" correctly`, () => {
const result = regexpEscape(c);
expect(result.length).toBe(2);
expect(result[0]).toBe('\\');
expect(result[1]).toBe(c);
});
});
});
4 changes: 4 additions & 0 deletions src/utils/sort.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

export function baseStringComparator(itemA, itemB) {
return itemA.localeCompare(itemB, 'en', { sensitivity: 'base' });
}

export function stringSortComparator(itemA, itemB) {
return itemA.localeCompare(itemB);
}
Expand Down
6 changes: 6 additions & 0 deletions src/utils/sort.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ import sinon from 'sinon';

import * as sortUtils from './sort';

it('baseStringComparator() provides a case-insensitive sort', () => {
const arr = ['Z', 'ab', 'AC'];
expect(arr.slice().sort()).toEqual(['AC', 'Z', 'ab']);
expect(arr.slice().sort(sortUtils.baseStringComparator)).toEqual(['ab', 'AC', 'Z']);
});

it('stringSortComparator() should properly sort a list of strings', () => {
const arr = ['allen', 'Gustav', 'paul', 'Tim', 'abernathy', 'tucker', 'Steve', 'mike', 'John', 'Paul'];

Expand Down

0 comments on commit ae5702f

Please sign in to comment.