Skip to content

Commit fe0833d

Browse files
bhufmannPatrickTasse
authored andcommitted
Support for searching for previous event in the Events Table
- Add a 'previous' button in the column search cells right beside the 'next' button - Use keyboard short-cut SHIFT+ENTER to search previous event fixes #412 Signed-off-by: Bernd Hufmann <[email protected]> Signed-off-by: Patrick Tasse <[email protected]>
1 parent cb7d812 commit fe0833d

File tree

2 files changed

+86
-17
lines changed

2 files changed

+86
-17
lines changed

packages/react-components/src/components/table-output-component.tsx

+72-15
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ type TableOutputProps = AbstractOutputProps & {
2424
tableWidth?: string;
2525
};
2626

27+
enum Direction {
28+
NEXT,
29+
PREVIOUS
30+
}
31+
2732
export class TableOutputComponent extends AbstractOutputComponent<TableOutputProps, TableOuputState> {
2833
private debugMode = false;
2934
private columnIds: Array<number> = [];
@@ -83,7 +88,7 @@ export class TableOutputComponent extends AbstractOutputComponent<TableOutputPro
8388
this.onModelUpdated = this.onModelUpdated.bind(this);
8489
this.onKeyDown = this.onKeyDown.bind(this);
8590
this.searchEvents = this.searchEvents.bind(this);
86-
this.findNextMatchedEvent = this.findNextMatchedEvent.bind(this);
91+
this.findMatchedEvent = this.findMatchedEvent.bind(this);
8792
}
8893

8994
renderMainArea(): React.ReactNode {
@@ -314,7 +319,8 @@ export class TableOutputComponent extends AbstractOutputComponent<TableOutputPro
314319
floatingFilterComponentParams: {
315320
suppressFilterButton: true,
316321
onFilterChange: this.searchEvents,
317-
onclickNext: this.findNextMatchedEvent,
322+
onclickNext: () => this.findMatchedEvent(Direction.NEXT),
323+
onclickPrevious: () => this.findMatchedEvent(Direction.PREVIOUS),
318324
colName: columnHeader.id.toString()
319325
},
320326
icons: {
@@ -409,7 +415,7 @@ export class TableOutputComponent extends AbstractOutputComponent<TableOutputPro
409415
return lines[0].index;
410416
}
411417

412-
private fetchAdditionalParams(isFiltered = false): ({ [key: string]: any }) {
418+
private fetchAdditionalParams(direction?: Direction): ({ [key: string]: any }) {
413419
let additionalParams: { [key: string]: any } = {};
414420
const filterObj: { [key: number]: string } = {};
415421
if (this.filterModel) {
@@ -419,8 +425,10 @@ export class TableOutputComponent extends AbstractOutputComponent<TableOutputPro
419425
});
420426
additionalParams = {
421427
['table_search_expressions']: filterObj,
422-
['isFiltered']: isFiltered
423428
};
429+
if (direction !== undefined) {
430+
additionalParams['table_search_direction'] = Direction[direction];
431+
}
424432
}
425433
return additionalParams;
426434

@@ -490,11 +498,11 @@ export class TableOutputComponent extends AbstractOutputComponent<TableOutputPro
490498
}
491499
}
492500

