Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ui/core/components/detailed_results.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ export abstract class DetailedResults extends Component {
this.simUI?.sim.settingsChangeEmitter.on(async () => await this.updateSettings());

// Allow styling the sticky toolbar
const toolbar = document.querySelector('.dr-toolbar') as HTMLElement;
const toolbar = document.querySelector<HTMLElement>('.dr-toolbar')!;
new IntersectionObserver(
([e]) => {
e.target.classList.toggle('stuck', e.intersectionRatio < 1);
Expand Down
24 changes: 13 additions & 11 deletions ui/core/components/detailed_results/metrics_table/metrics_table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ import { ActionId } from '../../../proto_utils/action_id';
import { ActionMetrics, AuraMetrics, ResourceMetrics, UnitMetrics } from '../../../proto_utils/sim_result';
import { TypedEvent } from '../../../typed_event';
import { ResultComponent, ResultComponentConfig, SimResultData } from '../result_component';

declare let $: any;
import { TableSorter } from './table_sorter';

export enum ColumnSortType {
None,
Expand Down Expand Up @@ -36,7 +35,8 @@ export abstract class MetricsTable<T extends ActionMetrics | AuraMetrics | UnitM
private readonly columnConfigs: Array<MetricsColumnConfig<T>>;

protected readonly tableElem: HTMLElement;
protected readonly bodyElem: HTMLElement;
protected readonly bodyElem: HTMLTableSectionElement;
private readonly sorter: TableSorter;

readonly onUpdate = new TypedEvent<void>('MetricsTableUpdate');

Expand All @@ -53,7 +53,7 @@ export abstract class MetricsTable<T extends ActionMetrics | AuraMetrics | UnitM
</table>,
);

this.tableElem = this.rootElem.querySelector<HTMLTableSectionElement>('.metrics-table')!;
this.tableElem = this.rootElem.querySelector<HTMLTableElement>('.metrics-table')!;
this.bodyElem = this.rootElem.querySelector<HTMLTableSectionElement>('.metrics-table-body')!;

const headerRowElem = this.rootElem.querySelector<HTMLTableRowElement>('.metrics-table-header-row')!;
Expand All @@ -78,13 +78,15 @@ export abstract class MetricsTable<T extends ActionMetrics | AuraMetrics | UnitM
headerRowElem.appendChild(headerCell);
});

const sortList = this.columnConfigs
.map((config, i) => [i, config.sort == ColumnSortType.Ascending ? 0 : 1])
.filter(sortData => this.columnConfigs[sortData[0]].sort);
const sortCol = this.columnConfigs.findIndex(v => !!v.sort);

$(this.tableElem).tablesorter({
sortList: sortList,
cssChildRow: 'child-metric',
this.sorter = new TableSorter({
tableHead: headerRowElem,
tableBody: this.bodyElem,
dataSetKey: 'text',
childRowClass: 'child-metric',
defaultSortCol: sortCol !== -1 ? sortCol : 0,
defaultSortDesc: sortCol !== -1 && this.columnConfigs[sortCol].sort == ColumnSortType.Descending,
});
}

Expand Down Expand Up @@ -173,7 +175,7 @@ export abstract class MetricsTable<T extends ActionMetrics | AuraMetrics | UnitM
}

groupedMetrics.forEach(group => this.addGroup(group));
$(this.tableElem).trigger('update');
this.sorter.update();
this.onUpdate.emit(resultData.eventID);
}

Expand Down
104 changes: 104 additions & 0 deletions ui/core/components/detailed_results/metrics_table/table_sorter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
type TableSorterRowData = {
readonly values: ReadonlyArray<string | number>;
readonly rowElement: HTMLTableRowElement;
};

type TableSorterConfig = {
tableHead: HTMLTableRowElement;
tableBody: HTMLTableSectionElement;
dataSetKey: string;
childRowClass: string;
defaultSortCol: number;
defaultSortDesc: boolean;
};

export class TableSorter {
private readonly cfg: Readonly<TableSorterConfig>;
private readonly rowData: Array<TableSorterRowData & { children?: Array<TableSorterRowData> }> = [];
private sortCol = -1;
private sortDesc: Array<boolean>;

constructor(config: TableSorterConfig) {
if (config.tableHead.cells[config.defaultSortCol] === undefined) throw new Error('Default sort column must be a valid header cell index!');

this.cfg = config;

this.sortCol = this.cfg.defaultSortCol;
this.sortDesc = Array(config.tableHead.cells.length).fill(true);
this.sortDesc[config.defaultSortCol] = config.defaultSortDesc;

Array.from(config.tableHead.cells).forEach((cell, i) => {
cell.addEventListener('click', () => this.setSort(i));
});
}

private sortFunc = (a: TableSorterRowData, b: TableSorterRowData) => {
const aValue = a.values[this.sortCol];
const bValue = b.values[this.sortCol];
const asc = !this.sortDesc[this.sortCol];
if (typeof aValue === 'number' && typeof bValue === 'number') {
return asc ? aValue - bValue : bValue - aValue;
} else {
return asc ? aValue.toString().localeCompare(bValue.toString()) : bValue.toString().localeCompare(aValue.toString());
}
};

private sort() {
if (!this.rowData.length || !(this.sortCol in this.rowData[0].values)) return;

const sortedRowElems: Array<HTMLTableRowElement> = [];

this.rowData.sort(this.sortFunc);
for (const row of this.rowData) {
sortedRowElems.push(row.rowElement);
if (row.children) {
row.children.sort(this.sortFunc);
sortedRowElems.push(...row.children.map(v => v.rowElement));
}
}

this.cfg.tableBody.replaceChildren(...sortedRowElems);
}

/**
* Set column to sort by. If set to the current sort column the order will be reversed.
* @param column If omitted use default column.
*/
setSort(column = -1) {
if (this.sortDesc[column] === undefined) column = this.cfg.defaultSortCol;
this.sortDesc[column] = !this.sortDesc[column];
this.sortCol = column;
this.sort();
}

private parseRowValues(rowElement: HTMLTableRowElement): Array<number | string> {
const values: Array<string | number> = [];
for (const cell of rowElement.cells) {
const val = cell.dataset[this.cfg.dataSetKey] ?? cell.innerText;
const numVal = parseFloat(val);
values.push(!isNaN(numVal) ? numVal : val);
}
return values;
}

/**
* Update internal data structure for changed table data.
*/
update() {
this.rowData.length = 0;

for (const rowElement of this.cfg.tableBody.rows) {
const values = this.parseRowValues(rowElement);
if (!rowElement.classList.contains(this.cfg.childRowClass)) {
this.rowData.push({ values, rowElement });
} else {
const parentData = this.rowData[this.rowData.length - 1];
if (!parentData) throw new Error('Child row has no parent!');
if (!parentData.children) parentData.children = [];
parentData.children.push({ values, rowElement });
}
}

this.sort();
}
}
7 changes: 0 additions & 7 deletions ui/index_template.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,6 @@
crossorigin="anonymous"
referrerpolicy="no-referrer" />

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.31.3/js/jquery.tablesorter.min.js"
integrity="sha512-qzgd5cYSZcosqpzpn7zF2ZId8f/8CHmFKZ8j7mU4OUXTNRd5g+ZHBPsgKEwoqxCtdQvExE5LprwwPAgoicguNg=="
crossorigin="anonymous"
referrerpolicy="no-referrer"></script>

<!-- Load the top-level ui/index.ts -->
<script src="../../index.ts" type="module"></script>
<script src="./index.ts" type="module"></script>
Expand Down
Loading