Skip to content
This repository was archived by the owner on Jan 14, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
289fc88
Data action buttons
Ehevi Sep 6, 2023
99be0fc
Mark the previous hide buttons as obsolete
Ehevi Sep 6, 2023
368f10e
Eslint cleanup :broom:
Ehevi Sep 6, 2023
afecdcb
Unify the hideSelectedRows function
Ehevi Sep 6, 2023
06b0dfb
Obsolete filter cleanup :broom:
Ehevi Sep 6, 2023
73b9c69
Add new filtering panel
Ehevi Sep 6, 2023
eb3f50c
Display field names in the filter panel
Ehevi Sep 6, 2023
5ccf103
On filtering type change
Ehevi Sep 6, 2023
2287eab
Include filter object in fetch periods request
Ehevi Sep 6, 2023
102d288
Introduce filter model
Ehevi Sep 6, 2023
adc7e26
Fix typo
Ehevi Sep 12, 2023
6705703
Cleanup :broom:
Ehevi Sep 12, 2023
40d2f9e
Use filterModel in the periods model
Ehevi Sep 12, 2023
f8b1e69
Make periods filtering work with the new API
Ehevi Sep 13, 2023
1acb0ce
Add filterTypesMapping
Ehevi Sep 13, 2023
11e6a28
Cleanup :broom:
Ehevi Sep 13, 2023
6c8418a
Mark the old activeFilters component as deprecated
Ehevi Sep 13, 2023
15cd707
Fix js docs
Ehevi Sep 13, 2023
db53307
Use dedicated active filters in the periods view
Ehevi Sep 13, 2023
076dcaf
Make use of the bugfix :bug:
Ehevi Sep 13, 2023
6c264e5
Show active filters
Ehevi Sep 13, 2023
f704e3c
Display ective filters
Ehevi Sep 13, 2023
9a20c43
Fix
Ehevi Sep 13, 2023
a78521c
Apply eslint rules
Ehevi Sep 13, 2023
4873fc0
Apply eslint rules
Ehevi Sep 13, 2023
b932b4f
Apply eslint rules
Ehevi Sep 13, 2023
6ac95e2
Fix hasOwnProperty bug :bug:
Ehevi Sep 14, 2023
31c4f77
Cleanup :broom:
Ehevi Sep 14, 2023
492798e
Apply eslint rules
Ehevi Sep 14, 2023
1fdf951
File parser cleanup :broom:
Ehevi Sep 14, 2023
f4a71dc
Fix typos
Ehevi Sep 14, 2023
9b29aa1
Cleanup :broom:
Ehevi Sep 14, 2023
abb24a5
Address review comments
Ehevi Sep 22, 2023
53f6d87
Address review comments
Ehevi Sep 22, 2023
d00653b
Remove unused getter
Ehevi Sep 22, 2023
3b73bdc
Address review comments
Ehevi Sep 22, 2023
83298f9
Address review comments
Ehevi Sep 22, 2023
be90ee4
Cleanup :broom:
Ehevi Sep 22, 2023
1132b72
Filter objects refactor
Ehevi Sep 22, 2023
5a786ea
Split filter panel
Ehevi Sep 22, 2023
371314d
Disable filter saving
Ehevi Sep 22, 2023
6b48d6a
Apply eslint rules
Ehevi Sep 22, 2023
2e532a1
Address review comments
Ehevi Sep 22, 2023
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
6 changes: 3 additions & 3 deletions app/lib/server/utilities/filterParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ class TransformHelper {
* 1 URL search params are:
*
* .../?filter[or][field1][gt]=10&filter[or][field1][lt]=3&filter[or][field1][_goperator]=or
* &filter[or][filed2][like]=LHC_%25pass&filter[or][filed2][notLike]=LHC_c%25
* &filter[or][field2][like]=LHC_%25pass&filter[or][field2][notLike]=LHC_c%25
* &filter[and][$1][or][f1]=1&filter[and][$1][or][f2]=2&filter[and][$2][or][f3]=3&filter[and][$2][or][f4]=5
*
* 2 The url will be parsed by qs in express to:
Expand All @@ -234,7 +234,7 @@ class TransformHelper {
* "lt": "3",
* "_goperator": "or"
* },
* "filed2": {
* "field2": {
* "like": "LHC_%pass",
* "notLike": "LHC_c%"
* }
Expand Down Expand Up @@ -266,7 +266,7 @@ class TransformHelper {
* [Symbol(lt)]: '3'
* }
* },
* filed2: {
* field2: {
* [Symbol(and)]: {
* [Symbol(like)]: 'LHC_%pass',
* [Symbol(notLike)]: 'LHC_c%'
Expand Down
2 changes: 1 addition & 1 deletion app/lib/utils/ResProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class ResProvider {
* @param {Object} objDefinition define mapping of env vars names to local nodejs process names
* @param {CallableFunction} failurePredicate func(res, objDefinition) that get res - object with read env vars values and objDefinition
* and examine if combination of read env vars fulfil some requirements - default check if all fildes are set
* @param {CallableFunction|String} onFailureAction action on failure - defualt throw error with message about unset fileds
* @param {CallableFunction|String} onFailureAction action on failure - defualt throw error with message about unset fields
* can be string: warn just log warning, error: log error and throw error,
* @returns {Object} desired env vars stored in object under names defined by mapping
*/
Expand Down
36 changes: 32 additions & 4 deletions app/public/components/buttons/dataActionButtons.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,24 @@ import { modalIds, showModal } from '../../views/modal/modal.js';

export const dataActions = {
hide: 'Hide',
obsoleteHide: 'Hide (obsolete)',
reload: 'Reload',
obsoleteDownloadCSV: 'Download CSV (obsolete)',
downloadCSV: 'Download CSV',
copyLink: 'Copy link',
showFilteringPanel: 'Filter',
obsoleteShowFilteringPanel: 'Filter (obsolete)',
};

export default function dataActionButtons(model, applicableDataActions) {
/**
*
* @param {DataAccessModel} model data access model (obsolete)
* @param {Object} applicableDataActions object
* @param {OverviewModel} dataModel - data model (e.g. periodsModel)
* @returns {vnode}
*/

export default function dataActionButtons(model, applicableDataActions, dataModel = null) {
return h('.btn-group',
applicableDataActions[dataActions.reload]
? h('button.btn.btn-secondary.icon-only-button', {
Expand Down Expand Up @@ -57,17 +67,35 @@ export default function dataActionButtons(model, applicableDataActions) {
? copyLinkButton(model.router.getUrl().toString())
: '',

applicableDataActions[dataActions.hide]
applicableDataActions[dataActions.obsoleteHide]
? h('button.btn.icon-only-button', {
className: model.hideCurrentPageMarkedRows ? 'btn-primary' : 'btn-secondary',
onclick: () => model.changeMarkedRowsVisibility(),
onclick: () => {
model.changeMarkedRowsVisibility();
},
}, model.hideCurrentPageMarkedRows ? h('.hide-20-off-white.abs-center') : h('.hide-20-primary.abs-center'))
: '',

applicableDataActions[dataActions.showFilteringPanel]
applicableDataActions[dataActions.hide] && dataModel
? h('button.btn.icon-only-button', {
className: dataModel.shouldHideSelectedRows ? 'btn-primary' : 'btn-secondary',
onclick: () => {
dataModel.toggleSelectedRowsVisibility();
},
}, dataModel.shouldHideSelectedRows ? h('.hide-20-off-white.abs-center') : h('.hide-20-primary.abs-center'))
: '',

applicableDataActions[dataActions.obsoleteShowFilteringPanel]
? h('button.btn.icon-only-button', {
className: model.showFilteringPanel ? 'btn-primary' : 'btn-secondary',
onclick: () => model.changeSearchFieldsVisibility(),
}, model.showFilteringPanel ? h('.slider-20-off-white.abs-center') : h('.slider-20-primary.abs-center'))
: '',

applicableDataActions[dataActions.showFilteringPanel] && dataModel
? h('button.btn.icon-only-button', {
className: dataModel.filterPanelVisible ? 'btn-primary' : 'btn-secondary',
onclick: () => dataModel.toggleFilterPanelVisibility(),
}, dataModel.filterPanelVisible ? h('.slider-20-off-white.abs-center') : h('.slider-20-primary.abs-center'))
: '');
}
51 changes: 51 additions & 0 deletions app/public/components/filter/activeFilters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* @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 { h } from '/js/src/index.js';

/**
* Component responsible for displaying active filters
* @param {FilterModel} model model repsonsible for filter management for the current model
* @returns {vnode} active filters chips
*/
export default function activeFilters(model) {
const onClearAll = () => {
model.reset();
};

const onClearFilter = (filter) => {
model.removeFilter(filter.field, filter.value, filter.type);
};

return [
h('.flex-wrap.justify-between.items-center',
h('.flex-wrap.justify-between.items-center',
h('h5', 'Active filters'),
h('button.btn.btn-secondary.font-size-small', {
onclick: () => onClearAll(),
}, 'Clear all'))),
h('.flex-wrap.items-center.chips',
model.filterObjects.map((filter) => [
h('.chip.filter-chip.inline',
h('.filter-field.inline', filter.field),
h('.filter-type.inline', filter.type),
h('.filter-input.inline', filter.value),
h('button.btn.icon-only-button.transparent', {
onclick: () => {
onClearFilter(filter);
},
}, h('.close-10-primary'))),
])),
];
}
141 changes: 141 additions & 0 deletions app/public/components/filter/filteringPanel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/* eslint-disable no-console */
/**
* @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 { h } from '/js/src/index.js';
import { RCT } from '../../config.js';
const { filterTypes } = RCT;

/**
* Filtering panel
* @param {OverviewModel} model model implementing the OverviewModel interface (e.g. PeriodsModel)
* @returns {vnode} filter panel
*/
export default function filteringPanel(model) {
const fieldSelectId = 'filter-field-select';
const typeSelectId = 'filter-type-select';
const leftInputId = 'filter-left-input';
const rightInputId = 'filter-right-input';

const fields = model.visibleFields;
const aggregatedFiltersTypes = `${filterTypes.match}-${filterTypes.exclude}`;

const onFilteringTypeChange = () => {
const filteringTypeSelect = document.getElementById(typeSelectId);
const selectedType = filteringTypeSelect.options[filteringTypeSelect.selectedIndex].value;
const types = selectedType == filterTypes.between ? ['from', 'to'] : [filterTypes.match, filterTypes.exclude];

const leftFilterPlaceholder = document.getElementById('left-filter-placeholder');
const rightFilterPlaceholder = document.getElementById('right-filter-placeholder');
[leftFilterPlaceholder.innerHTML, rightFilterPlaceholder.innerHTML] = types;
};

const clearUserInput = () => {
document.getElementById(fieldSelectId).value = '';
document.getElementById(typeSelectId).value = '';
document.getElementById(leftInputId).value = '';
document.getElementById(rightInputId).value = '';
};

const onFilterSubmit = async () => {
const fieldNameSelect = document.getElementById(fieldSelectId);
const fieldNameValue = fieldNameSelect.options[fieldNameSelect.selectedIndex].value;

const leftFilterType = document.getElementById('left-filter-placeholder').innerHTML;
const rightFilterType = document.getElementById('right-filter-placeholder').innerHTML;

const leftFilterInput = document.getElementById(leftInputId).value;
const rightFilterInput = document.getElementById(rightInputId).value;

if (rightFilterInput) {
model.filtering.addFilter(fieldNameValue, rightFilterInput, rightFilterType);
}

if (leftFilterInput) {
model.filtering.addFilter(fieldNameValue, leftFilterInput, leftFilterType);
}

clearUserInput();
};

const fieldSelect = h('select.select', {
id: fieldSelectId,
name: 'showOptions' }, [
h('option', {
value: '',
selected: true,
disabled: true,
hidden: true,
}, 'Field'),
fields.map((field) => h('option', { value: field.name }, field.fieldName)),
], h('.close-10-primary'));

const typeSelect = h('select.select', {
id: typeSelectId,
name: 'showOptions',
onchange: () => onFilteringTypeChange(),
}, [
h('option', { value: '', selected: true, disabled: true, hidden: true }, 'Filtering type'),
h('option', { value: aggregatedFiltersTypes }, aggregatedFiltersTypes),
h('option', { value: filterTypes.between }, filterTypes.between),
], h('.close-10-primary'));

const leftInput = h('.text-field',
h('input.form-control.relative', {
style: 'width:120px',
type: 'text',
value: document.getElementById(typeSelectId)?.options[Selection.selectedIndex]?.value,
disabled: false,
id: leftInputId,
required: true,
}),
h('span.placeholder', { id: 'left-filter-placeholder' }, 'match/from'));

const rightInput = h('.text-field',
h('input.form-control.relative', {
style: 'width:120px',
type: 'text',
value: '',
disabled: false,
id: rightInputId,
required: true,
}),
h('span.placeholder', { id: 'right-filter-placeholder' }, 'to/exclude'));

const submitFilterButton = h('button.btn.btn-primary', {
onclick: () => onFilterSubmit(),
}, 'Filter');

const saveFilterButton = h('button.btn.btn-secondary.icon-only-button', {
disabled: true,
}, h('.save-20-primary'));

return h('.font-size-small', [
h('.flex-wrap.justify-between.items-center',
h('.flex-wrap.justify-between.items-center',
h('h5', 'Filter data'),
h('button.btn.btn-secondary', {
onclick: () => {},
}, 'Use defined filters'))),
h('.flex-wrap.justify-between.items-center',
h('.flex-wrap.justify-between.items-center',
fieldSelect,
typeSelect,
leftInput,
rightInput,

submitFilterButton,
saveFilterButton)),
]);
}
6 changes: 6 additions & 0 deletions app/public/model/OverviewModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,10 @@

/**
* @property {PaginationModel} OverviewModel#pagination pagination model of the overview
* @property {RemoteData} currentPageData
* @property {*} fields
* @property {*} visibleFields
* @property {Boolean} shouldHideSelectedRows
* @function fetchCurrentPageData
* @function createDataExport
*/
88 changes: 88 additions & 0 deletions app/public/model/filtering/FilterModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* @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 { Observable } from '/js/src/index.js';

/**
* Class for managing the filters on a general level
*/
export default class FilterModel extends Observable {
constructor() {
super();
this._activeFilters = {};
}

reset() {
this._activeFilters = {};
this.notify();
}

resetFilterValue(field, type) {
this._activeFilters[field][type] = [];
}

resetObjectStructure(field, type) {
if (! Object.prototype.hasOwnProperty.call(this._activeFilters, field)) {
this._activeFilters[field] = {
[type]: [],
};
}
if (! Object.prototype.hasOwnProperty.call(this._activeFilters[field], type)) {
this.resetFilterValue(field, type);
}
}

addFilter(field, value, type) {
this.resetObjectStructure(field, type);
if (['from', 'to'].includes(type)) {
this.resetFilterValue(field, type);
}
this._activeFilters[field][type].push(value);
this.notify();
}

removeFilter(field, value, type) {
this._activeFilters[field][type] = this._activeFilters[field][type].filter((element) => element !== value);
this.notify();
}

buildOperatorPhrase(filterType, value) {
switch (filterType) {
case 'match': return `[or][like]=%${value}%`;
case 'exclude': return `[and][notLike]=%${value}%`;
case 'from': return `[gte]=${value}`;
case 'to': return `[lte]=${value}`;
default: return `=${value}`;
}
}

get filterObjects() {
return Object.entries(this._activeFilters)
.map(([field, typeToValues]) =>
Object.entries(typeToValues)
.map(([type, values]) => values.map((value) => ({ field, type, value })))).flat(2);
}

isAnyFilterActive() {
return Object.keys(this._activeFilters).length > 0;
}

buildSingleFilterPhrase(field, type, value) {
return `filter[${field}]${this.buildOperatorPhrase(type, value)}`;
}

buildFilterPhrase() {
return this.filterObjects.map(({ field, type, value }) => this.buildSingleFilterPhrase(field, type, value)).join('&');
}
}
Loading