diff --git a/viewer-prototype/src/browser/style/output-components-style.css b/viewer-prototype/src/browser/style/output-components-style.css index 7e704a08e..853b64e2f 100644 --- a/viewer-prototype/src/browser/style/output-components-style.css +++ b/viewer-prototype/src/browser/style/output-components-style.css @@ -62,6 +62,30 @@ canvas { width: 100%; } +.table-tree>tbody>tr:nth-child(1)>th { + background-color: var(--theia-editor-background); + position: sticky; + top: 0; +} + +.table-tree th, .table-tree td { + padding: 3px 5px; + text-align: left; + border-bottom: 1px solid #333; + border-right: 1px solid #333; + white-space: nowrap; + min-width: 50px; +} + +.timegraph-tree tr { + /* TODO: Fix row alignment, this number is arbitrary, it works [on my machine], but it should match line height in timeline-chart */ + line-height: 18px; + position: relative; + white-space: nowrap; + top: 50%; + padding: 0 0; +} + #input-filter-tree { background-color: var(--theia-input-background); border: none; diff --git a/viewer-prototype/src/browser/trace-viewer/components/timegraph-output-component.tsx b/viewer-prototype/src/browser/trace-viewer/components/timegraph-output-component.tsx index ff87b71f4..6a8d29c30 100644 --- a/viewer-prototype/src/browser/trace-viewer/components/timegraph-output-component.tsx +++ b/viewer-prototype/src/browser/trace-viewer/components/timegraph-output-component.tsx @@ -130,12 +130,15 @@ export class TimegraphOutputComponent extends AbstractTreeOutputComponent; } diff --git a/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/checkbox-component.tsx b/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/checkbox-component.tsx index b7fdb3c06..23c039311 100644 --- a/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/checkbox-component.tsx +++ b/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/checkbox-component.tsx @@ -3,7 +3,6 @@ import icons from './icons'; interface CheckboxProps { id: number; - name: string; checkedStatus: number; onToggleCheck: (id: number) => void; } @@ -31,11 +30,8 @@ export class CheckboxComponent extends React.Component { }; render(): JSX.Element { - return
- + return {this.renderCheckbox(this.props.checkedStatus)} - - {this.props.name} -
; + ; } } diff --git a/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/entry-tree.tsx b/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/entry-tree.tsx index 0b7feb874..94cee403b 100644 --- a/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/entry-tree.tsx +++ b/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/entry-tree.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { Entry } from 'tsp-typescript-client/lib/models/entry'; import { listToTree } from './utils'; import { FilterTree } from './tree'; +import { TreeNode } from './tree-node'; interface EntryTreeProps { entries: Entry[]; @@ -10,12 +11,18 @@ interface EntryTreeProps { collapsedNodes: number[]; showFilter: boolean; onToggleCheck: (ids: number[]) => void; - onToggleCollapse: (id: number) => void; + onToggleCollapse: (id: number, nodes: TreeNode[]) => void; + onOrderChange: (ids: number[]) => void; + showHeader: boolean; + className: string; } export class EntryTree extends React.Component { static defaultProps: Partial = { - showFilter: true + showFilter: true, + onOrderChange: () => { /* Nothing to do */ }, + showHeader: true, + className: 'table-tree' }; constructor(props: EntryTreeProps) { diff --git a/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/icons.tsx b/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/icons.tsx index 2b634599f..5ee0d991c 100644 --- a/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/icons.tsx +++ b/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/icons.tsx @@ -1,13 +1,16 @@ import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faChevronDown, faChevronRight, faCheckSquare, faSquare, faMinusSquare } from '@fortawesome/free-solid-svg-icons'; +import { faChevronDown, faChevronRight, faCheckSquare, faSquare, faMinusSquare, faSort, faSortDown, faSortUp } from '@fortawesome/free-solid-svg-icons'; interface iconsShape { expand: React.ReactNode, collapse: React.ReactNode, unchecked: React.ReactNode, checked: React.ReactNode, - halfChecked: React.ReactNode + halfChecked: React.ReactNode, + sort: React.ReactNode, + sortDown: React.ReactNode, + sortUp: React.ReactNode } const icons: iconsShape = { @@ -15,7 +18,10 @@ const icons: iconsShape = { collapse: , unchecked: , checked: , - halfChecked: + halfChecked: , + sort: , + sortDown: , + sortUp: }; export default icons; diff --git a/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/sort.tsx b/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/sort.tsx new file mode 100644 index 000000000..2feedd13e --- /dev/null +++ b/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/sort.tsx @@ -0,0 +1,70 @@ +import icons from './icons'; +import { TreeNode } from './tree-node'; + +interface SortState { + asc: React.ReactNode, + desc: React.ReactNode, + default: React.ReactNode; +} + +export const sortState: SortState = { + asc: icons.sortUp, + desc: icons.sortDown, + default: icons.sort +}; + +export interface SortConfig { + column: string; + sortState: React.ReactNode; +} + +export const nextSortState = (currentState: React.ReactNode): React.ReactNode => { + if (currentState === sortState.default || currentState === sortState.asc) { + return sortState.desc; + } else if (currentState === sortState.desc) { + return sortState.asc; + } else { + return sortState.default; + } +}; + +export const sortNodes = (nodes: TreeNode[], sortConfig: SortConfig[]): TreeNode[] => { + const sortedNodes = [...nodes]; + const orderToSort = sortConfig.find((config: SortConfig) => config.sortState !== sortState.default); + if (orderToSort) { + sortedNodes.sort((node1: TreeNode, node2: TreeNode) => { + const key = orderToSort.column; + const order = (orderToSort.sortState === sortState.asc) ? 'asc' : 'desc'; + const value1 = node1[key as keyof TreeNode]; + const value2 = node2[key as keyof TreeNode]; + let result = 0; + if (!value1 && value2) { + result = -1; + } else if (value1 && !value2) { + result = 1; + } else if (!value1 && !value2) { + result = 0; + } else { + if (typeof value1 === 'string' && typeof value2 === 'string') { + const comp = value1.localeCompare(value2); + result = (order === 'asc') ? -comp : comp; + } else { + if (value1 < value2) { + result = (order === 'asc') ? -1 : 1; + } else if (value1 > value2) { + result = (order === 'asc') ? 1 : -1; + } else { + result = 0; + } + } + } + return result; + }); + sortedNodes.forEach((node: TreeNode) => { + if (node.children.length) { + node.children = sortNodes(node.children, sortConfig); + } + }); + } + return sortedNodes; +}; diff --git a/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/table-body.tsx b/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/table-body.tsx new file mode 100644 index 000000000..9f86f57c3 --- /dev/null +++ b/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/table-body.tsx @@ -0,0 +1,39 @@ +import * as React from 'react'; +import { TreeNode } from './tree-node'; +import { TableRow } from './table-row'; + +interface TableBodyProps { + nodes: TreeNode[]; + keys: string[]; + collapsedNodes: number[]; + isCheckable: boolean; + getCheckedStatus: (id: number) => number; + onToggleCollapse: (id: number) => void; + onToggleCheck: (id: number) => void; +} + +export class TableBody extends React.Component { + constructor(props: TableBodyProps) { + super(props); + } + + createRow = (node: TreeNode): React.ReactNode => + ; + + renderRows = (): React.ReactNode => this.props.nodes.map((node: TreeNode) => this.createRow(node)); + + render(): React.ReactNode | undefined { + if (!this.props.nodes) {return undefined;} + + return ( + + {this.renderRows()} + + ); + } +} diff --git a/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/table-cell.tsx b/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/table-cell.tsx new file mode 100644 index 000000000..44e931f74 --- /dev/null +++ b/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/table-cell.tsx @@ -0,0 +1,25 @@ +import * as React from 'react'; +import { TreeNode } from './tree-node'; + +interface TableCellProps { + nodeKey: string; + node: TreeNode; +} + +export class TableCell extends React.Component { + constructor(props: TableCellProps) { + super(props); + } + + render(): React.ReactNode { + const content: React.ReactNode = (this.props.nodeKey !== 'Legend') + ? this.props.node[this.props.nodeKey as keyof TreeNode] + : undefined; + return ( + + {this.props.children} + {content} + + ); + } +} diff --git a/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/table-header.tsx b/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/table-header.tsx new file mode 100644 index 000000000..c62bc61a1 --- /dev/null +++ b/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/table-header.tsx @@ -0,0 +1,46 @@ +import * as React from 'react'; +import { SortConfig } from './sort'; + +interface TableHeaderProps { + columns: string[]; + sortableColumns: string[]; + sortConfig: SortConfig[]; + onSort: (sortColumn: string) => void; +} + +export class TableHeader extends React.Component { + constructor(props: TableHeaderProps) { + super(props); + } + + handleSortChange = (sortColumn: string): void => { + this.props.onSort(sortColumn); + }; + + toCapitalCase = (name: string): string => (name.charAt(0).toUpperCase() + name.slice(1)); + + renderSortIcon = (column: string): React.ReactNode | undefined => { + if (this.props.sortableColumns.includes(column)) { + const state = this.props.sortConfig.find((config: SortConfig) => config.column === column); + return state + ? {state.sortState} + : undefined; + } + return undefined; + }; + + renderHeader = (): React.ReactNode => this.props.columns.map((column: string, index) => + this.handleSortChange(column)}> + {this.toCapitalCase(column)} + {this.renderSortIcon(column)} + + ); + + render(): React.ReactNode { + return + + {this.renderHeader()} + + ; + } +} diff --git a/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/table-row.tsx b/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/table-row.tsx new file mode 100644 index 000000000..2c97f6057 --- /dev/null +++ b/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/table-row.tsx @@ -0,0 +1,91 @@ +import * as React from 'react'; +import { TreeNode } from './tree-node'; +import { TableCell } from './table-cell'; +import { CheckboxComponent } from './checkbox-component'; +import icons from './icons'; + +interface TableRowProps { + node: TreeNode; + keys: string[]; + level: number; + collapsedNodes: number[]; + isCheckable: boolean; + getCheckedStatus: (id: number) => number; + onToggleCollapse: (id: number) => void; + onToggleCheck: (id: number) => void; +} + +export class TableRow extends React.Component { + constructor(props: TableRowProps) { + super(props); + } + + isCollapsed = (): boolean => this.props.collapsedNodes.includes(this.props.node.id); + + private handleCollapse = (): void => { + this.props.onToggleCollapse(this.props.node.id); + }; + + renderToggleCollapse = (): React.ReactNode => { + const marginLeft = this.props.level * 15 + 'px'; + return ( + (this.props.node.children.length === 0) + ? + : + {(this.isCollapsed() ? icons.expand : icons.collapse)} + + ); + }; + + renderCheckbox = (): React.ReactNode => { + const checkedStatus = this.props.getCheckedStatus(this.props.node.id); + return this.props.isCheckable + ? + : ; + }; + + renderRow = (): React.ReactNode => this.props.keys.map((nodeKey: string, index) => { + let toggleCollapse: React.ReactNode; + let toggleCheck: React.ReactNode; + if (index === 0) { + toggleCollapse = this.renderToggleCollapse(); + toggleCheck = this.renderCheckbox(); + } + + return + {toggleCollapse} + {toggleCheck} + ; + }); + + renderChildren = (): React.ReactNode | undefined => { + if (this.props.node.children.length && !this.isCollapsed()) { + return this.props.node.children.map((child: TreeNode) => + + ); + } + return undefined; + }; + + render(): React.ReactNode | undefined { + if (!this.props.node) {return undefined;} + const children = this.renderChildren(); + + return ( + + {this.renderRow()} + {children} + + ); + } +} diff --git a/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/table.tsx b/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/table.tsx new file mode 100644 index 000000000..f42b956db --- /dev/null +++ b/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/table.tsx @@ -0,0 +1,91 @@ +import * as React from 'react'; +import { TreeNode } from './tree-node'; +import { TableHeader } from './table-header'; +import { TableBody } from './table-body'; +import { SortConfig, sortState, nextSortState, sortNodes } from './sort'; + +interface TableProps { + nodes: TreeNode[]; + collapsedNodes: number[]; + isCheckable: boolean; + sortConfig: SortConfig[]; + getCheckedStatus: (id: number) => number; + onToggleCollapse: (id: number) => void; + onToggleCheck: (id: number) => void; + onSort: (sortedNodes: TreeNode[]) => void; + onSortConfigChange: (sortConfig: SortConfig[]) => void; + showHeader: boolean; + className: string; +} + +export class Table extends React.Component { + + private keys: string[] = []; + private sortableColumns: string[] = []; + + constructor(props: TableProps) { + super(props); + this.keys = this.getTableHeaderKeys(); + this.sortableColumns = this.getSortableColumns(); + this.initializeSortConfig(); + } + + initializeSortConfig = (): void => { + const config: SortConfig[] = []; + this.sortableColumns.forEach((column: string) => { + config.push({column: column, sortState: sortState.default}); + }); + this.props.onSortConfigChange(config); + }; + + getTableHeaderKeys = (): string[] => { + // TODO Re-factor this once the server can properly send column headers + const keys = Object.keys(this.props.nodes[0]); + const excludeKeys = ['isRoot', 'children', 'style', 'metadata', 'labels', 'id', 'parentId']; + return keys.filter(key => !excludeKeys.includes(key)).concat('Legend'); + }; + + getSortableColumns = (): string[] => { + const sortableColumns: string[] = []; + this.keys.forEach((key: string) => { + if (typeof this.props.nodes[0][key as keyof TreeNode] === 'string' || typeof this.props.nodes[0][key as keyof TreeNode] === 'number') { + sortableColumns.push(key); + } + }); + return sortableColumns; + }; + + onSortChange = (sortColumn: string): void => { + let newSortConfigs: SortConfig[] = [...this.props.sortConfig]; + newSortConfigs = newSortConfigs.map((config: SortConfig) => { + if (config.column === sortColumn) { + return {...config, sortState: nextSortState(config.sortState)}; + } else { + return {...config, sortState: sortState.default}; + } + }); + const newSortedNodes = sortNodes(this.props.nodes, newSortConfigs); + this.props.onSortConfigChange(newSortConfigs); + this.props.onSort(newSortedNodes); + }; + + render(): JSX.Element { + return ( +
+ + {this.props.showHeader && } + +
+
+ ); + } +} diff --git a/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/tree-node.tsx b/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/tree-node.tsx index aea4a5f81..d01e54fa7 100644 --- a/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/tree-node.tsx +++ b/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/tree-node.tsx @@ -1,7 +1,3 @@ -import * as React from 'react'; -import { CheckboxComponent } from './checkbox-component'; -import icons from './icons'; - export interface TreeNode { id: number; parentId: number; @@ -9,77 +5,3 @@ export interface TreeNode { children: Array; isRoot: boolean; } - -export const defaultTreeNode: TreeNode = { - id: -1, - parentId: -1, - name: '', - children: [], - isRoot: false -}; - -interface TreeNodeComponentProps { - node: TreeNode; - checkedStatus: number; - level: number; - padding: number; - isCheckable: boolean; - collapsed: boolean; - children: JSX.Element | undefined; - onToggleCollapse: (id: number) => void; - onToggleCheck: (id: number) => void; -} - -export class TreeNodeComponent extends React.Component { - constructor(props: TreeNodeComponentProps) { - super(props); - } - - private isLeaf = (): boolean => this.props.node.children.length === 0; - - private handleCollapse = (): void => { - this.props.onToggleCollapse(this.props.node.id); - }; - - renderChildren = (): JSX.Element | undefined => { - if (this.props.collapsed) { - return undefined; - } - return this.props.children; - }; - - render(): JSX.Element { - return ( -
  • -
    - { this.isLeaf() - ? - : - {(this.props.collapsed ? icons.expand : icons.collapse)} - - } - - { this.props.isCheckable - ? - : {this.props.node.name} - } -
    - {this.renderChildren()} -
  • - ); - } -} diff --git a/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/tree.tsx b/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/tree.tsx index 8f88aa2e9..e477c6eb0 100644 --- a/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/tree.tsx +++ b/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/tree.tsx @@ -1,7 +1,10 @@ import * as React from 'react'; -import { TreeNode, TreeNodeComponent } from './tree-node'; +import { TreeNode } from './tree-node'; import { Message } from './message'; import { Filter } from './filter'; +import { Table } from './table'; +import { getAllExpandedNodeIds } from './utils'; +import { SortConfig, sortNodes } from './sort'; interface FilterTreeProps { nodes: TreeNode[]; @@ -10,11 +13,15 @@ interface FilterTreeProps { checkedSeries: number[]; // Optional collapsedNodes: number[]; onToggleCheck: (ids: number[]) => void; // Optional - onToggleCollapse: (id: number) => void; + onToggleCollapse: (id: number, nodes: TreeNode[]) => void; + onOrderChange: (ids: number[]) => void; + showHeader: boolean; + className: string; } interface FilterTreeState { filteredNodes: TreeNode[]; + sortConfig: SortConfig[]; } export class FilterTree extends React.Component { @@ -22,12 +29,14 @@ export class FilterTree extends React.Component { /* Nothing to do */ }, + onOrderChange: () => { /* Nothing to do */ }, }; constructor(props: FilterTreeProps) { super(props); this.state = { - filteredNodes: this.props.nodes + filteredNodes: this.props.nodes, + sortConfig: [] }; } @@ -59,7 +68,17 @@ export class FilterTree extends React.Component { - this.props.onToggleCollapse(id); + const nodes = sortNodes(this.state.filteredNodes, this.state.sortConfig); + this.props.onToggleCollapse(id, nodes); + }; + + handleOrderChange = (nodes: TreeNode[]): void => { + const ids = getAllExpandedNodeIds(nodes, this.props.collapsedNodes); + this.props.onOrderChange(ids); + }; + + handleSortConfigChange = (sortConfig: SortConfig[]): void => { + this.setState({sortConfig: sortConfig}); }; getAllChildrenIds = (node: TreeNode, ids: number[]): number[] => { @@ -147,13 +166,16 @@ export class FilterTree extends React.Component { const visibleNodes = node.children.filter((child: TreeNode) =>this.getNode(this.state.filteredNodes, child.id) !== undefined); - const allChildrenChecked = visibleNodes.every((child: TreeNode) => { - let isChecked = this.isNodeChecked(child.id); - if (child.children.length) { - isChecked = isChecked && this.isEveryChildChecked(child); - } - return isChecked; - }); + let allChildrenChecked = false; + if (visibleNodes.length) { + allChildrenChecked = visibleNodes.every((child: TreeNode) => { + let isChecked = this.isNodeChecked(child.id); + if (child.children.length) { + isChecked = isChecked && this.isEveryChildChecked(child); + } + return isChecked; + }); + } const leaves = this.getAllLeavesId(this.state.filteredNodes, []); const allLeavesChecked = leaves.every((id: number) => this.isNodeChecked(id)); return allChildrenChecked || allLeavesChecked; @@ -187,6 +209,7 @@ export class FilterTree extends React.Component this.getMatchingIds(node, filter, matchedIds)); filteredTree = this.filterTree(this.props.nodes, matchedIds); this.setState({filteredNodes: filteredTree}); + this.handleOrderChange(filteredTree); }; getMatchingIds = (node: TreeNode, filter: string, foundIds: number[]): boolean => { @@ -213,41 +236,25 @@ export class FilterTree extends React.Component )=> this.handleFilterChanged(e.target.value)}/> {this.state.filteredNodes.length - ? this.renderTreeNodes(this.state.filteredNodes) + ? this.renderTable(this.state.filteredNodes) : No entries found } ; - renderTreeNodes = (nodes: TreeNode[], level = 0): JSX.Element | undefined => { - const treeNodes = nodes.map((node: TreeNode) => { - const children = node.children.length > 0 ? this.renderTreeNodes(node.children, level+1) : undefined; - const checkedStatus = this.getCheckedStatus(node.id); - - if (!node.isRoot && this.isCollapsed(node.parentId)) { - return undefined; - } - return ( - - {children} - - ); - }); - return ( -
      - {treeNodes} -
    - ); - }; + renderTable = (nodes: TreeNode[]): JSX.Element => + ; render(): JSX.Element | undefined { if (!this.props.nodes) {return undefined;} @@ -256,7 +263,7 @@ export class FilterTree extends React.Component { this.props.showFilter ? this.renderFilterTree() - : this.renderTreeNodes(rootNodes) + : this.renderTable(rootNodes) } ; diff --git a/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/utils.tsx b/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/utils.tsx index 744d254cd..2a64d0565 100644 --- a/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/utils.tsx +++ b/viewer-prototype/src/browser/trace-viewer/components/utils/filtrer-tree/utils.tsx @@ -2,11 +2,10 @@ import { Entry } from 'tsp-typescript-client/lib/models/entry'; import { TreeNode } from './tree-node'; const entryToTreeNode = (entry: Entry) => ({ - id: entry.id, - parentId: entry.parentId, name: ((entry.labels) && (entry.labels.length > 0)) ? entry.labels[0] : '', isRoot: false, - children: [] + children: [], + ...entry } as TreeNode); export const listToTree = (list: Entry[]): TreeNode[] => { diff --git a/viewer-prototype/src/browser/trace-viewer/components/xy-output-component.tsx b/viewer-prototype/src/browser/trace-viewer/components/xy-output-component.tsx index 3c722f21e..01b373e34 100644 --- a/viewer-prototype/src/browser/trace-viewer/components/xy-output-component.tsx +++ b/viewer-prototype/src/browser/trace-viewer/components/xy-output-component.tsx @@ -9,12 +9,15 @@ import { ResponseStatus } from 'tsp-typescript-client/lib/models/response/respon import { XYSeries } from 'tsp-typescript-client/lib/models/xy'; import Chart = require('chart.js'); import { EntryTree } from './utils/filtrer-tree/entry-tree'; +import { getAllExpandedNodeIds } from './utils/filtrer-tree/utils'; +import { TreeNode } from './utils/filtrer-tree/tree-node'; type XYOuputState = AbstractOutputState & { selectedSeriesId: number[]; XYTree: Entry[]; checkedSeries: number[]; collapsedNodes: number[]; + orderedNodes: number[]; XYData: any; }; @@ -32,6 +35,7 @@ export class XYOutputComponent extends AbstractTreeOutputComponent : undefined ; @@ -172,7 +178,7 @@ export class XYOutputComponent extends AbstractTreeOutputComponent expandId === id); @@ -182,7 +188,12 @@ export class XYOutputComponent extends AbstractTreeOutputComponent(this.props.traceId, this.props.outputDescriptor.id, xyTreeParameters)).getModel(); const treeModel = xyTreeResponse.model; - this.buildTreeNodes(treeModel.entries); + if (treeModel) { + this.buildTreeNodes(treeModel.entries); + } } private async updateXY() {