Skip to content

Commit 8abb924

Browse files
theibhirMatthewKhouzam
authored andcommitted
Add pin rows in ganttChart
show icon to pin row duplicate rows when pinned show pinned rows on top of the tree/chart Signed-off-by: Yassine Ibhir <yassine.ibhir.ibhir@ericsson.com>
1 parent 47a51cb commit 8abb924

13 files changed

Lines changed: 176 additions & 17 deletions

local-libs/traceviewer-libs/react-components/src/components/abstract-gantt-output-component.tsx

Lines changed: 63 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export type AbstractGanttOutputState = AbstractTreeOutputState & {
7070
| undefined;
7171
selectedRow?: number;
7272
multiSelectedRows?: number[];
73+
pinnedRows?: number[];
7374
selectedMarkerRow?: number;
7475
collapsedNodes: number[];
7576
collapsedMarkerNodes: number[];
@@ -87,7 +88,6 @@ export abstract class AbstractGanttOutputComponent<
8788
> extends AbstractTreeOutputComponent<P, S> {
8889
protected MENU_ID: string;
8990
protected COARSE_RESOLUTION_FACTOR: number;
90-
9191
private totalHeight = 0;
9292
private rowController: TimeGraphRowController;
9393
private markerRowController: TimeGraphRowController;
@@ -98,7 +98,6 @@ export abstract class AbstractGanttOutputComponent<
9898
private markerChartCursors: TimeGraphChartCursors;
9999
private arrowLayer: TimeGraphChartArrows;
100100
private rangeEventsLayer: TimeGraphRangeEventsLayer;
101-
102101
private horizontalContainer: React.RefObject<HTMLDivElement>;
103102
protected chartTreeRef: React.RefObject<HTMLDivElement>;
104103
protected markerTreeRef: React.RefObject<HTMLDivElement>;
@@ -122,6 +121,8 @@ export abstract class AbstractGanttOutputComponent<
122121
this.chartLayer.updateChart(this.filterExpressionsMap());
123122
}, 500);
124123

