Skip to content

Commit

Permalink
Improved UX of column search, results
Browse files Browse the repository at this point in the history
Upon pressing Enter with search text, a search is triggered. During the
search, a spinning loading icon is displayed in the column next to the
search text input field.

If there are results, the same behaviour as before is used. If no
results match the search the search text goes red. Any changes to the
search text reset the color of the search text.

Modified hover text to tell user to press Enter, added hover text to other icons.

This makes using the column search more clear, and makes the user aware
of the search status and if there are any results.

Signed-off-by: Dylan leclair dleclair@blackberry.com
  • Loading branch information
dleclairbb authored and bhufmann committed Jul 7, 2023
1 parent 981689f commit 6eeed5a
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ exports[`<TableOutputComponent /> Empty search filter renderer 1`] = `
onClick={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
title="Enter a regular expression"
>
<svg
aria-hidden="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -770,7 +770,7 @@ export class TableOutputComponent extends AbstractOutputComponent<TableOutputPro
} else if (direction === Direction.PREVIOUS) {
// no backward search if there is no selection
this.gridMatched = false;
return;
return false;
}

let rowNodes: RowNode[] = [];
Expand Down Expand Up @@ -829,7 +829,7 @@ export class TableOutputComponent extends AbstractOutputComponent<TableOutputPro
if (isFound) {
// Match found in cache
this.gridMatched = false;
return;
return isFound;
}
// find match outside the cache
let syncData = undefined;
Expand All @@ -843,6 +843,7 @@ export class TableOutputComponent extends AbstractOutputComponent<TableOutputPro
this.startTimestamp = this.endTimestamp = BigInt(data.row[this.timestampCol]);
syncData = data.row;
}
isFound = true;
}
}

Expand All @@ -860,6 +861,7 @@ export class TableOutputComponent extends AbstractOutputComponent<TableOutputPro
}
this.gridMatched = false;
}
return isFound;
}

private isValidRowSelection(rowNode: RowNode): boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@ type CellRendererProps = ICellRendererParams & {

type SearchFilterRendererProps = IFloatingFilterParams & {
onFilterChange: (colName: string, filterValue: string) => void;
onclickNext: () => void;
onclickPrevious: () => void;
onclickNext: () => Promise<boolean>;
onclickPrevious: () => Promise<boolean>;
colName: string;
filterModel: Map<string, string>;
};

interface SearchFilterRendererState {
hasHovered: boolean;
hasClicked: boolean;
isSearchSuccessful: boolean;
isSearching: boolean;
}

export class LoadingRenderer extends React.Component<ICellRendererParams> {
Expand Down Expand Up @@ -86,11 +88,13 @@ export class SearchFilterRenderer extends React.Component<SearchFilterRendererPr
super(props);
this.state = {
hasHovered: false,
hasClicked: this.props.filterModel.has(this.props.colName)
hasClicked: this.props.filterModel.has(this.props.colName),
isSearchSuccessful: true,
isSearching: false
};

this.debouncedChangeHandler = debounce((colName, inputVal) => {
this.props.onFilterChange(colName, inputVal);
this.onFilterChange(colName, inputVal);
}, 10);

this.onInputBoxChanged = this.onInputBoxChanged.bind(this);
Expand All @@ -101,6 +105,8 @@ export class SearchFilterRenderer extends React.Component<SearchFilterRendererPr
this.onUpClickHandler = this.onUpClickHandler.bind(this);
this.onCloseClickHandler = this.onCloseClickHandler.bind(this);
this.onKeyDownEvent = this.onKeyDownEvent.bind(this);
this.onFilterChange = this.onFilterChange.bind(this);
this.runSearch = this.runSearch.bind(this);
}

render(): JSX.Element {
Expand All @@ -110,7 +116,6 @@ export class SearchFilterRenderer extends React.Component<SearchFilterRendererPr
onMouseEnter={this.onMouseEnterHandler}
onMouseLeave={this.onMouseLeaveHandler}
onClick={this.onClickHandler}
title="Enter a regular expression"
>
{!this.state.hasClicked && !this.state.hasHovered && (
<FontAwesomeIcon style={{ marginLeft: '10px' }} icon={faSearch} />
Expand Down Expand Up @@ -139,45 +144,90 @@ export class SearchFilterRenderer extends React.Component<SearchFilterRendererPr
autoFocus={true}
onKeyDown={this.onKeyDownEvent}
onInput={this.onInputBoxChanged}
style={{ width: '50%', margin: '10px' }}
style={{
width: '50%',
margin: '10px',
color: this.state.isSearchSuccessful ? '' : 'red'
}}
defaultValue={this.props.filterModel.get(this.props.colName) ?? ''}
title="Enter a regular expression, then press Enter"
/>
{this.state.isSearching && (
<FontAwesomeIcon
spin
icon={faSpinner}
style={{ marginRight: '10px', marginTop: '20px' }}
title="Searching..."
/>
)}
<FontAwesomeIcon
className="hoverClass"
icon={faTimes}
style={{ marginTop: '20px' }}
onClick={this.onCloseClickHandler}
title="Clear search"
/>
<FontAwesomeIcon
className="hoverClass"
icon={faAngleDown}
style={{ marginLeft: '10px', marginTop: '20px' }}
onClick={this.onDownClickHandler}
title="Find next"
/>
<FontAwesomeIcon
className="hoverClass"
icon={faAngleUp}
style={{ marginLeft: '10px', marginTop: '20px' }}
onClick={this.onUpClickHandler}
title="Find previous"
/>
</div>
)}
</div>
);
}

private async onFilterChange(colName: string, inputVal: string) {
this.setState({ isSearchSuccessful: true, isSearching: false });
this.props.onFilterChange(colName, inputVal);
}

private runSearch(callback: () => Promise<boolean> | undefined): void {
this.setState({ isSearching: true, isSearchSuccessful: true }, () => {
this.forceUpdate(() => {
const result = callback();
if (result === undefined) {
return;
}
result.then(isFound => {
// need to trigger backend query
this.setState({
isSearching: false,
isSearchSuccessful: isFound
});
});
result.catch(() => {
this.setState({
isSearching: false,
isSearchSuccessful: false
});
});
});
});
}

private onKeyDownEvent(event: React.KeyboardEvent) {
if (event.key === 'Enter') {
if (event.shiftKey) {
this.props.onclickPrevious();
this.runSearch(this.props.onclickPrevious);
} else {
this.props.onclickNext();
this.runSearch(this.props.onclickNext);
}
} else if (event.key === 'Escape') {
this.setState({
hasClicked: false
});
this.props.onFilterChange(this.props.colName, '');
this.onFilterChange(this.props.colName, '');
}
return;
}
Expand Down Expand Up @@ -209,20 +259,20 @@ export class SearchFilterRenderer extends React.Component<SearchFilterRendererPr
}

private onDownClickHandler() {
this.props.onclickNext();
this.runSearch(this.props.onclickNext);
return;
}

private onUpClickHandler() {
this.props.onclickPrevious();
this.runSearch(this.props.onclickPrevious);
return;
}

private onCloseClickHandler(event: React.MouseEvent) {
this.setState({
hasClicked: false
});
this.props.onFilterChange(this.props.colName, '');
this.onFilterChange(this.props.colName, '');
event.stopPropagation();
return;
}
Expand Down

0 comments on commit 6eeed5a

Please sign in to comment.