Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
9d6f065
feat: add setFilterFromURL function to filteringModel
Apr 24, 2026
f6a0212
feat: mundane implementations of setFilterFromURL in OverviewModels
Apr 25, 2026
4058609
feat: implement setfilterFromURL for all runsOverview models
Apr 25, 2026
e253ed6
test: add tests for urlToFilter
Apr 27, 2026
72af509
undo faulty textFilter -> textInputFilter conversion
Apr 27, 2026
571ca62
fix eslint issues
Apr 27, 2026
4e22d71
feat: create filterLogger
May 20, 2026
d23795c
feat: add logs to all filterable overview endpoints
May 20, 2026
192fddd
fix: remove async from setFilterFromURL and improve readability
May 27, 2026
ceb2ced
chore: remove unneeded variable
May 27, 2026
ae2acca
chore: fix fillter getter from request
May 27, 2026
94a9057
chore: initialize FilterLogger with parenthesis
May 27, 2026
ee0dda9
test: uncomment a test suite
May 27, 2026
83c28cb
undo a textInputFilter migration
May 27, 2026
7df8afe
chore: undo filterlogger
May 27, 2026
7bcf905
feat: use replace history instead of pushHistory in FilteringModel
May 27, 2026
09483c4
chore: rename documentation to be more consistent with the rest of th…
May 27, 2026
cc6dc57
feat: reset filters if no filters are present in url
May 27, 2026
ba1ce7d
add test
May 27, 2026
9699f37
fix linting issues
May 27, 2026
7939594
uncomment test suites
May 27, 2026
a806dde
feat: update setFilterFromURL to receive a notify argument
May 28, 2026
19ae3f2
only notify once for rundefinitions filter
May 28, 2026
6b520a6
feat: attempt to fully move filter from url setting to RunModel
May 28, 2026
781bece
feat: addSetFilterFromURL functions to all overview models
May 28, 2026
b817c87
feat: call setFilterFromURL fromLogsModel
May 28, 2026
0401062
feat: move all setFilterFromURL calls entity model files
May 28, 2026
e88a2de
improve selectionModel normalize setter for allowing a different order
May 28, 2026
a092efd
textFilterModel migration.
May 28, 2026
c405d9f
feat: add isInactive getter to FilterModel and SelectionModel
Apr 30, 2026
1a3cbe4
feat: implement isInactive for ToggleFilterModel and MultiComposition…
Apr 30, 2026
0cc8d6a
feat: allow the stable beams only toggle to be toggled and untoggled …
May 28, 2026
87a5941
linter fixes
May 28, 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
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ export class MultiCompositionFilterModel extends FilterModel {
return Object.values(this._filters).every((filter) => filter.isEmpty);
}

/**
* @inheritDoc
*/
get isInactive() {
return Object.values(this._filters).every((filter) => filter.isInactive);
}

/**
* @inheritDoc
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ export class RunDefinitionFilterModel extends SelectionModel {
if (!this.isPhysicsOnly()) {
this.selectedOptions = [];
this.select(RunDefinition.Physics);
this.notify();
}
}

Expand Down
9 changes: 9 additions & 0 deletions lib/public/components/Filters/common/FilterModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,15 @@ export class FilterModel extends Observable {
return this._visualChange$;
}

/**
* States if the filter is active. By default this is equivalent to isEmpty
*
* @return {boolean} true if the filter is active
*/
get isInactive() {
return this.isEmpty;
}

/**
* Utility function to register a filter model as sub-filter model
*
Expand Down
35 changes: 28 additions & 7 deletions lib/public/components/Filters/common/FilteringModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export class FilteringModel extends Observable {

if (clearUrl) {
const { params } = this._router;
delete params.filter;
params.filter = this.normalized;
this._router.go(buildUrl('?', params), false, true);
}
}
Expand All @@ -93,12 +93,7 @@ export class FilteringModel extends Observable {
* @return {boolean} true if at least one filter is active
*/
isAnyFilterActive() {
for (const model of this._filterModels) {
if (!model.isEmpty) {
return true;
}
}
return false;
return !this._filterModels.every((model) => model.isInactive);
}

