Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
73e2d60
feat: add search bar to the table header of the object tree page
AlexJanson Dec 18, 2025
c4b9b58
feat: add the collapse all button to the table header
AlexJanson Dec 18, 2025
1b8eeab
feat: add reusable component for adding table sort buttons
AlexJanson Dec 18, 2025
4273674
feat: add more margin on the sort direction icon
AlexJanson Dec 18, 2025
fc4452f
feat: add sortable column to virtual table
AlexJanson Dec 18, 2025
4b06d78
style: fix linting errors
AlexJanson Dec 18, 2025
5078fd9
feat: add hover title text to sort button
AlexJanson Dec 18, 2025
8c9450f
test: fix object tree tests
AlexJanson Dec 18, 2025
9c4bf1a
fix: dom node errors when rerendering
AlexJanson Dec 18, 2025
b43ad11
test: fix failing tests due to changes
AlexJanson Dec 18, 2025
ee02932
style: fix linting errors
AlexJanson Dec 18, 2025
dc87639
wip: trying to get the table actions inside the table header
AlexJanson Jan 7, 2026
4677c41
feat: add table actions inside a fake table header
AlexJanson Jan 7, 2026
cbcb41e
Merge remote-tracking branch 'origin/dev' into improv/QCG/OGUI-1853/m…
AlexJanson Jan 7, 2026
2dc0c40
test: fix failing tests due to markup change
AlexJanson Jan 7, 2026
073514e
style: fix linting errors
AlexJanson Jan 7, 2026
b4542d0
fix: virtual table not working anymore
AlexJanson Jan 9, 2026
1c64d62
Merge remote-tracking branch 'origin/dev' into improv/QCG/OGUI-1853/m…
AlexJanson Jan 9, 2026
baf33e0
chore: remove unused none sorting option
AlexJanson Jan 13, 2026
076e2f9
feat: add label name to title on sortable buttons
AlexJanson Jan 13, 2026
aaef6fc
feat: add better function names, remove redudant class, simplify func…
AlexJanson Jan 13, 2026
e1b7515
style: fix linting errors
AlexJanson Jan 13, 2026
196a51e
Merge remote-tracking branch 'origin/dev' into improv/QCG/OGUI-1853/m…
AlexJanson Jan 13, 2026
3485848
test: update the selector to select the right element
AlexJanson Jan 13, 2026
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
17 changes: 17 additions & 0 deletions QualityControl/public/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,20 @@
.whitespace-nowrap {
white-space: nowrap;
}

.sort-button {
.hover-icon {
display: none;
opacity: 0.6;
}

&:hover {
.current-icon {
display: none;
}

.hover-icon {
display: inline-block;
}
}
}
24 changes: 24 additions & 0 deletions QualityControl/public/common/enums/columnSort.enum.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* @license
* Copyright 2019-2020 CERN and copyright holders of ALICE O2.
* See http://alice-o2.web.cern.ch/copyright for details of the copyright holders.
* All rights not expressly granted are reserved.
*
* This software is distributed under the terms of the GNU General Public
* License v3 (GPL Version 3), copied verbatim in the file "COPYING".
*
* In applying this license CERN does not waive the privileges and immunities
* granted to it by virtue of its status as an Intergovernmental Organization
* or submit itself to any jurisdiction.
*/

/**
* Enumeration for sort directions
* @enum {number}
* @readonly
*/
export const SortDirectionsEnum = Object.freeze({
NONE: 0,
Comment thread
graduta marked this conversation as resolved.
Outdated
ASC: 1,
DESC: -1,
});
81 changes: 81 additions & 0 deletions QualityControl/public/common/sortButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* @license
* Copyright 2019-2020 CERN and copyright holders of ALICE O2.
* See http://alice-o2.web.cern.ch/copyright for details of the copyright holders.
* All rights not expressly granted are reserved.
*
* This software is distributed under the terms of the GNU General Public
* License v3 (GPL Version 3), copied verbatim in the file "COPYING".
*
* In applying this license CERN does not waive the privileges and immunities
* granted to it by virtue of its status as an Intergovernmental Organization
* or submit itself to any jurisdiction.
*/

import { SortDirectionsEnum } from '../common/enums/columnSort.enum.js';
import { h, iconCircleX, iconArrowBottom, iconArrowTop } from '/js/src/index.js';

/**
* Get the icon for the sort direction.
* @param {SortDirectionsEnum} direction - direction of the sort.
* @returns {vnode} the correct icon related to the direction.
*/
const getSortIcon = (direction) => {
if (direction === SortDirectionsEnum.ASC) {
return iconArrowTop();
}
if (direction === SortDirectionsEnum.DESC) {
return iconArrowBottom();
}
return iconCircleX();
};

/**
* @callback SortClickCallback
* @param {string} label - The label of the column being sorted.
* @param {number} order - The next sort direction in the cycle.
* @param {vnode} icon - The VNode for the icon representing the next sort state.
* @returns {void}
*/

