Skip to content

Commit

Permalink
Select chart components from a selection in the events table
Browse files Browse the repository at this point in the history
Select corresponding chart components from the events table.

Clicking on a line in the events table will cause a chart like "Thread
Status" to scroll vertically to the row which has the element that
corresponds to the clicked event.

For each line in the timegraph tree, parse the metadata in the
TimeGraphEntry received from the server and try to find matches with the
key / values pairs of the clicked event. It will select the entry which
has the most matches.

When extending the selection, only the last selected event will be
used to broadcast the selection (multi-line selection).

This PR requires the following pull requests:
https://git.eclipse.org/r/c/tracecompass.incubator/org.eclipse.tracecompass.incubator/+/193246
eclipse-cdt-cloud/tsp-typescript-client#63
eclipse-cdt-cloud/timeline-chart#204

The corresponding Trace Server Protocol (TSP) update:
eclipse-cdt-cloud/trace-server-protocol#83

Signed-off-by: Rodrigo Pinto <rodrigo.pinto@calian.ca>
Signed-off-by: Bernd Hufmann <bernd.hufmann@ericsson.com>
  • Loading branch information
Rodrigoplp-work authored and bhufmann committed Jun 17, 2022
1 parent 94032b7 commit e27fb4a
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export class TableOutputComponent extends AbstractOutputComponent<TableOutputPro
renderMainArea(): React.ReactNode {
return <div id={this.props.traceId + this.props.outputDescriptor.id + 'focusContainer'}
tabIndex={-1}
onFocus={event=>this.checkFocus(event)}
onFocus={event => this.checkFocus(event)}
className={this.props.backgroundTheme === 'light' ? 'ag-theme-balham' : 'ag-theme-balham-dark'}
style={{ height: this.props.style.height, width: this.props.outputWidth }}>
<AgGridReact
Expand Down Expand Up @@ -166,8 +166,7 @@ export class TableOutputComponent extends AbstractOutputComponent<TableOutputPro
const mouseEvent = event.event as MouseEvent;
const gridApi = event.api;
const rowIndex = event.rowIndex;
const itemPropsObj = this.fetchItemProperties(columns, data);
signalManager().fireTooltipSignal(itemPropsObj);
const itemPropsObj: { [key: string]: string } | undefined = this.fetchItemProperties(columns, data);

const currTimestamp = (this.timestampCol && data) ? data[this.timestampCol] : undefined;
this.enableIndexSelection = true;
Expand Down Expand Up @@ -196,7 +195,10 @@ export class TableOutputComponent extends AbstractOutputComponent<TableOutputPro
this.startTimestamp = this.endTimestamp = BigInt(currTimestamp);
}
}
this.handleRowSelectionChange();
// Notfiy selection changed
this.handleRowSelectionChange(itemPropsObj);
// Notfiy properties changed
signalManager().fireTooltipSignal(itemPropsObj);
}

private fetchItemProperties(columns: Column[], data: any) {
Expand Down Expand Up @@ -392,11 +394,11 @@ export class TableOutputComponent extends AbstractOutputComponent<TableOutputPro
}
}