124+
static NEW_ID_KEY = -3;
125+
125126
constructor(props: P) {
126127
super(props);
127128
this.MENU_ID = props.menu_id;
@@ -437,15 +438,19 @@ export abstract class AbstractGanttOutputComponent<
437438

438439
private updateTotalHeight() {
439440
const visibleEntries = [...this.state.chartTree].filter(entry => this.isVisible(entry));
440-
this.totalHeight = visibleEntries.length * this.props.style.rowHeight;
441+
const pinnedCount = this.state.pinnedRows ? this.state.pinnedRows.length : 0;
442+
this.totalHeight = (visibleEntries.length + pinnedCount) * this.props.style.rowHeight;
441443
this.rowController.totalHeight = this.totalHeight;
442444
}
443445

444446
private isVisible(entry: TimeGraphEntry): boolean {
445447
const { collapsedNodes, emptyNodes } = this.state;
446448

449+
// Convert duplicate ID back to original ID for checking
450+
const originalId = entry.id < -1 ? AbstractGanttOutputComponent.getOriginalId(entry.id) : entry.id;
451+
447452
// Check for empty nodes
448-
if (this.shouldHideEmptyNodes && emptyNodes.includes(entry.id)) {
453+
if (this.shouldHideEmptyNodes && emptyNodes.includes(originalId)) {
449454
return false;
450455
}
451456

@@ -1017,8 +1022,18 @@ export abstract class AbstractGanttOutputComponent<
10171022
}
10181023

10191024
private getTimegraphRowIds() {
1020-
const { chartTree, columns, collapsedNodes } = this.state;
1021-
const rowIds = getAllExpandedNodeIds(listToTree(chartTree, columns), collapsedNodes);
1025+
const { chartTree, columns, collapsedNodes, pinnedRows } = this.state;
1026+
const tree = listToTree(chartTree, columns);
1027+
const regularRowIds = getAllExpandedNodeIds(tree, collapsedNodes);
1028+
1029+
// Add pinned rows at the beginning
1030+
const pinnedRowIds = pinnedRows
1031+
? pinnedRows
1032+
.filter(id => chartTree.some(entry => entry.id === id))
1033+
.map(id => AbstractGanttOutputComponent.createNewId(id))
1034+
: [];
1035+
const rowIds = [...pinnedRowIds, ...regularRowIds];
1036+
10221037
return { rowIds };
10231038
}
10241039

@@ -1039,11 +1054,13 @@ export abstract class AbstractGanttOutputComponent<
10391054

10401055
const strategy = additionalProperties?.filter_query_parameters?.strategy;
10411056
const ids = rowIds ? rowIds : this.getTimegraphRowIds().rowIds;
1057+
const originalIds = ids.map(id => (id < -1 ? AbstractGanttOutputComponent.getOriginalId(id) : id));
1058+
10421059
const { start, end } = range;
10431060
const newRange: TimelineChart.TimeGraphRange = range;
10441061
const nbTimes = Math.ceil(Number(end - start) / resolution) + 1;
10451062
const timeGraphData: TimelineChart.TimeGraphModel = await this.tspDataProvider.getData(
1046-
ids,
1063+
originalIds,
10471064
this.state.chartTree,
10481065
fetchArrows,
10491066
this.props.range,
@@ -1064,6 +1081,12 @@ export abstract class AbstractGanttOutputComponent<
10641081
}
10651082

10661083
let rows = timeGraphData ? timeGraphData.rows : [];
1084+
1085+
rows = rows.map((row, index) => ({
1086+
...row,
1087+
id: ids[index]
1088+
}));
1089+
10671090
let emptyNodes: number[] = [...this.state.emptyNodes];
10681091
if (this.shouldHideEmptyNodes) {
10691092
rows = rows.filter(row => {
@@ -1478,19 +1501,23 @@ export abstract class AbstractGanttOutputComponent<
14781501
* @param {number} id TreeNode id number
14791502
*/
14801503
public onRowClick = (id: number): void => {
1504+
const originalId = id < -1 ? AbstractGanttOutputComponent.getOriginalId(id) : id;
14811505
const rowIndex = getIndexOfNode(
1482-
id,
1506+
originalId,
14831507
listToTree(this.state.chartTree, this.state.columns),
14841508
this.state.collapsedNodes,
14851509
this.state.emptyNodes
14861510
);
1487-
this.chartLayer.selectAndReveal(rowIndex);
1511+
const pinnedCount = this.state.pinnedRows ? this.state.pinnedRows.length : 0;
1512+
1513+
const chartRowIndex = id < -1 ? (this.state.pinnedRows?.indexOf(originalId) ?? -1) : pinnedCount + rowIndex;
1514+
1515+
if (chartRowIndex >= 0) {
1516+
this.chartLayer.selectAndReveal(chartRowIndex);
1517+
}
14881518
if (this.rowController.selectedRow?.id !== id) {
1489-
// This highlights the left side if the row is loading.
14901519
this.setState({ selectedRow: id });
14911520
}
1492-
1493-
// Regular clicking on a row should clear the multi selected rows to only include the clicked row
14941521
this.setState({ multiSelectedRows: [id] });
14951522
};
14961523

@@ -1603,4 +1630,28 @@ export abstract class AbstractGanttOutputComponent<
16031630
);
16041631
this.chartLayer.selectAndReveal(rowIndex);
16051632
}
1633+
1634+
public onPin = (id: number): void => {
1635+
const rows = this.state.pinnedRows ? this.state.pinnedRows.slice() : [];
1636+
// Handle both original ID and duplicate ID
1637+
const originalId = id < -1 ? AbstractGanttOutputComponent.getOriginalId(id) : id;
1638+
const index = rows.indexOf(originalId);
1639+
if (index === -1) {
1640+
rows.push(originalId);
1641+
} else {
1642+
rows.splice(index, 1);
1643+
}
1644+
this.setState({ pinnedRows: rows }, () => {
1645+
this.updateTotalHeight();
1646+
this.chartLayer.update();
1647+
});
1648+
};
1649+
1650+
static createNewId(originalId: number) {
1651+
return originalId ^ this.NEW_ID_KEY;
1652+
}
1653+
1654+
static getOriginalId(newId: number) {
1655+
return newId ^ this.NEW_ID_KEY;
1656+
}
16061657
}

local-libs/traceviewer-libs/react-components/src/components/abstract-xy-output-component.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@ export abstract class AbstractXYOutputComponent<
341341
<EntryTree
342342
entries={this.state.xyTree}
343343
showCheckboxes={true}
344+
showPinIcons={false}
344345
collapsedNodes={this.state.collapsedNodes}
345346
checkedSeries={this.state.checkedSeries}
346347
onToggleCheck={this.onToggleCheck}

local-libs/traceviewer-libs/react-components/src/components/datatree-output-component.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ export class DataTreeOutputComponent extends AbstractOutputComponent<AbstractOut
115115
<EntryTree
116116
entries={this.state.xyTree}
117117
showCheckboxes={false}
118+
showPinIcons={false}
118119
collapsedNodes={this.state.collapsedNodes}
119120
onContextMenu={this.onContextMenu}
120121
onToggleCollapse={this.onToggleCollapse}

local-libs/traceviewer-libs/react-components/src/components/gantt-chart-output-component.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export class GanttChartOutputComponent extends AbstractGanttOutputComponent<
3939
: [],
4040
selectedRow: undefined,
4141
multiSelectedRows: [],
42+
pinnedRows: [],
4243
selectedMarkerRow: undefined,
4344
columns: [],
4445
collapsedMarkerNodes: validateNumArray(this.props.persistChartState?.collapsedMarkerNodes)
@@ -60,6 +61,24 @@ export class GanttChartOutputComponent extends AbstractGanttOutputComponent<
6061
renderTree(): React.ReactNode {
6162
this.onOrderChange = this.onOrderChange.bind(this);
6263
this.onOrderReset = this.onOrderReset.bind(this);
64+
65+
// Add pinned entries at the top, maintaining tree order
66+
const pinnedEntries = this.state.pinnedRows
67+
? this.state.pinnedRows
68+
.map(id => this.state.chartTree.find(entry => entry.id === id))
69+
.filter(entry => entry !== undefined)
70+
.map(entry => ({ ...entry, id: AbstractGanttOutputComponent.createNewId(entry.id), parentId: -1 }))
71+
: [];
72+
const entriesWithPinned = [...pinnedEntries, ...this.state.chartTree];
73+
74+
// Show pin icons on both original and duplicate rows
75+
const extendedPinnedRows = this.state.pinnedRows
76+
? [
77+
...this.state.pinnedRows,
78+
...this.state.pinnedRows.map(id => AbstractGanttOutputComponent.createNewId(id))
79+
]
80+
: [];
81+
6382
// TODO Show header, when we can have entries in-line with timeline-chart
6483
return (
6584
<>
@@ -80,8 +99,11 @@ export class GanttChartOutputComponent extends AbstractGanttOutputComponent<
8099
<EntryTree
81100
collapsedNodes={this.state.collapsedNodes}
82101
showFilter={false}
83-
entries={this.state.chartTree}
102+
entries={entriesWithPinned}
84103
showCheckboxes={false}
104+
showPinIcons={true}
105+
pinnedRows={extendedPinnedRows}
106+
onPin={this.onPin}
85107
onToggleCollapse={this.onToggleCollapse}
86108
onRowClick={this.onRowClick}
87109
onMultipleRowClick={this.onMultipleRowClick}

local-libs/traceviewer-libs/react-components/src/components/generic-xy-output-component.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ export class GenericXYOutputComponent extends AbstractTreeOutputComponent<Generi
178178
<EntryTree
179179
entries={this.state.xyTree}
180180
showCheckboxes={true}
181+
showPinIcons={false}
181182
collapsedNodes={this.state.collapsedNodes}
182183
checkedSeries={this.state.checkedSeries}
183184
onToggleCheck={this.onToggleCheck}

local-libs/traceviewer-libs/react-components/src/components/timegraph-output-component.tsx

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export class TimegraphOutputComponent extends AbstractGanttOutputComponent {
1919
: [],
2020
selectedRow: undefined,
2121
multiSelectedRows: [],
22+
pinnedRows: [],
2223
selectedMarkerRow: undefined,
2324
columns: [],
2425
collapsedMarkerNodes: validateNumArray(this.props.persistChartState?.collapsedMarkerNodes)
@@ -35,7 +36,24 @@ export class TimegraphOutputComponent extends AbstractGanttOutputComponent {
3536
renderTree(): React.ReactNode {
3637
this.onOrderChange = this.onOrderChange.bind(this);
3738
this.onOrderReset = this.onOrderReset.bind(this);
38-
// TODO Show header, when we can have entries in-line with timeline-chart
39+
40+
// Add pinned entries at the top, maintaining tree order
41+
const pinnedEntries = this.state.pinnedRows
42+
? this.state.pinnedRows
43+
.map(id => this.state.chartTree.find(entry => entry.id === id))
44+
.filter(entry => entry !== undefined)
45+
.map(entry => ({ ...entry, id: AbstractGanttOutputComponent.createNewId(entry.id), parentId: -1 }))
46+
: [];
47+
const entriesWithPinned = [...pinnedEntries, ...this.state.chartTree];
48+
49+
// Show pin icons on both original and duplicate rows
50+
const extendedPinnedRows = this.state.pinnedRows
51+
? [
52+
...this.state.pinnedRows,
53+
...this.state.pinnedRows.map(id => AbstractGanttOutputComponent.createNewId(id))
54+
]
55+
: [];
56+
3957
return (
4058
<>
4159
<div
@@ -55,8 +73,11 @@ export class TimegraphOutputComponent extends AbstractGanttOutputComponent {
5573
<EntryTree
5674
collapsedNodes={this.state.collapsedNodes}
5775
showFilter={false}
58-
entries={this.state.chartTree}
76+
entries={entriesWithPinned}
5977
showCheckboxes={false}
78+
showPinIcons={true}
79+
pinnedRows={extendedPinnedRows}
80+
onPin={this.onPin}
6081
onToggleCollapse={this.onToggleCollapse}
6182
onRowClick={this.onRowClick}
6283
onMultipleRowClick={this.onMultipleRowClick}

local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/entry-tree.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,16 @@ interface EntryTreeProps {
1010
checkedSeries: number[];
1111
showCheckboxes: boolean;
1212
showCloseIcons: boolean;
13+
showPinIcons?: boolean;
14+
pinnedRows?: number[];
1315
selectedRow?: number;
1416
multiSelectedRows?: number[];
1517
collapsedNodes: number[];
1618
emptyNodes: number[];
1719
hideEmptyNodes: boolean;
1820
showFilter: boolean;
1921
onToggleCheck: (ids: number[]) => void;
22+
onPin?: (id: number) => void;
2023
onRowClick: (id: number) => void;
2124
onMultipleRowClick?: (id: number, isShiftClicked?: boolean) => void;
2225
onContextMenu: (event: React.MouseEvent<HTMLDivElement>, id: number) => void;
@@ -54,6 +57,7 @@ export class EntryTree extends React.Component<EntryTreeProps> {
5457
this.props.collapsedNodes !== nextProps.collapsedNodes ||
5558
this.props.selectedRow !== nextProps.selectedRow ||
5659
this.props.multiSelectedRows !== nextProps.multiSelectedRows ||
60+
this.props.pinnedRows !== nextProps.pinnedRows ||
5761
this.props.hideEmptyNodes !== nextProps.hideEmptyNodes ||
5862
this.props.emptyNodes !== nextProps.emptyNodes;
5963

local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/icons.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import {
99
faSort,
1010
faSortDown,
1111
faSortUp,
12-
faTimes
12+
faTimes,
13+
faThumbtack
1314
} from '@fortawesome/free-solid-svg-icons';
1415

1516
interface iconsShape {
@@ -22,6 +23,7 @@ interface iconsShape {
2223
sortDown: React.ReactNode;
2324
sortUp: React.ReactNode;
2425
close: React.ReactNode;
26+
pin: React.ReactNode;
2527
}
2628

2729
const icons: iconsShape = {
@@ -33,7 +35,8 @@ const icons: iconsShape = {
3335
sort: <FontAwesomeIcon icon={faSort} />,
3436
sortDown: <FontAwesomeIcon icon={faSortDown} />,
3537
sortUp: <FontAwesomeIcon icon={faSortUp} />,
36-
close: <FontAwesomeIcon icon={faTimes} />
38+
close: <FontAwesomeIcon icon={faTimes} />,
39+
pin: <FontAwesomeIcon icon={faThumbtack} />
3740
};
3841

3942
export default icons;

local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/table-body.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@ interface TableBodyProps {
99
collapsedNodes: number[];
1010
isCheckable: boolean;
1111
isClosable: boolean;
12+
isPinnable?: boolean;
13+
pinnedRows?: number[];
1214
getCheckedStatus: (id: number) => number;
1315
onToggleCollapse: (id: number) => void;
1416
onRowClick: (id: number) => void;
1517
onMultipleRowClick?: (id: number, isShiftClicked?: boolean) => void;
1618
onClose: (id: number) => void;
1719
onToggleCheck: (id: number) => void;
20+
onPin?: (id: number) => void;
1821
onContextMenu: (event: React.MouseEvent<HTMLDivElement>, id: number) => void;
1922
hideFillers?: boolean;
2023
}

local-libs/traceviewer-libs/react-components/src/components/utils/filter-tree/table-cell.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ interface TableCellProps {
55
node: TreeNode;
66
index: number;
77
children?: React.ReactNode | React.ReactNode[];
8+
pinButton?: React.ReactNode;
89
}
910

1011
export class TableCell extends React.Component<TableCellProps> {

0 commit comments

Comments
 (0)