Skip to content
Open
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
17 changes: 17 additions & 0 deletions QualityControl/public/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,23 @@
white-space: nowrap;
}

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

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

.hover-icon {
display: inline-block;
}
}
}

.drop-zone {
position: absolute;
height: 100%;
Expand Down
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,
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(
'button.btn.sort-button',
{
onclick: () => onclick(label, nextSortOrder, hoverIcon),
title: `Sort by ${directionLabel}`,
},
[
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
53 changes: 4 additions & 49 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,53 +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',
id: 'collapse-tree-button',
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]);
62 changes: 56 additions & 6 deletions QualityControl/public/object/objectTreePage.js
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('', [
tableHeaderRow(model),
virtualTable(model, 'side', objectsToDisplay),
]);
}
return tableShow(model);
return h('', [
tableHeaderRow(model),
tableShow(model),
]);
},
Failure: () => null, // Notification is displayed
})),
Expand Down Expand Up @@ -164,11 +180,45 @@ const statusBarRight = (model) => model.object.selected
* @returns {vnode} - virtual node element
*/
const tableShow = (model) =>
h('table.table.table-sm.text-no-select', [
h('thead', [h('tr', [h('th', 'Name')])]),
h('tbody', [treeRows(model)]),
h('table.table.table-sm.text-no-select', h('tbody', [treeRows(model)]));

const tableHeaderRow = (model) => h('.bg-gray-light.pv2', [
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);
},
}),
tableHeader(model.object),
]);

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),
id: 'collapse-tree-button',
}, iconCollapseUp());

const tableSearchInput = (qcObject) =>
h('input.form-control.form-inline.mv2.mh3.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
7 changes: 3 additions & 4 deletions QualityControl/test/public/features/filterTest.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,16 +241,15 @@ export const filterTests = async (url, page, timeout = 5000, testParent) => {
);

let rowCount = await page.evaluate(() => document.querySelectorAll('tr').length);
strictEqual(rowCount, 7);
strictEqual(rowCount, 6);

const runNumber = '0';
await page.locator('#runNumberFilter').fill(runNumber);
await page.locator('#filterElement #triggerFilterButton').click();

await extendTree(3, 5);
await delay(100);

rowCount = await page.evaluate(() => document.querySelectorAll('tr').length);
strictEqual(rowCount, 5); // Due to the filter there are two objects fewer.
strictEqual(rowCount, 4); // Due to the filter there are two objects fewer.
});

await testParent.test('ObjectTree infoPanel should show filtered object versions', { timeout }, async () => {
Expand Down
Loading
Loading