private handleRowSelectionChange() {
private handleRowSelectionChange(load?: any | undefined) {
if (this.timestampCol) {
const startTimestamp = String(this.startTimestamp);
const endTimestamp = String(this.endTimestamp);
const payload = { startTimestamp, endTimestamp };
const payload = { startTimestamp, endTimestamp, load };
this.prevStartTimestamp = BigInt(startTimestamp);
this.eventSignal = true;
signalManager().fireSelectionChangedSignal(payload);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { TspDataProvider } from './data-providers/tsp-data-provider';
import { ReactTimeGraphContainer } from './utils/timegraph-container-component';
import { OutputElementStyle } from 'tsp-typescript-client/lib/models/styles';
import { EntryTree } from './utils/filter-tree/entry-tree';
import { listToTree, getAllExpandedNodeIds } from './utils/filter-tree/utils';
import { listToTree, getAllExpandedNodeIds, getIndexOfNode } from './utils/filter-tree/utils';
import hash from 'traceviewer-base/lib/utils/value-hash';
import ColumnHeader from './utils/filter-tree/column-header';
import { TimeGraphAnnotationComponent } from 'timeline-chart/lib/components/time-graph-annotation';
Expand All @@ -41,6 +41,7 @@ type TimegraphOutputState = AbstractOutputState & {
collapsedNodes: number[];
collapsedMarkerNodes: number[];
columns: ColumnHeader[];
dataRows: TimelineChart.TimeGraphRowModel[];
};

const COARSE_RESOLUTION_FACTOR = 8; // resolution factor to use for first (coarse) update
Expand All @@ -67,6 +68,7 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent<Timegr
private selectedElement: TimeGraphStateComponent | undefined;
private selectedMarkerCategories: string[] | undefined = undefined;
private onSelectionChanged = (payload: { [key: string]: string; }) => this.doHandleSelectionChangedSignal(payload);
private pendingSelection: TimeGraphEntry | undefined;

constructor(props: TimegraphOutputProps) {
super(props);
Expand All @@ -78,7 +80,8 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent<Timegr
collapsedNodes: [],
columns: [],
collapsedMarkerNodes: [],
optionsDropdownOpen: false
optionsDropdownOpen: false,
dataRows: []
};
this.selectedMarkerCategories = this.props.markerCategories;
this.onToggleCollapse = this.onToggleCollapse.bind(this);
Expand Down Expand Up @@ -124,6 +127,7 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent<Timegr

this.markersChartLayer = new TimeGraphChart('timeGraphChart', markersProvider, this.markerRowController);
this.chartLayer.onSelectedStateChanged(model => {
this.pendingSelection = undefined;
if (model) {
this.selectedElement = this.chartLayer.getStateById(model.id);
} else {
Expand Down Expand Up @@ -287,6 +291,19 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent<Timegr
if (startTimestamp !== undefined && endTimestamp !== undefined) {
const selectionRangeStart = BigInt(startTimestamp) - offset;
const selectionRangeEnd = BigInt(endTimestamp) - offset;
const foundElement = this.findElement(payload);

// Scroll vertically
if (foundElement) {
// Expand parent
if (this.expandParents(foundElement)) {
this.selectAndReveal(foundElement);
} else {
this.pendingSelection = foundElement;
}
}

// Scroll horizontally
this.props.unitController.selectionRange = {
start: selectionRangeStart,
end: selectionRangeEnd
Expand All @@ -295,6 +312,48 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent<Timegr
}
}

/**
* For each line in the tree (this.state.timegraphTree), parse the metadata and try to find
* matches with the key / values pairs of the selected event.
* It counts the amount of metadata matches and returns the TimeGraphEntry with the greatest amount,
* which is the most likely result.
*
* @params payload
* Object with information about the selected event.
* @return
* Correspondent TimeGraphEntry from this.state.timegraphTree
*/
private findElement(payload: { [key: string]: string | { [key: string]: string }}): TimeGraphEntry | undefined {
let element: TimeGraphEntry | undefined = undefined;
let max = 0;
if (payload && payload.load) {
this.state.timegraphTree.forEach(el => {
if (el.metadata) {
let cnt = 0;
Object.entries(el.metadata).forEach(([key, values]) => {
if (typeof (payload.load) !== 'string') {
const val = payload.load[key];
if (val !== undefined) {
const num = Number(val);
// at least one value in array needs to match
const result = values.find((value: string | number) => (num !== undefined && num === value) || (val === value));
if (result !== undefined) {
cnt++;
}
}
}
});
if (cnt > max) {
max = cnt;
element = el;
}
}
});

}
return element;
}

private getMarkersLayerHeight() {
const rowHeight = 20;
const scrollbarHeight = 10;
Expand Down Expand Up @@ -535,6 +594,14 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent<Timegr
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
document.getElementById(this.props.traceId + this.props.outputDescriptor.id + 'handleSpinner')!.style.visibility = 'hidden';
}
this.setState({ dataRows: timeGraphData.rows });

// Apply the pending selection here since the row provider had been called before this method.
if (this.pendingSelection) {
const foundElement = this.pendingSelection;
this.pendingSelection = undefined;
this.selectAndReveal(foundElement);
}
return {
rows: timeGraphData ? timeGraphData.rows : [],
range: newRange,
Expand Down Expand Up @@ -815,4 +882,34 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent<Timegr
}
return undefined;
}

private expandParents(entry: TimeGraphEntry) {
let foundNode = this.state.timegraphTree.find(node => node.id === entry?.id);
if (foundNode) {
let parentId: number | undefined = foundNode.parentId;
const ids: number[] = [];
while (parentId && parentId >= 0) {
ids.push(parentId);
foundNode = this.state.timegraphTree.find(node => node.id === parentId);
parentId = foundNode?.parentId;
}

let newList = [...this.state.collapsedNodes];
ids.forEach(parentIds => {
const exist = this.state.collapsedNodes.find(expandId => expandId === parentIds);
if (exist !== undefined) {
newList = newList.filter(collapsed => parentIds !== collapsed);
}
});
const retVal = newList.length === this.state.collapsedNodes.length;
this.setState({ collapsedNodes: newList }, this.updateTotalHeight);
return retVal;
}
}

private selectAndReveal(item: TimeGraphEntry) {
const rowIndex = getIndexOfNode(item.id, listToTree(this.state.timegraphTree, this.state.columns), this.state.collapsedNodes);
this.chartLayer.selectAndReveal(rowIndex);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,8 @@ export const getAllExpandedNodeIds = (nodes: TreeNode[],collapsedNodes: number[]
});
return visibleIds;
};

export const getIndexOfNode = (id: number, nodes: TreeNode[], collapsedNodes: number[]): number => {
const ids = getAllExpandedNodeIds(nodes, collapsedNodes);
return ids.findIndex(eId => eId === id);
};

0 comments on commit e27fb4a

Please sign in to comment.