/**
* Renders a sortable table header button that cycles through sort states.
* Displays the current sort icon and a preview icon of the next state on hover.
* @param {object} props - The component properties.
* @param {number} props.order - The current sort direction value from SortDirectionsEnum.
* @param {object|undefined} props.icon - The VNode/element for the current active sort icon.
* @param {string} props.label - The display text for the column header.
* @param {SortClickCallback} props.onclick - Callback triggered on click.
* @param {Array<number>} [props.sortOptions] - Array of SortDirectionsEnum values defining the
* order of the sort cycle. Defaults to all enum values.
* @returns {object} A HyperScript VNode representing the sortable button.
*/
export const sortableTableHead = ({
order,
icon,
label,
onclick,
sortOptions = [...Object.values(SortDirectionsEnum)],
}) => {
const currentIndex = sortOptions.indexOf(order);
const nextIndex = (currentIndex + 1) % sortOptions.length;
const nextSortOrder = sortOptions[nextIndex];
const hoverIcon = getSortIcon(nextSortOrder);

const directionLabel = Object.keys(SortDirectionsEnum).find((key) => SortDirectionsEnum[key] === nextSortOrder);

return h(
Comment thread
graduta marked this conversation as resolved.
'button.btn.sort-button',
{
onclick: () => onclick(label, nextSortOrder, hoverIcon),
title: `Sort by ${directionLabel}`,
Comment thread
graduta marked this conversation as resolved.
Outdated
},
[
label,
h('span.icon-container.mh1', [
h('span.current-icon', [order != SortDirectionsEnum.NONE ? icon : undefined]),
h('span.hover-icon', [getSortIcon(nextSortOrder)]),
]),
],
);
};
13 changes: 1 addition & 12 deletions QualityControl/public/object/QCObject.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ export default class QCObject extends BaseViewModel {
title: 'Name',
order: 1,
icon: iconArrowTop(),
open: false,
};

this.tree = new ObjectTree('database');
Expand Down Expand Up @@ -115,15 +114,6 @@ export default class QCObject extends BaseViewModel {
this.notify();
}

/**
* Toggle the display of the sort by dropdown
* @returns {undefined}
*/
toggleSortDropdown() {
this.sortBy.open = !this.sortBy.open;
this.notify();
}

/**
* Computes the final list of objects to be seen by user depending on search input from user
* If any of those changes, this method should be called to update the outputs.
Expand Down Expand Up @@ -189,7 +179,7 @@ export default class QCObject extends BaseViewModel {

this._computeFilters();

this.sortBy = { field, title, order, icon, open: false };
this.sortBy = { field, title, order, icon };
this.notify();
}

Expand Down Expand Up @@ -253,7 +243,6 @@ export default class QCObject extends BaseViewModel {
title: 'Name',
order: 1,
icon: iconArrowTop(),
open: false,
};
this._computeFilters();

Expand Down
52 changes: 4 additions & 48 deletions QualityControl/public/object/objectTreeHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
*/

import { h } from '/js/src/index.js';
import { iconCollapseUp, iconArrowBottom, iconArrowTop } from '/js/src/icons.js';
import { filterPanelToggleButton } from '../common/filters/filterViews.js';

/**
Expand All @@ -39,52 +38,9 @@ export default function objectTreeHeader(qcObject, filterModel) {
qcObject.objectsRemote.isSuccess() && h('span', `(${howMany})`),
]),

rightCol: h('.w-25.flex-row.items-center.g2.justify-end', [
filterModel.isRunModeActivated ? null : filterPanelToggleButton(filterModel),
' ',
h('.dropdown', {
id: 'sortTreeButton', title: 'Sort by', class: qcObject.sortBy.open ? 'dropdown-open' : '',
}, [
h('button.btn', {
title: 'Sort by',
onclick: () => qcObject.toggleSortDropdown(),
}, [qcObject.sortBy.title, ' ', qcObject.sortBy.icon]),
h('.dropdown-menu.text-left', [
sortMenuItem(qcObject, 'Name', 'Sort by name ASC', iconArrowTop(), 'name', 1),
sortMenuItem(qcObject, 'Name', 'Sort by name DESC', iconArrowBottom(), 'name', -1),

]),
]),
' ',
h('button.btn', {
title: 'Close whole tree',
onclick: () => qcObject.tree.closeAll(),
disabled: Boolean(qcObject.searchInput),
}, iconCollapseUp()),
' ',
h('input.form-control.form-inline.mh1.w-33', {
id: 'searchObjectTree',
placeholder: 'Search',
type: 'text',
value: qcObject.searchInput,
disabled: qcObject.queryingObjects ? true : false,
oninput: (e) => qcObject.search(e.target.value),
}),
' ',
]),
rightCol: h(
'.w-25.flex-row.items-center.g2.justify-end',
[filterModel.isRunModeActivated ? null : filterPanelToggleButton(filterModel)],
),
};
}

/**
* Create a menu-item for sort-by dropdown
* @param {QcObject} qcObject - Model that manages the QCObject state.
* @param {string} shortTitle - title that gets displayed to the user
* @param {string} title - title that gets displayed to the user on hover
* @param {Icon} icon - svg icon to be used
* @param {string} field - field by which sorting should happen
* @param {number} order - {-1/1}/{DESC/ASC}
* @returns {vnode} - virtual node element
*/
const sortMenuItem = (qcObject, shortTitle, title, icon, field, order) => h('a.menu-item', {
title: title, style: 'white-space: nowrap;', onclick: () => qcObject.sortTree(shortTitle, field, order, icon),
}, [shortTitle, ' ', icon]);
59 changes: 55 additions & 4 deletions QualityControl/public/object/objectTreePage.js
Comment thread
graduta marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,24 @@
* or submit itself to any jurisdiction.
*/

