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 Mar 28, 2024
1 parent e626575 commit 6bd7741
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 35 deletions.
3 changes: 2 additions & 1 deletion lib/public/components/Filters/common/FilterModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Observable } from '/js/src/index.js';

/**
* Model storing the state of a given filter
*
* @abstract
*/
export class FilterModel extends Observable {
Expand Down Expand Up @@ -49,7 +50,7 @@ export class FilterModel extends Observable {
/**
* Returns the normalized value of the filter, that can be used as URL parameter
*
* @return {string|number|object|array|null} the normalized value
* @return {string|number|object|string[]|number[]|null} the normalized value
* @abstract
*/
get normalized() {
Expand Down
145 changes: 145 additions & 0 deletions lib/public/components/Filters/common/FilteringModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/**
* @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 { 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 {Map<string, FilterModel[]>} filters the filter models indexed by their unique key
*/
constructor(filters) {
super();

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

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L26 - L27 were not covered by tests

this._visualChange$ = new Observable();

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L29 was not covered by tests

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

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

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L31 - L32 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 40 in lib/public/components/Filters/common/FilteringModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/FilteringModel.js#L38-L40

Added lines #L38 - L40 were not covered by tests
}

this._filtersStore = filters;

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L43 was not covered by tests
}

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

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

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L51 - L52 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 64 in lib/public/components/Filters/common/FilteringModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/FilteringModel.js#L60-L64

Added lines #L60 - L64 were not covered by tests
}
}
return ret;

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L67 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 78 in lib/public/components/Filters/common/FilteringModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/FilteringModel.js#L75-L78

Added lines #L75 - L78 were not covered by tests
}
}
return false;

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L81 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 93 in lib/public/components/Filters/common/FilteringModel.js

View check run for this annotation

Codecov / codecov/patch

lib/public/components/Filters/common/FilteringModel.js#L89-L93

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

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

View check run for this annotation

Codecov / codecov/patch

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

Added line #L96 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 105 in lib/public/components/Filters/common/FilteringModel.js

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L104 - L105 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 114 in lib/public/components/Filters/common/FilteringModel.js

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L113 - L114 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 123 in lib/public/components/Filters/common/FilteringModel.js

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L122 - L123 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 135 in lib/public/components/Filters/common/FilteringModel.js

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L134 - L135 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 143 in lib/public/components/Filters/common/FilteringModel.js

View check run for this annotation

Codecov / codecov/patch

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

Added lines #L142 - L143 were not covered by tests
}
}

This file was deleted.

69 changes: 69 additions & 0 deletions lib/public/utilities/serializeQueryParameters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* @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.
*/

/**
* Given a value and a query param prefix, returns a list of key => values representing the corresponding query parameters (null or undefined
* values are dropped)
*
* for example [1, 3] with prefix 'myPrefix' will result in [{key: 'myPrefix[]', value: 1}, {key: 'myPrefix[]', value: 3]
* for example {foo: 1, bar: 3} with prefix 'myPrefix' will result in [{key: 'myPrefix[foo]', value: 1}, {key: 'myPrefix[bar]', value: 3]
*
* @param {string|boolean|number|null|array|object} parameters the parameter to convert to query param
* @param {string} key the query parameter's key
* @return {({key: string, value: (string|number)}|null)[]} the query parameters definition
*/
export const serializeQueryParameters = (parameters, key) => {
if (parameters === null || parameters === undefined) {
return [null];

Check warning on line 27 in lib/public/utilities/serializeQueryParameters.js

View check run for this annotation

Codecov / codecov/patch

lib/public/utilities/serializeQueryParameters.js#L25-L27

Added lines #L25 - L27 were not covered by tests
}

if (Array.isArray(parameters)) {
return parameters.map((parameter) => serializeQueryParameters(parameter, `${key}[]`)).flat();

Check warning on line 31 in lib/public/utilities/serializeQueryParameters.js

View check run for this annotation

Codecov / codecov/patch

lib/public/utilities/serializeQueryParameters.js#L30-L31

Added lines #L30 - L31 were not covered by tests
}

switch (typeof parameters) {
case 'boolean':
return [{ key, value: parameters ? 'true' : 'false' }];
case 'number':
case 'string':
return [{ key, value: parameters }];
case 'object':
return Object.entries(parameters)
.map(([parameterKey, parameter]) => serializeQueryParameters(parameter, `${key}[${parameterKey}]`))

Check warning on line 42 in lib/public/utilities/serializeQueryParameters.js

View check run for this annotation

Codecov / codecov/patch

lib/public/utilities/serializeQueryParameters.js#L34-L42

Added lines #L34 - L42 were not covered by tests
.flat();
default:
return [null];

Check warning on line 45 in lib/public/utilities/serializeQueryParameters.js

View check run for this annotation

Codecov / codecov/patch

lib/public/utilities/serializeQueryParameters.js#L44-L45

Added lines #L44 - L45 were not covered by tests
}
};

/**
* Generate a {URLSearchParams} from an object representing the query parameters
*
* Parameters can be nested ({foo: {bar: 23}}) and values can be an array ({foo: ['bar', 'baz']})
*
* @param {Object} parameters the query parameters
* @return {URLSearchParams} the generated search params
*/
export const generateURLSearchParams = (parameters) => {
const ret = new URLSearchParams();

Check warning on line 58 in lib/public/utilities/serializeQueryParameters.js

View check run for this annotation

Codecov / codecov/patch

lib/public/utilities/serializeQueryParameters.js#L57-L58

Added lines #L57 - L58 were not covered by tests

for (const mainKey in parameters) {
const serializedQueryParameters = serializeQueryParameters(parameters[mainKey], mainKey);
for (const serializedQueryParameter of serializedQueryParameters) {
if (serializedQueryParameter) {
ret.append(serializedQueryParameter.key, serializedQueryParameter.value);

Check warning on line 64 in lib/public/utilities/serializeQueryParameters.js

View check run for this annotation

Codecov / codecov/patch

lib/public/utilities/serializeQueryParameters.js#L60-L64

Added lines #L60 - L64 were not covered by tests
}
}
}
return ret;

Check warning on line 68 in lib/public/utilities/serializeQueryParameters.js

View check run for this annotation

Codecov / codecov/patch

lib/public/utilities/serializeQueryParameters.js#L68

Added line #L68 was not covered by tests
};

0 comments on commit 6bd7741

Please sign in to comment.