diff --git a/src/components/calltree/CallTree.js b/src/components/calltree/CallTree.js index cdfbda64eb..95ca47c65a 100644 --- a/src/components/calltree/CallTree.js +++ b/src/components/calltree/CallTree.js @@ -6,7 +6,10 @@ import React, { PureComponent } from 'react'; import memoize from 'memoize-immutable'; import explicitConnect from 'firefox-profiler/utils/connect'; -import { TreeView } from 'firefox-profiler/components/shared/TreeView'; +import { + TreeView, + ColumnSortState, +} from 'firefox-profiler/components/shared/TreeView'; import { CallTreeEmptyReasons } from './CallTreeEmptyReasons'; import { Icon } from 'firefox-profiler/components/shared/Icon'; import { getCallNodePathFromIndex } from 'firefox-profiler/profile-logic/profile-data'; @@ -87,6 +90,8 @@ class CallTreeImpl extends PureComponent { }; _treeView: TreeView | null = null; _takeTreeViewRef = (treeView) => (this._treeView = treeView); + _sortableColumns = new Set(['self', 'total']); + _sortedColumns = new ColumnSortState([{ column: 'total', ascending: false }]); /** * Call Trees can have different types of "weights" for the data. Choose the @@ -260,6 +265,10 @@ class CallTreeImpl extends PureComponent { } } + _onSort = (sortedColumns: ColumnSortState) => { + this._sortedColumns = sortedColumns; + }; + render() { const { tree, @@ -296,6 +305,9 @@ class CallTreeImpl extends PureComponent { onKeyDown={this._onKeyDown} onEnterKey={this._onEnterOrDoubleClick} onDoubleClick={this._onEnterOrDoubleClick} + initialSortedColumns={this._sortedColumns} + onSort={this._onSort} + sortableColumns={this._sortableColumns} /> ); } diff --git a/src/components/marker-table/index.js b/src/components/marker-table/index.js index 1f85812064..5c90d72045 100644 --- a/src/components/marker-table/index.js +++ b/src/components/marker-table/index.js @@ -8,7 +8,7 @@ import React, { PureComponent } from 'react'; import memoize from 'memoize-immutable'; import explicitConnect from '../../utils/connect'; -import { TreeView } from '../shared/TreeView'; +import { TreeView, ColumnSortState } from '../shared/TreeView'; import { MarkerTableEmptyReasons } from './MarkerTableEmptyReasons'; import { getZeroAt, @@ -42,7 +42,9 @@ const MAX_DESCRIPTION_CHARACTERS = 500; type MarkerDisplayData = {| start: string, + rawStart: Milliseconds, duration: string | null, + rawDuration: Milliseconds | null, name: string, type: string, |}; @@ -70,12 +72,40 @@ class MarkerTree { this._getMarkerLabel = getMarkerLabel; } - getRoots(): MarkerIndex[] { + getRoots(sort: ColumnSortState | null = null): MarkerIndex[] { + if (sort !== null) { + return sort.sortItemsHelper( + this._markerIndexes, + (first: MarkerIndex, second: MarkerIndex, column: string) => { + const firstData = this.getDisplayData(first); + const secondData = this.getDisplayData(second); + switch (column) { + case 'start': + return secondData.rawStart - firstData.rawStart; + case 'duration': + if (firstData.rawDuration === null) { + return -1; + } + if (secondData.rawDuration === null) { + return 1; + } + return secondData.rawDuration - firstData.rawDuration; + case 'type': + return firstData.type.localeCompare(secondData.type); + default: + throw new Error('Invalid column ' + column); + } + } + ); + } return this._markerIndexes; } - getChildren(markerIndex: MarkerIndex): MarkerIndex[] { - return markerIndex === -1 ? this.getRoots() : []; + getChildren( + markerIndex: MarkerIndex, + sort: ColumnSortState | null = null + ): MarkerIndex[] { + return markerIndex === -1 ? this.getRoots(sort) : []; } hasChildren(_markerIndex: MarkerIndex): boolean { @@ -113,10 +143,13 @@ class MarkerTree { } let duration = null; + let rawDuration: number | null = null; + const markerEnd = marker.end; if (marker.incomplete) { duration = 'unknown'; - } else if (marker.end !== null) { - duration = formatTimestamp(marker.end - marker.start); + } else if (markerEnd !== null) { + duration = formatTimestamp(markerEnd - marker.start); + rawDuration = markerEnd - marker.start; } displayData = { @@ -128,6 +161,8 @@ class MarkerTree { marker.name, marker.data ), + rawDuration: rawDuration, + rawStart: marker.start, }; this._displayDataByIndex.set(markerIndex, displayData); } @@ -164,11 +199,13 @@ class MarkerTableImpl extends PureComponent { { propName: 'duration', titleL10nId: 'MarkerTable--duration' }, { propName: 'type', titleL10nId: 'MarkerTable--type' }, ]; + _sortableColumns = new Set(['start', 'duration', 'type', 'name']); _mainColumn = { propName: 'name', titleL10nId: 'MarkerTable--description' }; _expandedNodeIds: Array = []; _onExpandedNodeIdsChange = () => {}; _treeView: ?TreeView; _takeTreeViewRef = (treeView) => (this._treeView = treeView); + _sortedColumns = new ColumnSortState([{ column: 'start', ascending: true }]); getMarkerTree = memoize((...args) => new MarkerTree(...args), { limit: 1 }); @@ -204,6 +241,10 @@ class MarkerTableImpl extends PureComponent { changeRightClickedMarker(threadsKey, selectedMarker); }; + _onSort = (sortedColumns: ColumnSortState) => { + this._sortedColumns = sortedColumns; + }; + render() { const { getMarker, @@ -214,7 +255,7 @@ class MarkerTableImpl extends PureComponent { markerSchemaByName, getMarkerLabel, } = this.props; - const tree = this.getMarkerTree( + const tree: MarkerTree = this.getMarkerTree( getMarker, markerIndexes, zeroAt, @@ -247,6 +288,9 @@ class MarkerTableImpl extends PureComponent { contextMenuId="MarkerContextMenu" rowHeight={16} indentWidth={10} + initialSortedColumns={this._sortedColumns} + onSort={this._onSort} + sortableColumns={this._sortableColumns} /> )} diff --git a/src/components/shared/TreeView.css b/src/components/shared/TreeView.css index dda999b6ad..45e9cda9ae 100644 --- a/src/components/shared/TreeView.css +++ b/src/components/shared/TreeView.css @@ -102,6 +102,19 @@ border-right: 1px solid var(--grey-30); } +.treeViewHeaderColumn.sortInactive::after { + content: ' ▲'; + opacity: 0; +} + +.treeViewHeaderColumn.sortDescending::after { + content: ' ▲'; +} + +.treeViewHeaderColumn.sortAscending::after { + content: ' ▼ '; +} + .treeBadge { display: inline-block; overflow: hidden; diff --git a/src/components/shared/TreeView.js b/src/components/shared/TreeView.js index fa0c0a03ce..5e765e4f17 100644 --- a/src/components/shared/TreeView.js +++ b/src/components/shared/TreeView.js @@ -53,40 +53,138 @@ export type Column = {| |}>, |}; +type SingleColumnSortState = {| + column: string, + ascending: boolean, +|}; + type TreeViewHeaderProps = {| +fixedColumns: Column[], +mainColumn: Column, + +onSort: (string) => void, + +currentSortedColumn: SingleColumnSortState | null, |}; -const TreeViewHeader = ({ - fixedColumns, - mainColumn, -}: TreeViewHeaderProps) => { - if (fixedColumns.length === 0 && !mainColumn.titleL10nId) { - // If there is nothing to display in the header, do not render it. - return null; +export class ColumnSortState { + sortedColumns: SingleColumnSortState[]; + + // start by sorting last column + constructor(sortedColumns: SingleColumnSortState[]) { + this.sortedColumns = sortedColumns; + } + + sortColumn(column: string, ascending: boolean | null = null) { + const current = this._isLastSortedColumn(column) + ? this.getStateForColumn(column) + : null; + const sortedColumns = this.sortedColumns.filter((c) => c.column !== column); + if (ascending === true || current === null) { + sortedColumns.push({ column, ascending: true }); + } else { + sortedColumns.push(this._invertSortState(current)); + } + return new ColumnSortState(sortedColumns); + } + + _isLastSortedColumn(column: string) { + const cur = this.current(); + return cur !== null && cur.column === column; + } + + _invertSortState(state: SingleColumnSortState): SingleColumnSortState { + return { column: state.column, ascending: !state.ascending }; + } + + getStateForColumn(column: string): SingleColumnSortState | null { + return this.sortedColumns + .filter((c) => c.column === column) + .concat(null)[0]; + } + + getStateForColumnOrDefault(column: string): SingleColumnSortState { + return ( + this.getStateForColumn(column) || { column: column, ascending: true } + ); } - return ( -
- {fixedColumns.map((col) => ( + + current(): SingleColumnSortState | null { + return this.sortedColumns.length > 0 + ? this.sortedColumns[this.sortedColumns.length - 1] + : null; + } + + sortItemsHelper(items: T[], compareColumn: (T, T, string) => number): T[] { + let sorted = items; + for (const { column, ascending } of this.sortedColumns) { + const sign = ascending ? -1 : 1; + + sorted = sorted.sort((a, b) => { + return sign * compareColumn(a, b, column); + }); + } + return sorted; + } +} + +class TreeViewHeader extends React.PureComponent< + TreeViewHeaderProps +> { + _onSort = (e: MouseEvent) => { + const { onSort } = this.props; + const target = e.currentTarget; + if (target instanceof HTMLElement) { + onSort(Number(target.dataset.column)); + } + }; + + render() { + const { fixedColumns, mainColumn, currentSortedColumn } = this.props; + if (fixedColumns.length === 0 && !mainColumn.titleL10nId) { + // If there is nothing to display in the header, do not render it. + return null; + } + return ( +
+ {fixedColumns.map((col) => { + let sortClass = ''; + if ( + currentSortedColumn && + currentSortedColumn.column === col.propName + ) { + if (currentSortedColumn.ascending) { + sortClass = 'sortDescending'; + } else { + sortClass = 'sortAscending'; + } + } else { + sortClass = 'sortInactive'; + } + return ( + + + + ); + })} - ))} - - - -
- ); -}; +
+ ); + } +} function reactStringWithHighlightedSubstrings( string: string, @@ -364,10 +462,10 @@ class TreeViewRowScrolledColumns< interface Tree { getDepth(NodeIndex): number; - getRoots(): NodeIndex[]; + getRoots(ColumnSortState | null): NodeIndex[]; getDisplayData(NodeIndex): DisplayData; getParent(NodeIndex): NodeIndex; - getChildren(NodeIndex): NodeIndex[]; + getChildren(NodeIndex, ColumnSortState | null): NodeIndex[]; hasChildren(NodeIndex): boolean; getAllDescendants(NodeIndex): Set; } @@ -393,13 +491,29 @@ type TreeViewProps = {| +rowHeight: CssPixels, +indentWidth: CssPixels, +onKeyDown?: (SyntheticKeyboardEvent<>) => void, + +initialSortedColumns?: ColumnSortState, + +onSort?: (ColumnSortState) => void, + +sortableColumns?: Set, +|}; + +type TreeViewState = {| + sortedColumns: ColumnSortState, |}; export class TreeView extends React.PureComponent< - TreeViewProps + TreeViewProps, + TreeViewState > { _list: VirtualList | null = null; _takeListRef = (list: VirtualList | null) => (this._list = list); + state = { sortedColumns: new ColumnSortState([]) }; + + constructor(props: TreeViewProps) { + super(props); + if (props.initialSortedColumns) { + this.state.sortedColumns = props.initialSortedColumns; + } + } // The tuple `specialItems` always contains 2 elements: the first element is // the selected node id (if any), and the second element is the right clicked @@ -422,19 +536,23 @@ export class TreeView extends React.PureComponent< ); _computeAllVisibleRowsMemoized = memoize( - (tree: Tree, expandedNodes: Set) => { + ( + tree: Tree, + expandedNodes: Set, + sortedColumns: ColumnSortState + ) => { function _addVisibleRowsFromNode(tree, expandedNodes, arr, nodeId) { arr.push(nodeId); if (!expandedNodes.has(nodeId)) { return; } - const children = tree.getChildren(nodeId); + const children = tree.getChildren(nodeId, sortedColumns); for (let i = 0; i < children.length; i++) { _addVisibleRowsFromNode(tree, expandedNodes, arr, children[i]); } } - - const roots = tree.getRoots(); + // we only sort the root nodes for now + const roots = tree.getRoots(sortedColumns); const allRows = []; for (let i = 0; i < roots.length; i++) { _addVisibleRowsFromNode(tree, expandedNodes, allRows, roots[i]); @@ -462,7 +580,6 @@ export class TreeView extends React.PureComponent< _renderRow = (nodeId: NodeIndex, index: number, columnIndex: number) => { const { - tree, fixedColumns, mainColumn, appendageColumn, @@ -472,6 +589,7 @@ export class TreeView extends React.PureComponent< rowHeight, indentWidth, } = this.props; + const { tree } = this.props; const displayData = tree.getDisplayData(nodeId); // React converts height into 'px' values, while lineHeight is valid in // non-'px' units. @@ -523,7 +641,11 @@ export class TreeView extends React.PureComponent< _getAllVisibleRows(): NodeIndex[] { const { tree } = this.props; - return this._computeAllVisibleRowsMemoized(tree, this._getExpandedNodes()); + return this._computeAllVisibleRowsMemoized( + tree, + this._getExpandedNodes(), + this.state.sortedColumns + ); } _getSpecialItems(): [NodeIndex | void, NodeIndex | void] { @@ -595,7 +717,8 @@ export class TreeView extends React.PureComponent< _onCopy = (event: ClipboardEvent) => { event.preventDefault(); - const { tree, selectedNodeId, mainColumn } = this.props; + const { selectedNodeId, mainColumn } = this.props; + const { tree } = this.props; if (selectedNodeId) { const displayData = tree.getDisplayData(selectedNodeId); const clipboardData: DataTransfer = (event: any).clipboardData; @@ -706,7 +829,12 @@ export class TreeView extends React.PureComponent< } else { // Do KEY_DOWN only if the next element is a child if (this.props.tree.hasChildren(selected)) { - this._select(this.props.tree.getChildren(selected)[0]); + this._select( + this.props.tree.getChildren( + selected, + this.state.sortedColumns + )[0] + ); } } break; @@ -736,6 +864,24 @@ export class TreeView extends React.PureComponent< } } + _onSort = (column: string) => { + if ( + !this.props.sortableColumns || + !this.props.sortableColumns.has(column) + ) { + return; + } + this.setState((x) => { + const newSortedColumns = x.sortedColumns.sortColumn(column); + if (this.props.onSort) { + this.props.onSort(newSortedColumns); + } + return { + sortedColumns: newSortedColumns, + }; + }); + }; + render() { const { fixedColumns, @@ -747,9 +893,16 @@ export class TreeView extends React.PureComponent< rowHeight, selectedNodeId, } = this.props; + const { sortedColumns } = this.state; + const currentSortedColumn = sortedColumns.current(); return (
- + { + switch (column) { + case 'total': + return ( + this._callNodeSummary.total[second] - + this._callNodeSummary.total[first] + ); + case 'self': + return ( + this._callNodeSummary.self[second] - + this._callNodeSummary.self[first] + ); + default: + throw new Error('Invalid column ' + column); + } + } + ); + } return children; } @@ -353,6 +382,8 @@ export class CallTree { iconSrc, icon, ariaLabel, + rawTotal: total, + rawSelf: self, }; this._displayDataByIndex.set(callNodeIndex, displayData); } diff --git a/src/test/components/MarkerTable.test.js b/src/test/components/MarkerTable.test.js index ba67e6a2d7..4cf67da211 100644 --- a/src/test/components/MarkerTable.test.js +++ b/src/test/components/MarkerTable.test.js @@ -103,6 +103,17 @@ describe('MarkerTable', function () { expect(scrolledRows()).toHaveLength(2); }); + it('sorts when the start header and duration header is clicked', () => { + const { container, getByText } = setup(); + + fireFullClick(getByText('Start')); + expect(container).toMatchSnapshot(); + fireFullClick(getByText('Start')); + expect(container).toMatchSnapshot(); + fireFullClick(getByText('Duration')); + expect(container).toMatchSnapshot(); + }); + it('selects a row when left clicking', () => { const { getByText, getRowElement } = setup(); diff --git a/src/test/components/ProfileCallTreeView.test.js b/src/test/components/ProfileCallTreeView.test.js index 9e9ed6cf3a..d7656a9968 100644 --- a/src/test/components/ProfileCallTreeView.test.js +++ b/src/test/components/ProfileCallTreeView.test.js @@ -292,6 +292,28 @@ describe('calltree/ProfileCallTreeView', function () { const { container } = setup(profile); expect(container.querySelector('.treeViewRow.isSelected')).toBeFalsy(); }); + + it('sorts when the total column header is clicked', () => { + const { container, getByText } = setup(); + + expect(container.firstChild).toMatchSnapshot(); + + fireFullClick(getByText('Total (samples)')); + expect(container.firstChild).toMatchSnapshot(); + fireFullClick(getByText('Total (samples)')); + expect(container.firstChild).toMatchSnapshot(); + }); + + it('sorts when the total header and self header is clicked', () => { + const { container, getByText } = setup(); + + fireFullClick(getByText('Total (samples)')); + expect(container).toMatchSnapshot(); + fireFullClick(getByText('Total (samples)')); + expect(container).toMatchSnapshot(); + fireFullClick(getByText('Self')); + expect(container).toMatchSnapshot(); + }); }); describe('calltree/ProfileCallTreeView EmptyReasons', function () { diff --git a/src/test/components/__snapshots__/MarkerTable.test.js.snap b/src/test/components/__snapshots__/MarkerTable.test.js.snap index a46aaa6901..460fbaf483 100644 --- a/src/test/components/__snapshots__/MarkerTable.test.js.snap +++ b/src/test/components/__snapshots__/MarkerTable.test.js.snap @@ -87,17 +87,20 @@ exports[`MarkerTable renders some basic markers and updates when needed 1`] = ` class="treeViewHeader" > Start Duration Type @@ -456,3 +459,1287 @@ exports[`MarkerTable renders some basic markers and updates when needed 1`] = `
`; + +exports[`MarkerTable sorts when the start header and duration header is clicked 1`] = ` +
+
+
+
+
+ +
+
+
+
+ + Start + + + Duration + + + Type + + + Description + +
+
+
+
+
+
+
+
+ + 0.000s + + + 0s + + + UserTiming + +
+
+ + 0.002s + + + + Paint + +
+
+ + 0.108s + + + unknown + + + IPC + +
+
+ + 0.153s + + + 584.00ns + + + Text + +
+
+ + 0.153s + + + 584.00ns + + + Text + +
+
+ + 0.158s + + + + Log + +
+
+ + 0.162s + + + 1ms + + + FileIO + +
+
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+`; + +exports[`MarkerTable sorts when the start header and duration header is clicked 2`] = ` +
+
+
+
+
+ +
+
+
+
+ + Start + + + Duration + + + Type + + + Description + +
+
+
+
+
+
+
+
+ + 0.000s + + + 0s + + + UserTiming + +
+
+ + 0.002s + + + + Paint + +
+
+ + 0.108s + + + unknown + + + IPC + +
+
+ + 0.153s + + + 584.00ns + + + Text + +
+
+ + 0.153s + + + 584.00ns + + + Text + +
+
+ + 0.158s + + + + Log + +
+
+ + 0.162s + + + 1ms + + + FileIO + +
+
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+`; + +exports[`MarkerTable sorts when the start header and duration header is clicked 3`] = ` +
+
+
+
+
+ +
+
+
+
+ + Start + + + Duration + + + Type + + + Description + +
+
+
+
+
+
+
+
+ + 0.000s + + + 0s + + + UserTiming + +
+
+ + 0.002s + + + + Paint + +
+
+ + 0.108s + + + unknown + + + IPC + +
+
+ + 0.153s + + + 584.00ns + + + Text + +
+
+ + 0.153s + + + 584.00ns + + + Text + +
+
+ + 0.158s + + + + Log + +
+
+ + 0.162s + + + 1ms + + + FileIO + +
+
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+`; diff --git a/src/test/components/__snapshots__/ProfileCallTreeView.test.js.snap b/src/test/components/__snapshots__/ProfileCallTreeView.test.js.snap index 53f3829e06..60f3069ad1 100644 --- a/src/test/components/__snapshots__/ProfileCallTreeView.test.js.snap +++ b/src/test/components/__snapshots__/ProfileCallTreeView.test.js.snap @@ -149,10 +149,12 @@ exports[`ProfileCallTreeView with JS Allocations matches the snapshot for JS all class="treeViewHeader" > - -80% + -20% - -12 + -3 - -5 + — - -47% + -80% - -7 + -12 - — + -5
- -7 + —
- -20% + -47% - -3 + -7 - — + -7
+
-
@@ -1491,10 +1499,12 @@ exports[`ProfileCallTreeView with balanced native allocations matches the snapsh class="treeViewHeader" > - -59% + -41% - -24 + -17 - — + -17 - -41% + -59% - -17 + -24 - -17 + —
- Fjs + Gjs
`; + +exports[`calltree/ProfileCallTreeView sorts when the total column header is clicked 1`] = ` +
+
+
    +
  • + + + +
  • +
  • + +
  • +
+
+ +
+
+
    +
  1. + + Complete “⁨Empty⁩” + +
  2. +
+
+
+ + + Total (samples) + + + Self + + + +
+
+
+
+
+
+
+
+ + 100% + + + 3 + + + — + + +
+ +
+
+ + 100% + + + 3 + + + — + + +
+ +
+
+ + 67% + + + 2 + + + — + + +
+ +
+
+ + 33% + + + 1 + + + — + + +
+ +
+
+ + 33% + + + 1 + + + 1 + + +
+ +
+
+ + 33% + + + 1 + + + — + + +
+ +
+
+ + 33% + + + 1 + + + — + + +
+ +
+
+
+
+
+
+
+ + + + + A + + +
+
+ + + + + B + + +
+
+ + + + + C + + +
+
+ + + + + D + + +
+
+ +
+ + +
+
+
+
+
+
+
+`; + +exports[`calltree/ProfileCallTreeView sorts when the total column header is clicked 2`] = ` +
+
+
    +
  • + + + +
  • +
  • + +
  • +
+
+ +
+
+
    +
  1. + + Complete “⁨Empty⁩” + +
  2. +
+
+
+ + + Total (samples) + + + Self + + + +
+
+
+
+
+
+
+
+ + 100% + + + 3 + + + — + + +
+ +
+
+ + 100% + + + 3 + + + — + + +
+ +
+
+ + 67% + + + 2 + + + — + + +
+ +
+
+ + 33% + + + 1 + + + — + + +
+ +
+
+ + 33% + + + 1 + + + 1 + + +
+ +
+
+ + 33% + + + 1 + + + — + + +
+ +
+
+ + 33% + + + 1 + + + — + + +
+ +
+
+
+
+
+
+
+ + + + + A + + +
+
+ + + + + B + + +
+
+ + + + + C + + +
+
+ + + + + D + + +
+
+ +
+ + +
+
+
+
+
+
+
+`; + +exports[`calltree/ProfileCallTreeView sorts when the total column header is clicked 3`] = ` +
+
+
    +
  • + + + +
  • +
  • + +
  • +
+
+ +
+
+
    +
  1. + + Complete “⁨Empty⁩” + +
  2. +
+
+
+ + + Total (samples) + + + Self + + + +
+
+
+
+
+
+
+
+ + 100% + + + 3 + + + — + + +
+ +
+
+ + 100% + + + 3 + + + — + + +
+ +
+
+ + 67% + + + 2 + + + — + + +
+ +
+
+ + 33% + + + 1 + + + — + + +
+ +
+
+ + 33% + + + 1 + + + 1 + + +
+ +
+
+ + 33% + + + 1 + + + — + + +
+ +
+
+ + 33% + + + 1 + + + — + + +
+ +
+
+
+
+
+
+
+ + + + + A + + +
+
+ + + + + B + + +
+
+ + + + + C + + +
+
+ + + + + D + + +
+
+ +
+ + +
+
+
+
+
+
+
+`; + +exports[`calltree/ProfileCallTreeView sorts when the total header and self header is clicked 1`] = ` +
+
+
+
    +
  • + + + +
  • +
  • + +
  • +
+
+ +
+
+
    +
  1. + + Complete “⁨Empty⁩” + +
  2. +
+
+
+ + + Total (samples) + + + Self + + + +
+
+
+
+
+
+
+
+ + 100% + + + 3 + + + — + + +
+ +
+
+ + 100% + + + 3 + + + — + + +
+ +
+
+ + 67% + + + 2 + + + — + + +
+ +
+
+ + 33% + + + 1 + + + — + + +
+ +
+
+ + 33% + + + 1 + + + 1 + + +
+ +
+
+ + 33% + + + 1 + + + — + + +
+ +
+
+ + 33% + + + 1 + + + — + + +
+ +
+
+
+
+
+
+
+ + + + + A + + +
+
+ + + + + B + + +
+
+ + + + + C + + +
+
+ + + + + D + + +
+
+ +
+ + +
+
+
+
+
+
+
+
+`; + +exports[`calltree/ProfileCallTreeView sorts when the total header and self header is clicked 2`] = ` +
+
+
+
    +
  • + + + +
  • +
  • + +
  • +
+
+ +
+
+
    +
  1. + + Complete “⁨Empty⁩” + +
  2. +
+
+
+ + + Total (samples) + + + Self + + + +
+
+
+
+
+
+
+
+ + 100% + + + 3 + + + — + + +
+ +
+
+ + 100% + + + 3 + + + — + + +
+ +
+
+ + 67% + + + 2 + + + — + + +
+ +
+
+ + 33% + + + 1 + + + — + + +
+ +
+
+ + 33% + + + 1 + + + 1 + + +
+ +
+
+ + 33% + + + 1 + + + — + + +
+ +
+
+ + 33% + + + 1 + + + — + + +
+ +
+
+
+
+
+
+
+ + + + + A + + +
+
+ + + + + B + + +
+
+ + + + + C + + +
+
+ + + + + D + + +
+
+ +
+ + +
+
+
+
+
+
+
+
+`; + +exports[`calltree/ProfileCallTreeView sorts when the total header and self header is clicked 3`] = ` +
+
+
+
    +
  • + + + +
  • +
  • + +
  • +
+
+ +
+
+
    +
  1. + + Complete “⁨Empty⁩” + +
  2. +
+
+
+ + + Total (samples) + + + Self + + + +
+
+
+
+
+
+
+
+ + 100% + + + 3 + + + — + + +
+ +
+
+ + 100% + + + 3 + + + — + + +
+ +
+
+ + 67% + + + 2 + + + — + + +
+ +
+
+ + 33% + + + 1 + + + — + + +
+ +
+
+ + 33% + + + 1 + + + 1 + + +
+ +
+
+ + 33% + + + 1 + + + — + + +
+ +
+
+ + 33% + + + 1 + + + — + + +
+ +
+
+
+
+
+
+
+ + + + + A + + +
+
+ + + + + B + + +
+
+ + + + + C + + +
+
+ + + + + D + + +
+
+ +
+ + +
+
+
+
+
+
+
+
+`; diff --git a/src/test/store/icons.test.js b/src/test/store/icons.test.js index ff78a1397e..b3b6549444 100644 --- a/src/test/store/icons.test.js +++ b/src/test/store/icons.test.js @@ -48,6 +48,8 @@ describe('actions/icons', function () { icon, iconSrc: 'https://edition.cnn.com/favicon.ico', ariaLabel: 'fake aria label', + rawTotal: 0, + rawSelf: 0, }; } diff --git a/src/test/unit/profile-tree.test.js b/src/test/unit/profile-tree.test.js index 12a242ff21..a628cc032a 100644 --- a/src/test/unit/profile-tree.test.js +++ b/src/test/unit/profile-tree.test.js @@ -335,6 +335,9 @@ describe('unfiltered call tree', function () { totalPercent: '100%', categoryColor: 'grey', categoryName: 'Other', + rawSelf: 0, + rawTotal: 3, + badge: undefined, }); expect(callTree.getDisplayData(I)).toEqual({ ariaLabel: @@ -351,6 +354,9 @@ describe('unfiltered call tree', function () { totalPercent: '33%', categoryColor: 'grey', categoryName: 'Other', + rawSelf: 1, + rawTotal: 1, + badge: undefined, }); }); }); diff --git a/src/types/profile-derived.js b/src/types/profile-derived.js index d8f413437c..5fcf9dd610 100644 --- a/src/types/profile-derived.js +++ b/src/types/profile-derived.js @@ -244,6 +244,9 @@ export type CallNodeDisplayData = $Exact< badge?: ExtraBadgeInfo, icon: string | null, ariaLabel: string, + // just used for sorting + rawSelf: number, + rawTotal: number, }> >;