import { h, iconBarChart, iconCaretRight, iconResizeBoth, iconCaretBottom, iconCircleX } from '/js/src/index.js';
import {
h,
iconCollapseUp,
iconBarChart,
iconCaretRight,
iconResizeBoth,
iconCaretBottom,
iconCircleX,
} from '/js/src/index.js';
import { spinner } from '../common/spinner.js';
import { draw } from '../common/object/draw.js';
import timestampSelectForm from './../common/timestampSelectForm.js';
import virtualTable from './virtualTable.js';
import { defaultRowAttributes, qcObjectInfoPanel } from '../common/object/objectInfoCard.js';
import { downloadButton } from '../common/downloadButton.js';
import { resizableDivider } from '../common/resizableDivider.js';
import { SortDirectionsEnum } from '../common/enums/columnSort.enum.js';
import { sortableTableHead } from '../common/sortButton.js';

/**
* Shows a page to explore though a tree of objects with a preview on the right if clicked
Expand All @@ -46,9 +56,15 @@ export default (model) => {
const objectsLoaded = object.list;
const objectsToDisplay = objectsLoaded.filter((qcObject) =>
qcObject.name.toLowerCase().includes(searchInput.toLowerCase()));
return virtualTable(model, 'main', objectsToDisplay);
return h('', [
tableHeader(model.object),
virtualTable(model, 'main', objectsToDisplay),
]);
}
return tableShow(model);
return h('', [
tableHeader(model.object),
tableShow(model),
]);
},
Failure: () => null, // Notification is displayed
})),
Expand Down Expand Up @@ -165,10 +181,45 @@ const statusBarRight = (model) => model.object.selected
*/
const tableShow = (model) =>
h('table.table.table-sm.text-no-select', [
h('thead', [h('tr', [h('th', 'Name')])]),
h('thead', [
h('tr', [
h('th', sortableTableHead({
order: model.object.sortBy.order,
icon: model.object.sortBy.icon,
label: 'Name',
sortOptions: [SortDirectionsEnum.ASC, SortDirectionsEnum.DESC],
onclick: (label, order, icon) => {
model.object.sortTree(label, 'name', order, icon);
},
})),
]),
]),
h('tbody', [treeRows(model)]),
]);

const tableHeader = (qcObject) =>
h('.flex-row.w-100', [
tableSearchInput(qcObject),
tableCollapseAll(qcObject),
]);

const tableCollapseAll = (qcObject) =>
h('button.btn.m2', {
title: 'Close whole tree',
onclick: () => qcObject.tree.closeAll(),
disabled: Boolean(qcObject.searchInput),
}, iconCollapseUp());

const tableSearchInput = (qcObject) =>
h('input.form-control.form-inline.m2.flex-grow', {
id: 'searchObjectTree',
placeholder: 'Search',
type: 'text',
value: qcObject.searchInput,
disabled: qcObject.queryingObjects ? true : false,
oninput: (e) => qcObject.search(e.target.value),
});

/**
* Shows a list of lines <tr> of objects
* @param {Model} model - root model of the application
Expand Down
16 changes: 15 additions & 1 deletion QualityControl/public/object/virtualTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
* or submit itself to any jurisdiction.
*/

import { SortDirectionsEnum } from '../common/enums/columnSort.enum.js';
import { sortableTableHead } from '../common/sortButton.js';
import { h, iconBarChart } from '/js/src/index.js';

let ROW_HEIGHT = 33.6;
Expand Down Expand Up @@ -102,7 +104,19 @@ const objectFullRow = (model, item, location) =>
const tableHeader = () =>
h('table.table.table-sm.text-no-select', {
style: 'margin-bottom:0',
}, h('thead', [h('tr', [h('th', 'Name')])]));
}, h('thead', [
h('tr', [
h('th', sortableTableHead({
order: model.object.sortBy.order,
icon: model.object.sortBy.icon,
label: 'Name',
sortOptions: [SortDirectionsEnum.ASC, SortDirectionsEnum.DESC],
onclick: (label, order, icon) => {
model.object.sortTree(label, 'name', order, icon);
},
})),
]),
]));

/**
* Set styles of the floating table and its position inside the big div .tableLogsContentPlaceholder
Expand Down
Loading
Loading