493-
private async findNextMatchIndex(currRowIndex: number) {
501+
private async findMatchIndex(currRowIndex: number, direction = Direction.NEXT) {
494502
const traceUUID = this.props.traceId;
495503
const tspClient = this.props.tspClient;
496504
const outputId = this.props.outputDescriptor.id;
497-
const additionalParams = this.fetchAdditionalParams(true);
505+
const additionalParams = this.fetchAdditionalParams(direction);
498506
const tspClientResponse = await tspClient.fetchTableLines(traceUUID, outputId, QueryHelper.tableQuery(this.columnIds, currRowIndex, 1, additionalParams));
499507
const lineResponse = tspClientResponse.getModel();
500508
if (!tspClientResponse.isOk() || !lineResponse) {
@@ -508,13 +516,56 @@ export class TableOutputComponent extends AbstractOutputComponent<TableOutputPro
508516
return lines[0].index;
509517
}
510518

511-
private async findNextMatchedEvent() {
519+
private async findMatchedEvent(direction: Direction) {
512520
let isFound = false;
513521
if (this.gridApi) {
514-
this.gridApi.deselectAll();
522+
523+
// make sure that both index are either both -1 or both have a valid number
524+
if (this.selectStartIndex !== -1 && this.selectEndIndex === -1) {
525+
this.selectEndIndex = this.selectStartIndex;
526+
}
527+
if (this.selectEndIndex !== -1 && this.selectStartIndex === -1) {
528+
this.selectStartIndex = this.selectStartIndex;
529+
}
530+
531+
let currRowIndex = 0;
532+
if (this.selectStartIndex !== -1) {
533+
if (direction === Direction.NEXT) {
534+
currRowIndex = Math.max(this.selectStartIndex, this.selectEndIndex) + 1;
535+
} else {
536+
currRowIndex = Math.min(this.selectStartIndex, this.selectEndIndex) - 1;
537+
}
538+
} else if (direction === Direction.PREVIOUS) {
539+
// no backward search if there is no selection
540+
return;
541+
}
542+
543+
let rowNodes: RowNode[] = [];
515544
this.gridApi.forEachNode(rowNode => {
516-
if (rowNode.rowIndex > Math.max(this.selectStartIndex, this.selectEndIndex) && rowNode.data
517-
&& rowNode.data['isMatched'] && !isFound) {
545+
rowNodes.push(rowNode);
546+
});
547+
548+
if (direction === Direction.PREVIOUS) {
549+
rowNodes = rowNodes.reverse();
550+
}
551+
552+
this.gridApi.deselectAll();
553+
// consider only rows starting from the current row index and contiguous rows after that
554+
let currRowIndexFound = false;
555+
rowNodes.forEach(rowNode => {
556+
if (rowNode.rowIndex === currRowIndex) {
557+
currRowIndexFound = true;
558+
// update current row index to next/previous contiguous row
559+
if (direction === Direction.NEXT) {
560+
currRowIndex++;
561+
} else {
562+
currRowIndex--;
563+
}
564+
} else {
565+
// non-contiguous row found, stop searching in cache
566+
currRowIndexFound = false;
567+
}
568+
if (currRowIndexFound && !isFound && rowNode.data && rowNode.data['isMatched']) {
518569
this.gridApi?.ensureIndexVisible(rowNode.rowIndex);
519570
this.selectStartIndex = this.selectEndIndex = rowNode.rowIndex;
520571
this.handleRowSelectionChange();
@@ -524,14 +575,20 @@ export class TableOutputComponent extends AbstractOutputComponent<TableOutputPro
524575
});
525576

526577
if (isFound) {
578+
// match found in cache
527579
return;
528580
}
581+
// find match outside the cache
582+
if (currRowIndex >= 0) {
583+
const lineIndex = await this.findMatchIndex(currRowIndex, direction);
584+
if (lineIndex !== undefined) {
585+
this.gridApi.ensureIndexVisible(lineIndex);
586+
this.selectStartIndex = this.selectEndIndex = lineIndex;
587+
}
588+
}
529589

530-
const currRowIndex = this.gridApi?.getLastDisplayedRow() + 1;
531-
const lineIndex = await this.findNextMatchIndex(currRowIndex);
532-
if (lineIndex) {
533-
this.gridApi.ensureIndexVisible(lineIndex);
534-
this.selectStartIndex = this.selectEndIndex = lineIndex;
590+
// apply new or previous selection
591+
if (this.selectStartIndex !== -1 && this.selectEndIndex !== -1) {
535592
this.handleRowSelectionChange();
536593
this.enableIndexSelection = true;
537594
this.selectRows();

packages/react-components/src/components/table-renderer-components.tsx

+14-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { faAngleDown, faSearch, faSpinner, faTimes } from '@fortawesome/free-solid-svg-icons';
1+
import { faAngleDown, faAngleUp, faSearch, faSpinner, faTimes } from '@fortawesome/free-solid-svg-icons';
22
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
33
import { ICellRendererParams, IFloatingFilterParams } from 'ag-grid-community';
44
import debounce from 'lodash.debounce';
@@ -12,6 +12,7 @@ type CellRendererProps = ICellRendererParams & {
1212
type SearchFilterRendererProps = IFloatingFilterParams & {
1313
onFilterChange: (colName: string, filterValue: string) => void;
1414
onclickNext: () => void;
15+
onclickPrevious: () => void;
1516
colName: string;
1617
};
1718

@@ -90,6 +91,7 @@ export class SearchFilterRenderer extends React.Component<SearchFilterRendererPr
9091
this.onMouseLeaveHandler = this.onMouseLeaveHandler.bind(this);
9192
this.onClickHandler = this.onClickHandler.bind(this);
9293
this.onDownClickHandler = this.onDownClickHandler.bind(this);
94+
this.onUpClickHandler = this.onUpClickHandler.bind(this);
9395
this.onCloseClickHandler = this.onCloseClickHandler.bind(this);
9496
this.onKeyDownEvent = this.onKeyDownEvent.bind(this);
9597

@@ -110,14 +112,19 @@ export class SearchFilterRenderer extends React.Component<SearchFilterRendererPr
110112
<input type="text" autoFocus={true} onKeyDown={this.onKeyDownEvent} onInput={this.onInputBoxChanged} style={{ width: '50%', margin: '10px' }} />
111113
<FontAwesomeIcon className='hoverClass' icon={faTimes} style={{ marginTop: '20px' }} onClick={this.onCloseClickHandler} />
112114
<FontAwesomeIcon className='hoverClass' icon={faAngleDown} style={{ marginLeft: '10px', marginTop: '20px' }} onClick={this.onDownClickHandler} />
115+
<FontAwesomeIcon className='hoverClass' icon={faAngleUp} style={{ marginLeft: '10px', marginTop: '20px' }} onClick={this.onUpClickHandler} />
113116
</div>}
114117
</div>
115118
);
116119
}
117120

118121
private onKeyDownEvent(event: React.KeyboardEvent) {
119122
if (event.key === 'Enter') {
120-
this.props.onclickNext();
123+
if (event.shiftKey) {
124+
this.props.onclickPrevious();
125+
} else {
126+
this.props.onclickNext();
127+
}
121128
} else if (event.key === 'Escape') {
122129
this.setState({
123130
hasClicked: false
@@ -158,6 +165,11 @@ export class SearchFilterRenderer extends React.Component<SearchFilterRendererPr
158165
return;
159166
}
160167

168+
private onUpClickHandler() {
169+
this.props.onclickPrevious();
170+
return;
171+
}
172+
161173
private onCloseClickHandler(event: React.MouseEvent) {
162174
this.setState({
163175
hasClicked: false

0 commit comments

Comments
 (0)