Skip to content

Commit

Permalink
[O2B-532] Create a reusable filtering system
Browse files Browse the repository at this point in the history
  • Loading branch information
martinboulais committed Jan 23, 2024
1 parent d2f3188 commit 4c9d62b
Show file tree
Hide file tree
Showing 13 changed files with 953 additions and 35 deletions.
146 changes: 146 additions & 0 deletions lib/public/components/Filters/common/FilteringModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/**
* @license
* Copyright CERN and copyright holders of ALICE O2. This software is
* distributed under the terms of the GNU General Public License v3 (GPL
* Version 3), copied verbatim in the file "COPYING".
*
* See http://alice-o2.web.cern.ch/license for full licensing information.
*
* 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';
import { FilterModel } from './FilterModel.js';

Check failure on line 15 in lib/public/components/Filters/common/FilteringModel.js

View workflow job for this annotation

GitHub Actions / linter

'FilterModel' is defined but never used
import { ToggleableModel } from '../../common/toggle/TogglableModel.js';

/**
* Model representing a filtering system, including filter inputs visibility, filters values and so on
*/
export class FilteringModel extends Observable {
/**
* Constructor
*
* @param {FilterModel} filters the filters list model
*/
constructor(filters) {
super();

Check warning on line 28 in lib/public/components/Filters/common/FilteringModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/FilteringModel.js#L27-L28

Added lines #L27 - L28 were not covered by tests

this._visualChange$ = new Observable();

Check warning on line 30 in lib/public/components/Filters/common/FilteringModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/FilteringModel.js#L30

Added line #L30 was not covered by tests

this._toggleModel = new ToggleableModel();
this._toggleModel.bubbleTo(this._visualChange$);

Check warning on line 33 in lib/public/components/Filters/common/FilteringModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/FilteringModel.js#L32-L33

Added lines #L32 - L33 were not covered by tests

/**
* @type {Map<string, {filter: FilterModel, humanName: (string|undefined)}>}
* @private
*/
this._filtersMeta = new Map();
for (const propertyKey in filters) {
this._addFilter(propertyKey, filters[propertyKey]);

Check warning on line 41 in lib/public/components/Filters/common/FilteringModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/FilteringModel.js#L39-L41

Added lines #L39 - L41 were not covered by tests
}

this._filtersStore = filters;

Check warning on line 44 in lib/public/components/Filters/common/FilteringModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/FilteringModel.js#L44

Added line #L44 was not covered by tests
}

/**
* Reset the filters
*
* @return {void}
*/
reset() {
this._filtersMeta.forEach(({ filter }) => filter.reset());

Check warning on line 53 in lib/public/components/Filters/common/FilteringModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/FilteringModel.js#L52-L53

Added lines #L52 - L53 were not covered by tests
}

/**
* Returns the normalized value of all the filters, without null values
*
* @return {Object} the normalized values
*/
get normalized() {
const ret = {};
for (const [filterKey, { filter }] of this._filtersMeta) {
if (!filter.isEmpty) {
ret[filterKey] = filter.normalized;

Check warning on line 65 in lib/public/components/Filters/common/FilteringModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/FilteringModel.js#L61-L65

Added lines #L61 - L65 were not covered by tests
}
}
return ret;

Check warning on line 68 in lib/public/components/Filters/common/FilteringModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/FilteringModel.js#L68

Added line #L68 was not covered by tests
}

/**
* States if there is currently at least one filter active
*
* @return {boolean} true if at least one filter is active
*/
isAnyFilterActive() {
for (const [, { filter }] of this._filtersMeta) {
if (!filter.isEmpty) {
return true;

Check warning on line 79 in lib/public/components/Filters/common/FilteringModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/FilteringModel.js#L76-L79

Added lines #L76 - L79 were not covered by tests
}
}
return false;

Check warning on line 82 in lib/public/components/Filters/common/FilteringModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/FilteringModel.js#L82

Added line #L82 was not covered by tests
}

/**
* Returns the list of human-readable names of currently active filters
*
* @return {string} the active filters names
*/
get activeFiltersNames() {
const ret = [];
for (const [, { filter, humanName }] of this._filtersMeta) {
if (!filter.isEmpty) {
ret.push(humanName);

Check warning on line 94 in lib/public/components/Filters/common/FilteringModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/FilteringModel.js#L90-L94

Added lines #L90 - L94 were not covered by tests
}
}
return ret.join(', ');

Check warning on line 97 in lib/public/components/Filters/common/FilteringModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/FilteringModel.js#L97

Added line #L97 was not covered by tests
}

/**
* Returns the observable notified any time there is a visual change which has no impact on the actual filtering
*
* @return {Observable} the filters visibility observable
*/
get visualChange$() {
return this._visualChange$;

Check warning on line 106 in lib/public/components/Filters/common/FilteringModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/FilteringModel.js#L105-L106

Added lines #L105 - L106 were not covered by tests
}

/**
* Returns the object storing all the filters models
*
* @return {Object} the filters store
*/
get filters() {
return this._filtersStore;

Check warning on line 115 in lib/public/components/Filters/common/FilteringModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/FilteringModel.js#L114-L115

Added lines #L114 - L115 were not covered by tests
}

/**
* The visibility state of the filters popup
*
* @return {ToggleableModel} the toggle model
*/
get toggleModel() {
return this._toggleModel;

Check warning on line 124 in lib/public/components/Filters/common/FilteringModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/FilteringModel.js#L123-L124

Added lines #L123 - L124 were not covered by tests
}

/**
* Add a filter to the list of registered filters, and bubble filters events (global and visual) to this model
*
* @param {string} filterKey the key of the filter, used to normalize filtering request
* @param {FilterModel} filter the filter model
* @return {void}
* @private
*/
_addFilter(filterKey, filter) {
this._filtersMeta.set(

Check warning on line 136 in lib/public/components/Filters/common/FilteringModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/FilteringModel.js#L135-L136

Added lines #L135 - L136 were not covered by tests
filterKey,
{
filter,
humanName: `${filterKey[0].toUpperCase()}${filterKey.slice(1).replaceAll(/([A-Z])/g, ' $1').toLowerCase()}`,
},
);
filter.bubbleTo(this);
filter.visualChange$.bubbleTo(this._visualChange$);

Check warning on line 144 in lib/public/components/Filters/common/FilteringModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/FilteringModel.js#L143-L144

Added lines #L143 - L144 were not covered by tests
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/**
* @license
* Copyright CERN and copyright holders of ALICE O2. This software is
* distributed under the terms of the GNU General Public License v3 (GPL
* Version 3), copied verbatim in the file "COPYING".
*
* See http://alice-o2.web.cern.ch/license for full licensing information.
*
* 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 { arrayHasSameContent } from '../../../../utilities/arrayHasSameContent.js';
import { FilterModel } from '../FilterModel.js';

/**
* Model for a coma separated values filter
*
* This filter input is a comma separated list of values and its value is an array of values
*/
export class CommaSeparatedValuesFilterModel extends FilterModel {
/**
* Constructor
*/
constructor() {
super();

Check warning on line 26 in lib/public/components/Filters/common/filters/CommaSeparatedValuesFilterModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/filters/CommaSeparatedValuesFilterModel.js#L25-L26

Added lines #L25 - L26 were not covered by tests

this._values = null;
this._raw = '';

Check warning on line 29 in lib/public/components/Filters/common/filters/CommaSeparatedValuesFilterModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/filters/CommaSeparatedValuesFilterModel.js#L28-L29

Added lines #L28 - L29 were not covered by tests
}

// eslint-disable-next-line valid-jsdoc
/**
* @inheritDoc
* @override
*/
reset() {
this._values = null;
this._raw = '';

Check warning on line 39 in lib/public/components/Filters/common/filters/CommaSeparatedValuesFilterModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/filters/CommaSeparatedValuesFilterModel.js#L37-L39

Added lines #L37 - L39 were not covered by tests
}

// eslint-disable-next-line valid-jsdoc
/**
* @inheritDoc
* @override
*/
get isEmpty() {
const { values } = this;
return !values || values.length === 0;

Check warning on line 49 in lib/public/components/Filters/common/filters/CommaSeparatedValuesFilterModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/filters/CommaSeparatedValuesFilterModel.js#L47-L49

Added lines #L47 - L49 were not covered by tests
}

// eslint-disable-next-line valid-jsdoc
/**
* @inheritDoc
* @override
*/
get normalized() {
return this.values;

Check warning on line 58 in lib/public/components/Filters/common/filters/CommaSeparatedValuesFilterModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/filters/CommaSeparatedValuesFilterModel.js#L57-L58

Added lines #L57 - L58 were not covered by tests
}

/**
* Define the current value of the filter
*
* @param {string} raw the raw value of the filter
* @param {array} values the list of parsed values of the filter
*
* @return {void}
*/
update(raw, values) {
const previousValues = [...this._values || []];

Check warning on line 70 in lib/public/components/Filters/common/filters/CommaSeparatedValuesFilterModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/filters/CommaSeparatedValuesFilterModel.js#L69-L70

Added lines #L69 - L70 were not covered by tests

this._values = values;
this._raw = raw;

Check warning on line 73 in lib/public/components/Filters/common/filters/CommaSeparatedValuesFilterModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/filters/CommaSeparatedValuesFilterModel.js#L72-L73

Added lines #L72 - L73 were not covered by tests

if (arrayHasSameContent(values || [], previousValues)) {

Check warning on line 75 in lib/public/components/Filters/common/filters/CommaSeparatedValuesFilterModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/filters/CommaSeparatedValuesFilterModel.js#L75

Added line #L75 was not covered by tests
// Only raw value changed
this.visualChange$.notify();

Check warning on line 77 in lib/public/components/Filters/common/filters/CommaSeparatedValuesFilterModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/filters/CommaSeparatedValuesFilterModel.js#L77

Added line #L77 was not covered by tests
} else {
this.notify();

Check warning on line 79 in lib/public/components/Filters/common/filters/CommaSeparatedValuesFilterModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/filters/CommaSeparatedValuesFilterModel.js#L79

Added line #L79 was not covered by tests
}
}

/**
* Returns the raw value of the filter (the user input)
*
* @return {string} the raw value
*/
get raw() {
return this._raw;

Check warning on line 89 in lib/public/components/Filters/common/filters/CommaSeparatedValuesFilterModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/filters/CommaSeparatedValuesFilterModel.js#L88-L89

Added lines #L88 - L89 were not covered by tests
}

/**
* Return the parsed values of the filter
*
* @return {array} the parsed values
*/
get values() {
if (!Array.isArray(this._values) || this._values.length === 0) {
return null;

Check warning on line 99 in lib/public/components/Filters/common/filters/CommaSeparatedValuesFilterModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/filters/CommaSeparatedValuesFilterModel.js#L97-L99

Added lines #L97 - L99 were not covered by tests
}
return this._values;

Check warning on line 101 in lib/public/components/Filters/common/filters/CommaSeparatedValuesFilterModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/filters/CommaSeparatedValuesFilterModel.js#L101

Added line #L101 was not covered by tests
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/**
* @license
* Copyright CERN and copyright holders of ALICE O2. This software is
* distributed under the terms of the GNU General Public License v3 (GPL
* Version 3), copied verbatim in the file "COPYING".
*
* See http://alice-o2.web.cern.ch/license for full licensing information.
*
* 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 { FilterModel } from '../FilterModel.js';

export const COMPARISON_OPERATORS = ['<', '<=', '=', '>=', '>'];
export const DEFAULT_COMPARISON_OPERATOR = '=';

Check warning on line 17 in lib/public/components/Filters/common/filters/ComparisonOperatorFilterModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/filters/ComparisonOperatorFilterModel.js#L16-L17

Added lines #L16 - L17 were not covered by tests

/**
* Model representing comparison operator filter
*
* @template T
*/
export class ComparisonOperatorFilterModel extends FilterModel {
/**
* Constructor
*/
constructor() {
super();

Check warning on line 29 in lib/public/components/Filters/common/filters/ComparisonOperatorFilterModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/filters/ComparisonOperatorFilterModel.js#L28-L29

Added lines #L28 - L29 were not covered by tests

this._operator = DEFAULT_COMPARISON_OPERATOR;

Check warning on line 31 in lib/public/components/Filters/common/filters/ComparisonOperatorFilterModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/filters/ComparisonOperatorFilterModel.js#L31

Added line #L31 was not covered by tests

/**
* @type {(T|null)}
* @private
*/
this._limit = null;

Check warning on line 37 in lib/public/components/Filters/common/filters/ComparisonOperatorFilterModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/filters/ComparisonOperatorFilterModel.js#L37

Added line #L37 was not covered by tests
}

// eslint-disable-next-line valid-jsdoc
/**
* @override
* @inheritDoc
*/
reset() {
this._operator = DEFAULT_COMPARISON_OPERATOR;
this._limit = null;

Check warning on line 47 in lib/public/components/Filters/common/filters/ComparisonOperatorFilterModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/filters/ComparisonOperatorFilterModel.js#L45-L47

Added lines #L45 - L47 were not covered by tests
}

// eslint-disable-next-line valid-jsdoc
/**
* @inheritDoc
* @override
*/
get isEmpty() {
return this._limit === null;

Check warning on line 56 in lib/public/components/Filters/common/filters/ComparisonOperatorFilterModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/filters/ComparisonOperatorFilterModel.js#L55-L56

Added lines #L55 - L56 were not covered by tests
}

/**
* Updates the value of the operator and limit
*
* @param {object} raw the raw operator or/and limit of the filter
*
* @return {void}
*/
update({ operator: rawOperator, limit: rawLimit }) {
const operator = COMPARISON_OPERATORS.includes(rawOperator) ? rawOperator : this._operator;
let limit = this._limit;
if (rawLimit !== undefined) {
try {
limit = this.parseLimit(rawLimit);

Check warning on line 71 in lib/public/components/Filters/common/filters/ComparisonOperatorFilterModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/filters/ComparisonOperatorFilterModel.js#L66-L71

Added lines #L66 - L71 were not covered by tests
} catch (e) {
// Keep the current limit
}
}
const previousOperator = this._operator;
this._operator = operator;

Check warning on line 77 in lib/public/components/Filters/common/filters/ComparisonOperatorFilterModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/filters/ComparisonOperatorFilterModel.js#L76-L77

Added lines #L76 - L77 were not covered by tests

const previousLimit = this._limit;
this._limit = limit;

Check warning on line 80 in lib/public/components/Filters/common/filters/ComparisonOperatorFilterModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/filters/ComparisonOperatorFilterModel.js#L79-L80

Added lines #L79 - L80 were not covered by tests

if (previousOperator !== this._operator || previousLimit !== this._limit) {
this.notify();

Check warning on line 83 in lib/public/components/Filters/common/filters/ComparisonOperatorFilterModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/filters/ComparisonOperatorFilterModel.js#L82-L83

Added lines #L82 - L83 were not covered by tests
} else {
this.visualChange$.notify();

Check warning on line 85 in lib/public/components/Filters/common/filters/ComparisonOperatorFilterModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/filters/ComparisonOperatorFilterModel.js#L85

Added line #L85 was not covered by tests
}
}

/**
* Parse the given limit into a limit compatible for the current filter
*
* As a default, returns raw limit without modification. Models for specific comparison filter must handle parse here
*
* @param {*} rawLimit the raw value of the limit
*
* @return {T} the parsed limit
* @protected
*/
parseLimit(rawLimit) {
return rawLimit;

Check warning on line 100 in lib/public/components/Filters/common/filters/ComparisonOperatorFilterModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/filters/ComparisonOperatorFilterModel.js#L99-L100

Added lines #L99 - L100 were not covered by tests
}

/**
* Returns the current operator
*
* @return {string} the operator
*/
get operator() {
return this._operator;

Check warning on line 109 in lib/public/components/Filters/common/filters/ComparisonOperatorFilterModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/filters/ComparisonOperatorFilterModel.js#L108-L109

Added lines #L108 - L109 were not covered by tests
}

/**
* Returns the current limit
*
* @return {T} the current limit
*/
get limit() {
return this._limit;

Check warning on line 118 in lib/public/components/Filters/common/filters/ComparisonOperatorFilterModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/filters/ComparisonOperatorFilterModel.js#L117-L118

Added lines #L117 - L118 were not covered by tests
}
}
Loading

0 comments on commit 4c9d62b

Please sign in to comment.