From 18566404a980b5d00bb7ec98ba4d61dc66323e84 Mon Sep 17 00:00:00 2001 From: Bernd Hufmann Date: Mon, 15 May 2023 07:39:38 -0400 Subject: [PATCH 1/2] Support TIME_RANGE columns in Time Graph output component With this, it's possible to provide time range column to select the time ranges provided in such columns. Signed-off-by: Bernd Hufmann --- .../components/timegraph-output-component.tsx | 147 ++++++++++++++++-- 1 file changed, 132 insertions(+), 15 deletions(-) diff --git a/packages/react-components/src/components/timegraph-output-component.tsx b/packages/react-components/src/components/timegraph-output-component.tsx index 0a76d8121..2fbdf31f4 100644 --- a/packages/react-components/src/components/timegraph-output-component.tsx +++ b/packages/react-components/src/components/timegraph-output-component.tsx @@ -1,37 +1,39 @@ +import { faSpinner } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { isEqual } from 'lodash'; import * as React from 'react'; +import { TimeGraphAnnotationComponent } from 'timeline-chart/lib/components/time-graph-annotation'; import { TimeGraphComponent } from 'timeline-chart/lib/components/time-graph-component'; import { TimeGraphStateComponent, TimeGraphStateStyle } from 'timeline-chart/lib/components/time-graph-state'; import { TimeGraphChart, TimeGraphChartProviders } from 'timeline-chart/lib/layer/time-graph-chart'; import { TimeGraphChartArrows } from 'timeline-chart/lib/layer/time-graph-chart-arrows'; -import { TimeGraphRangeEventsLayer } from 'timeline-chart/lib/layer/time-graph-range-events-layer'; import { TimeGraphChartCursors } from 'timeline-chart/lib/layer/time-graph-chart-cursors'; -import { TimeGraphMarkersChartCursors } from 'timeline-chart/lib/layer/time-graph-markers-chart-cursors'; import { TimeGraphChartGrid } from 'timeline-chart/lib/layer/time-graph-chart-grid'; import { TimeGraphChartSelectionRange } from 'timeline-chart/lib/layer/time-graph-chart-selection-range'; +import { TimeGraphMarkersChartCursors } from 'timeline-chart/lib/layer/time-graph-markers-chart-cursors'; +import { TimeGraphRangeEventsLayer } from 'timeline-chart/lib/layer/time-graph-range-events-layer'; import { TimeGraphVerticalScrollbar } from 'timeline-chart/lib/layer/time-graph-vertical-scrollbar'; import { TimelineChart } from 'timeline-chart/lib/time-graph-model'; import { TimeGraphRowController } from 'timeline-chart/lib/time-graph-row-controller'; +import { Signals, signalManager } from 'traceviewer-base/lib/signals/signal-manager'; +import { convertColorStringToHexNumber } from 'traceviewer-base/lib/utils/convert-color-string-to-hex'; +import hash from 'traceviewer-base/lib/utils/value-hash'; +import { Entry } from 'tsp-typescript-client'; import { QueryHelper } from 'tsp-typescript-client/lib/models/query/query-helper'; import { ResponseStatus } from 'tsp-typescript-client/lib/models/response/responses'; +import { OutputElementStyle } from 'tsp-typescript-client/lib/models/styles'; import { TimeGraphEntry } from 'tsp-typescript-client/lib/models/timegraph'; -import { signalManager, Signals } from 'traceviewer-base/lib/signals/signal-manager'; import { AbstractOutputProps } from './abstract-output-component'; import { AbstractTreeOutputComponent, AbstractTreeOutputState } from './abstract-tree-output-component'; import { StyleProperties } from './data-providers/style-properties'; import { StyleProvider } from './data-providers/style-provider'; 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, getIndexOfNode, validateNumArray } 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'; -import { Entry } from 'tsp-typescript-client'; -import { isEqual } from 'lodash'; -import { convertColorStringToHexNumber } from 'traceviewer-base/lib/utils/convert-color-string-to-hex'; -import { faSpinner } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { EntryTree } from './utils/filter-tree/entry-tree'; +import { getAllExpandedNodeIds, getIndexOfNode, listToTree, validateNumArray } from './utils/filter-tree/utils'; +import { ReactTimeGraphContainer } from './utils/timegraph-container-component'; +import { Item, ItemParams, Menu, useContextMenu } from 'react-contexify'; +import { DataType } from 'tsp-typescript-client/lib/models/data-type'; type TimegraphOutputProps = AbstractOutputProps & { addWidgetResizeHandler: (handler: () => void) => void; @@ -54,6 +56,8 @@ type TimegraphOutputState = AbstractTreeOutputState & { const COARSE_RESOLUTION_FACTOR = 8; // resolution factor to use for first (coarse) update +const MENU_ID = 'datatree.context.menuId '; + export class TimegraphOutputComponent extends AbstractTreeOutputComponent { private totalHeight = 0; private rowController: TimeGraphRowController; @@ -252,7 +256,13 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent 0) { headers.forEach(header => { - columns.push({ title: header.name, sortable: true, resizable: true, tooltip: header.tooltip }); + columns.push({ + title: header.name, + sortable: true, + resizable: true, + tooltip: header.tooltip, + dataType: header.dataType + }); }); } else { columns.push({ title: 'Name', sortable: true }); @@ -435,6 +445,7 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent + {this.renderContextMenu()}
@@ -473,6 +485,111 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent { + if (col.dataType === DataType.TIME_RANGE) { + timeRanges.push(col.title); + } + }); + return ( + + {' '} + { + + {timeRanges && timeRanges.length > 0 ? ( + timeRanges.map(key => ( + + Select {key} + + )) + ) : ( + <> + )} + + } + + ); + } + + protected handleItemClick = (args: ItemParams): void => { + const tooltip: { [key: string]: string } = args.props.data; + const min = tooltip[args.event.currentTarget.id]; + if (min !== undefined) { + let rx = /\[(\d*),.*/g; + let arr = rx.exec(min); + let start: bigint | undefined = undefined; + if (arr) { + start = BigInt(arr[1]) - this.props.unitController.offset; + } + rx = /.*,(\d*)\]/g; + arr = rx.exec(min); + let end: bigint | undefined = undefined; + if (arr) { + end = BigInt(arr[1]) - this.props.unitController.offset; + } + if (start !== undefined && end !== undefined) { + this.props.unitController.selectionRange = { + start, + end + }; + } + } + }; + + private onContextMenu = (event: React.MouseEvent, id: number): void => { + event.preventDefault(); + event.stopPropagation(); + this.doContextMenu(event, id); + }; + + private async doContextMenu(event: React.MouseEvent, id: number): Promise { + if (this.state.timegraphTree) { + const timeProperties: { [key: string]: string } = {}; + const entry = this.state.timegraphTree.find(e => e.id === id); + if (entry && this.state.columns && this.state.columns.length > 0) { + const cols = this.state.columns; + for (let i = 0; i < cols.length; i++) { + if (cols[i].dataType === DataType.TIME_RANGE) { + timeProperties[cols[i].title] = entry.labels[i]; + } + } + } + + if (Object.keys(timeProperties).length > 0) { + const { show } = useContextMenu({ + id: MENU_ID + this.props.outputDescriptor.id + }); + show(event, { + props: { + data: timeProperties + }, + position: this.getMenuPosition(event) + }); + } + } + } + + private getMenuPosition(event: React.MouseEvent): { x: number; y: number } { + const refNode = this.treeRef.current; + if (refNode) { + return { + // Compute position relative to treeRef + x: event.clientX - refNode.getBoundingClientRect().left, + y: event.clientY - refNode.getBoundingClientRect().top + }; + } + return { + x: 0, + y: 0 + }; + } + renderYAxis(): React.ReactNode { return undefined; } From f8e4177800cb743be7a9288c58c8b912a7f9df06 Mon Sep 17 00:00:00 2001 From: Bernd Hufmann Date: Mon, 15 May 2023 08:56:42 -0400 Subject: [PATCH 2/2] WIP: Allow custom context-sensitive menu in Time Graph output components Signed-off-by: Bernd Hufmann --- .../components/timegraph-output-component.tsx | 79 ++++++++++++++----- 1 file changed, 59 insertions(+), 20 deletions(-) diff --git a/packages/react-components/src/components/timegraph-output-component.tsx b/packages/react-components/src/components/timegraph-output-component.tsx index 2fbdf31f4..b4c16c63b 100644 --- a/packages/react-components/src/components/timegraph-output-component.tsx +++ b/packages/react-components/src/components/timegraph-output-component.tsx @@ -83,6 +83,8 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent this.doHandleSelectionChangedSignal(payload); private pendingSelection: TimeGraphEntry | undefined; + private customRowMenus: string[] = []; + constructor(props: TimegraphOutputProps) { super(props); this.state = { @@ -211,6 +213,8 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent )} + {this.customRowMenus && this.customRowMenus.length > 0 ? ( + this.customRowMenus.map(key => ( + + {key} + + )) + ) : ( + <> + )} } @@ -518,26 +531,40 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent { - const tooltip: { [key: string]: string } = args.props.data; - const min = tooltip[args.event.currentTarget.id]; - if (min !== undefined) { - let rx = /\[(\d*),.*/g; - let arr = rx.exec(min); - let start: bigint | undefined = undefined; - if (arr) { - start = BigInt(arr[1]) - this.props.unitController.offset; - } - rx = /.*,(\d*)\]/g; - arr = rx.exec(min); - let end: bigint | undefined = undefined; - if (arr) { - end = BigInt(arr[1]) - this.props.unitController.offset; + const data: { [key: string]: unknown } = args.props.data; + if (data['ranges']) { + const tooltip: { [key: string]: string } = data['ranges'] as { [key: string]: string }; + const min = tooltip[args.event.currentTarget.id]; + if (min !== undefined) { + let rx = /\[(\d*),.*/g; + let arr = rx.exec(min); + let start: bigint | undefined = undefined; + if (arr) { + start = BigInt(arr[1]) - this.props.unitController.offset; + } + rx = /.*,(\d*)\]/g; + arr = rx.exec(min); + let end: bigint | undefined = undefined; + if (arr) { + end = BigInt(arr[1]) - this.props.unitController.offset; + } + if (start !== undefined && end !== undefined) { + this.props.unitController.selectionRange = { + start, + end + }; + } } - if (start !== undefined && end !== undefined) { - this.props.unitController.selectionRange = { - start, - end - }; + } + }; + + protected handleCustomItemClick = (args: ItemParams): void => { + const data: { [key: string]: unknown } = args.props.data; + if (data['custom']) { + const tooltip: { [key: string]: string } = data['custom'] as { [key: string]: string }; + const min = tooltip[args.event.currentTarget.id]; + if (min !== undefined) { + console.log('Clicked on ' + min); } } }; @@ -550,6 +577,8 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent, id: number): Promise { if (this.state.timegraphTree) { + const data: { [key: string]: unknown } = {}; + const timeProperties: { [key: string]: string } = {}; const entry = this.state.timegraphTree.find(e => e.id === id); if (entry && this.state.columns && this.state.columns.length > 0) { @@ -562,12 +591,22 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent 0) { + data['ranges'] = timeProperties; + } + + if (this.customRowMenus.length > 0) { + const menuProperties: { [key: string]: string } = {}; + this.customRowMenus.forEach(menu => (menuProperties[menu] = menu)); + data['custom'] = menuProperties; + } + + if (Object.keys(data).length > 0) { const { show } = useContextMenu({ id: MENU_ID + this.props.outputDescriptor.id }); show(event, { props: { - data: timeProperties + data: data }, position: this.getMenuPosition(event) });