/**
Expand Down Expand Up @@ -140,6 +135,32 @@ export class FilteringModel extends Observable {
this.notify();
}

/**
* Look for parameters used for filtering in URL and apply them in the layout if it exists
*
* @param {boolean} notify if observers should be notified after setting the filters
* @returns {undefined}
*/
setFilterFromURL(notify = false) {
const { params: { page = '', filter } } = this._router;

if (this._pageIdentifier === page) {
if (filter) {
for (const [key, value] of Object.entries(filter)) {
if (key in this._filters) {
this._filters[key].normalized = value;
}
}
} else {
this.reset();
}
}

if (notify) {
this.notify();
}
}

/**
* Add new filter
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@ export class ToggleFilterModel extends SelectionModel {
/**
* Constructor
* @param {boolean} toggledByDefault If the filter should be toggled by default
* @param {boolean} untoggledIsEmpty if true, will treat the untoggled state (false) as empty.
* @param {boolean} defaultIsInactive if true, will treat the untoggled state (false) as empty.
*/
constructor(toggledByDefault = false, untoggledIsEmpty = false) {
constructor(toggledByDefault = false, defaultIsInactive = false) {
super({
availableOptions: [{ value: true }, { value: false }],
defaultSelection: [{ value: toggledByDefault }],
multiple: false,
allowEmpty: false,
});

this._untoggledIsEmpty = untoggledIsEmpty;
this._defaultIsInactive = defaultIsInactive;
}

/**
Expand All @@ -51,15 +51,22 @@ export class ToggleFilterModel extends SelectionModel {
}

/**
* Determines whether the current value should be considered empty.
* When this._untoggledIsEmpty is set to 'true', then untoggled states (false) will be treated as an empty filter
* Toggles are always filled, as 'false' / untoggled is also considered a value
*
* @return {boolean} `true` if the current value is considered empty,
* otherwise `false`.
* @return {boolean} `false`
*/
get isEmpty() {
if (this._untoggledIsEmpty) {
return this.isToggled === false;
return false;
}

/**
* Returns if the toggle filter is considered 'inactive'
*
* @return {boolean}
*/
get isInactive() {
if (this._defaultIsInactive) {
return this.hasOnlyDefaultSelection();
}

return false;
Expand Down
25 changes: 22 additions & 3 deletions lib/public/components/common/selection/SelectionModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,15 @@ export class SelectionModel extends Observable {
return selected.length === defaultSelection.length && selected.every((item) => defaultSelection.includes(item));
}

/**
* States if the filter is active. By default this is equivalent to isEmpty
*
* @return {boolean} true if the filter is active
*/
get isInactive() {
return this.isEmpty;
}

/**
* Reset the selection to the default
*
Expand Down Expand Up @@ -358,9 +367,19 @@ export class SelectionModel extends Observable {
*/
set normalized(value) {
const options = value.split(',').map((option) => ({ value: option.trim() }));
const postponeSelection = this.options instanceof RemoteData || !this.options?.length;

if (postponeSelection) {
const isRemoteData = this.options instanceof RemoteData;
const noOptions = !this.options?.length;

if (isRemoteData) {
this._availableOptions.match({
Success: (_) => {
this.selectedOptions = options;
},
Other: () => {
this._selectionBacklog = options;
},
});
} else if (noOptions) {
this._selectionBacklog = options;
} else {
this.selectedOptions = options;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { h } from '/js/src/index.js';
*/
export const eorReasonFilterComponent = (eorReasonFilterModel, eorReasonTypes) => {
const eorReasonsCategories = [...new Set(eorReasonTypes.map(({ category }) => category))];
const { category: currentCategory, title: currentTitle } = eorReasonFilterModel;

return [
h('.flex-row', [
Expand All @@ -36,7 +37,7 @@ export const eorReasonFilterComponent = (eorReasonFilterModel, eorReasonTypes) =
h('option', { selected: eorReasonFilterModel.category === '', value: '' }, '-'),
eorReasonsCategories.map((category, index) => h(
`option#eorCategory${index}`,
{ key: category, value: category },
{ key: category, value: category, selected: category === currentCategory },
category,
)),
],
Expand All @@ -54,7 +55,7 @@ export const eorReasonFilterComponent = (eorReasonFilterModel, eorReasonTypes) =
.filter((reason) => reason.category === eorReasonFilterModel.category)
.map(({ title }, index) => h(
`option#eorTitle${index}`,
{ key: title, value: title },
{ key: title, value: title, selected: title === currentTitle },
title || '(empty)',
)),
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { h } from '/js/src/index.js';
import { formatDataPassName } from '../format/formatDataPassName.js';
import { formatDataPassStatusHistory } from '../format/formatStatusHistory.js';
import { checkboxes } from '../../../components/Filters/common/filters/checkboxFilter.js';
import { textInputFilter } from '../../../components/Filters/common/filters/textInputFilter.js';
import { textFilter } from '../../../components/Filters/common/filters/textFilter.js';

/**
* List of active columns for a generic data passes table
Expand All @@ -35,7 +35,7 @@ export const dataPassesActiveColumns = {
visible: true,
sortable: true,
format: (_, dataPass) => formatDataPassName(dataPass),
filter: (filteringModel) => textInputFilter(filteringModel, 'names', 'e.g. LHC22a_apass1, ...', 'w-75'),
filter: (filteringModel) => textFilter(filteringModel.get('names'), { class: 'w-75 mt1', placeholder: 'e.g. LHC22a, lhc23b, ...' }),
balloon: true,
classes: 'w-20',
},
Expand Down
2 changes: 2 additions & 0 deletions lib/public/views/DataPasses/DataPassesModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export class DataPassesModel extends Observable {
* @returns {void}
*/
loadPerLhcPeriodOverview({ lhcPeriodId }) {
this._perLhcPeriodOverviewModel.setFilterFromURL(false);
this._perLhcPeriodOverviewModel.load({ lhcPeriodId });
}

Expand Down Expand Up @@ -68,6 +69,7 @@ export class DataPassesModel extends Observable {
*/
loadPerSimulationPassOverview({ simulationPassId }) {
this._perSimulationPassOverviewModel.simulationPassId = parseInt(simulationPassId, 10);
this._perSimulationPassOverviewModel.setFilterFromURL(false);
this._perSimulationPassOverviewModel.load();
}

Expand Down
9 changes: 9 additions & 0 deletions lib/public/views/DataPasses/DataPassesOverviewModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ export class DataPassesOverviewModel extends OverviewPageModel {
return this._filteringModel.normalized;
}

/**
* Set underlying FilteringModel's filters from the query parameters in the URL
*
* @param {boolean} notify if the FilteringModel should notify it's observers after finishing setting the filters
*/
setFilterFromURL(notify) {
this._filteringModel.setFilterFromURL(notify);
}

/**
* Reset this model to its default
*
Expand Down
1 change: 1 addition & 0 deletions lib/public/views/Environments/EnvironmentModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export class EnvironmentModel extends Observable {
*/
loadOverview() {
if (!this._overviewModel.pagination.isInfiniteScrollEnabled) {
this._overviewModel.setFilterFromURL(false);
this._overviewModel.load();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ export class EnvironmentOverviewModel extends OverviewPageModel {
this._filteringModel.observe(() => this._applyFilters(true));
this._filteringModel.visualChange$?.bubbleTo(this);

this.reset(false);
const updateDebounceTime = () => {
this._debouncedLoad = debounce(this.load.bind(this), model.inputDebounceTime);
};
Expand All @@ -70,6 +69,15 @@ export class EnvironmentOverviewModel extends OverviewPageModel {
return buildUrl('/api/environments', { filter: this.filteringModel.normalized });
}

/**
* Set underlying FilteringModel's filters from the query parameters in the URL
*
* @param {boolean} notify if the FilteringModel should notify it's observers after finishing setting the filters
*/
setFilterFromURL(notify) {
this._filteringModel.setFilterFromURL(notify);
}

/**
* Returns the current environments list as remote data
*
Expand Down
1 change: 1 addition & 0 deletions lib/public/views/LhcFills/LhcFills.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export default class LhcFills extends Observable {
* @returns {void}
*/
loadOverview() {
this._overviewModel.setFilterFromURL(false);
this._overviewModel.load();
}

Expand Down
16 changes: 10 additions & 6 deletions lib/public/views/LhcFills/Overview/LhcFillsOverviewModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@ export class LhcFillsOverviewModel extends OverviewPageModel {
this._filteringModel.pageIdentifier = pageIdentifier;
this._filteringModel.observe(() => this._applyFilters());
this._filteringModel.visualChange$.bubbleTo(this);

this.reset(false);
}

/**
Expand All @@ -72,10 +70,16 @@ export class LhcFillsOverviewModel extends OverviewPageModel {
* @inheritDoc
*/
getRootEndpoint() {
const params = {
filter: this.filteringModel.normalized,
};
return buildUrl('/api/lhcFills', params);
return buildUrl('/api/lhcFills', { filter: this.filteringModel.normalized });
}

/**
* Set underlying FilteringModel's filters from the query parameters in the URL
*
* @param {boolean} notify if the FilteringModel should notify it's observers after finishing setting the filters
*/
setFilterFromURL(notify) {
this._filteringModel.setFilterFromURL(notify);
}

/**
Expand Down
1 change: 1 addition & 0 deletions lib/public/views/Logs/LogsModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export class LogsModel extends Observable {
*/
loadOverview() {
if (!this._overviewModel.pagination.isInfiniteScrollEnabled) {
this._overviewModel.setFilterFromURL(false);
this._overviewModel.fetchLogs();
}
}
Expand Down
32 changes: 20 additions & 12 deletions lib/public/views/Logs/Overview/LogsOverviewModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,30 +53,38 @@ export class LogsOverviewModel extends Observable {
},
);

this._filteringModel.pageIdentifier = pageIdentifier;
this._overviewSortModel = new SortModel();
this._pagination = new PaginationModel();
const updateDebounceTime = () => {
this._debouncedFetchAllLogs = debounce(this.fetchLogs.bind(this), model.inputDebounceTime);
};

updateDebounceTime();
model.appConfiguration$.observe(() => updateDebounceTime());

// Filters
this.filteringModel.pageIdentifier = pageIdentifier;
excludeAnonymous && this._filteringModel.get('author').update('!Anonymous');
this._filteringModel.observe(() => this._applyFilters());
this._filteringModel.visualChange$.bubbleTo(this);

// Sub-models
this._overviewSortModel = new SortModel();
this._overviewSortModel.observe(() => this._applyFilters(true));
this._overviewSortModel.visualChange$.bubbleTo(this);

this._pagination = new PaginationModel();
this._pagination.observe(() => this.fetchLogs());
this._pagination.itemsPerPageSelector$.observe(() => this.notify());

this._logs = RemoteData.NotAsked();
}

const updateDebounceTime = () => {
this._debouncedFetchAllLogs = debounce(this.fetchLogs.bind(this), model.inputDebounceTime);
};
model.appConfiguration$.observe(() => updateDebounceTime());
updateDebounceTime();

excludeAnonymous && this._filteringModel.get('author').update('!Anonymous');

this.reset(false);
/**
* Set underlying FilteringModel's filters from the query parameters in the URL
*
* @param {boolean} notify if the FilteringModel should notify it's observers after finishing setting the filters
*/
setFilterFromURL(notify) {
this._filteringModel.setFilterFromURL(notify);
}

/**
Expand Down
Loading
Loading