Skip to content

Commit ca5d12b

Browse files
[O2B-1556] Configure filtering model observability to set active filters to the url (#2142)
Notable changes for users: - When configuring a filter a string version will then appear in the URL Notable changes for developers: - FilteringModel now has a router argument for its constructor. - All pages that use a filteringModel must explicitly set filteringModel.pageIdentifier. This identifier determines if this specific filteringModel instance should sync its active filters to the URL, and which page’s URL should be used to initialize the filter values for that filteringModel. - This is currently done by passing the identifier page identifier via the constructor - All overview page reset() functions have gained a clearUrl boolean argument, that determines if running the reset function should also remove the filters from the url.
1 parent cd02894 commit ca5d12b

32 files changed

Lines changed: 851 additions & 184 deletions

lib/public/Model.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -95,21 +95,27 @@ export default class Model extends Observable {
9595
this._appConfiguration$ = new Observable();
9696
this._inputDebounceTime = INPUT_DEBOUNCE_TIME;
9797

98+
// Setup router
99+
this.router = new QueryRouter();
100+
this.router.observe(this.handleLocationChange.bind(this));
101+
this.router.bubbleTo(this);
102+
registerFrontLinkListener((e) => this.router.handleLinkEvent(e));
103+
98104
// Models
99105

100106
this.home = new HomePageModel(this);
101107
this.home.bubbleTo(this);
102108

103-
this.lhcPeriods = new LhcPeriodsModel(this);
109+
this.lhcPeriods = new LhcPeriodsModel(this.router);
104110
this.lhcPeriods.bubbleTo(this);
105111

106-
this.dataPasses = new DataPassesModel(this);
112+
this.dataPasses = new DataPassesModel(this.router);
107113
this.dataPasses.bubbleTo(this);
108114

109115
this.qcFlags = new QcFlagsModel(this);
110116
this.qcFlags.bubbleTo(this);
111117

112-
this.simulationPasses = new SimulationPassesModel(this);
118+
this.simulationPasses = new SimulationPassesModel(this.router);
113119
this.simulationPasses.bubbleTo(this);
114120

115121
this.qcFlagTypes = new QcFlagTypesModel(this);
@@ -178,12 +184,6 @@ export default class Model extends Observable {
178184
this.errorModel = new ErrorModel();
179185
this.errorModel.bubbleTo(this);
180186

181-
// Setup router
182-
this.router = new QueryRouter();
183-
this.router.observe(this.handleLocationChange.bind(this));
184-
this.router.bubbleTo(this);
185-
registerFrontLinkListener((e) => this.router.handleLinkEvent(e));
186-
187187
// Init pages
188188
this.handleLocationChange();
189189
this.window.addEventListener('resize', debounce(() => this.notify(), 100));

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

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import { expandQueryLikeNestedKey } from '../../../utilities/expandNestedKey.js';
1515
import { SelectionModel } from '../../common/selection/SelectionModel.js';
1616
import { FilterModel } from './FilterModel.js';
17-
import { Observable } from '/js/src/index.js';
17+
import { buildUrl, Observable } from '/js/src/index.js';
1818

1919
/**
2020
* Model representing a filtering system, including filter inputs visibility, filters values and so on
@@ -23,32 +23,52 @@ export class FilteringModel extends Observable {
2323
/**
2424
* Constructor
2525
*
26+
* @param {QueryRouter} router router that controls the application's page navigation
2627
* @param {Object<string, FilterModel>} filters the filters with their label and model
2728
*/
28-
constructor(filters) {
29+
constructor(router, filters) {
2930
super();
30-
3131
this._visualChange$ = new Observable();
32+
this._pageIdentifier = null;
3233

34+
this._router = router;
3335
this._filters = {};
3436
this._filterModels = [];
3537
Object.entries(filters).forEach(([key, model]) => this.put(key, model));
3638
}
3739

40+
/**
41+
* Sets the page identifiers
42+
*
43+
* @param {string} identifier a string identifies a page from the router params.
44+
* Used to prevent unneeded reads/writes from/to the url
45+
* @returns {void}
46+
*/
47+
set pageIdentifier(identifier) {
48+
this._pageIdentifier = identifier;
49+
}
50+
3851
/**
3952
* Reset the filters
4053
*
4154
* @param {boolean} [notify=false] if true the model notifies its observers
55+
* @param {boolean} [clearUrl=false] if true filters will be removed from the url
4256
* @return {void}
4357
*/
44-
reset(notify = false) {
58+
reset(notify = false, clearUrl = false) {
4559
for (const model of this._filterModels) {
4660
model.reset();
4761
}
4862

4963
if (notify) {
5064
this.notify();
5165
}
66+
67+
if (clearUrl) {
68+
const { params } = this._router;
69+
delete params.filter;
70+
this._router.go(buildUrl('?', params), false, true);
71+
}
5272
}
5373

5474
/**
@@ -104,6 +124,22 @@ export class FilteringModel extends Observable {
104124
return this._filters[key];
105125
}
106126

127+
/**
128+
* When the user updates the displayed Objects, the filters should be placed in the URL as well
129+
* @returns {undefined}
130+
*/
131+
setFilterToURL() {
132+
const { params } = this._router;
133+
const newParams = { ...params };
134+
newParams.filter = this.normalized;
135+
136+
if (this._pageIdentifier === params.page) {
137+
this._router.go(buildUrl('?', newParams), false, true);
138+
}
139+
140+
this.notify();
141+
}
142+
107143
/**
108144
* Add new filter
109145
*
@@ -123,7 +159,7 @@ export class FilteringModel extends Observable {
123159

124160
this._filters[key] = filter;
125161
this._filterModels.push(filter);
126-
filter.bubbleTo(this);
162+
filter.observe(() => this.setFilterToURL());
127163
filter.visualChange$?.bubbleTo(this._visualChange$);
128164
}
129165
}

lib/public/components/Filters/common/filtersPanelPopover.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ const filtersToggleContentHeader = (filteringModel) => h('.flex-row.justify-betw
4848
'button#reset-filters.btn.btn-danger',
4949
{
5050
onclick: () => filteringModel.resetFiltering
51-
? filteringModel.resetFiltering()
52-
: filteringModel.reset(true),
51+
? filteringModel.resetFiltering(true, true)
52+
: filteringModel.reset(true, true),
5353
disabled: !filteringModel.isAnyFilterActive(),
5454
},
5555
'Reset all filters',

lib/public/views/DataPasses/DataPassesModel.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,15 @@ import { DataPassesPerSimulationPassOverviewModel } from './PerSimulationPassOve
2121
export class DataPassesModel extends Observable {
2222
/**
2323
* The constructor of the model
24+
* @param {QueryRouter} router router that controls the application's page navigation
2425
*/
25-
constructor() {
26+
constructor(router) {
2627
super();
2728

28-
this._perLhcPeriodOverviewModel = new DataPassesPerLhcPeriodOverviewModel();
29+
this._perLhcPeriodOverviewModel = new DataPassesPerLhcPeriodOverviewModel(router, 'data-passes-per-lhc-period-overview');
2930
this._perLhcPeriodOverviewModel.bubbleTo(this);
3031

31-
this._perSimulationPassOverviewModel = new DataPassesPerSimulationPassOverviewModel();
32+
this._perSimulationPassOverviewModel = new DataPassesPerSimulationPassOverviewModel(router, 'data-passes-per-simulation-pass-overview');
3233
this._perSimulationPassOverviewModel.bubbleTo(this);
3334
}
3435

lib/public/views/DataPasses/DataPassesOverviewModel.js

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,22 @@ import { OverviewPageModel } from '../../models/OverviewModel.js';
2222
export class DataPassesOverviewModel extends OverviewPageModel {
2323
/**
2424
* Constructor
25+
* @param {QueryRouter} router router that controls the application's page navigation
26+
* @param {string} pageIdentifier string that indicates what page this model represents
2527
*/
26-
constructor() {
28+
constructor(router, pageIdentifier) {
2729
super();
28-
this._filteringModel = new FilteringModel({
29-
names: new TextTokensFilterModel(),
30-
'include[byName]': new SelectionFilterModel({
31-
availableOptions: NON_PHYSICS_PRODUCTIONS_NAMES_WORDS.map((word) => ({ label: word.toUpperCase(), value: word })),
32-
}),
33-
});
30+
this._filteringModel = new FilteringModel(
31+
router,
32+
{
33+
names: new TextTokensFilterModel(),
34+
'include[byName]': new SelectionFilterModel({
35+
availableOptions: NON_PHYSICS_PRODUCTIONS_NAMES_WORDS.map((word) => ({ label: word.toUpperCase(), value: word })),
36+
}),
37+
},
38+
);
3439

40+
this._filteringModel.pageIdentifier = pageIdentifier;
3541
this._filteringModel.visualChange$.bubbleTo(this);
3642
this._filteringModel.observe(() => {
3743
this._pagination.currentPage = 1;
@@ -51,10 +57,12 @@ export class DataPassesOverviewModel extends OverviewPageModel {
5157
/**
5258
* Reset this model to its default
5359
*
54-
* @returns {void}
60+
* @param {boolean} _fetch Whether to refetch all data after filters have been reset
61+
* @param {boolean} [clearUrl=false] if true filters will be removed from the url
62+
* @return {void}
5563
*/
56-
reset() {
57-
this._filteringModel.reset();
64+
reset(_fetch = true, clearUrl = false) {
65+
this._filteringModel.reset(false, clearUrl);
5866
super.reset();
5967
}
6068

lib/public/views/DataPasses/PerLhcPeriodOverview/DataPassesPerLhcPeriodOverviewModel.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ import { buildUrl } from '/js/src/index.js';
1919
export class DataPassesPerLhcPeriodOverviewModel extends DataPassesOverviewModel {
2020
/**
2121
* Constructor
22+
* @param {QueryRouter} router router that controls the application's page navigation
23+
* @param {string} pageIdentifier string that indicates what page this model represents
2224
*/
23-
constructor() {
24-
super();
25+
constructor(router, pageIdentifier) {
26+
super(router, pageIdentifier);
2527
this._lhcPeriodId = null;
2628
}
2729

lib/public/views/DataPasses/PerSimulationPassOverview/DataPassesPerSimulationPassOverviewModel.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ import { DataPassesOverviewModel } from '../DataPassesOverviewModel.js';
2121
export class DataPassesPerSimulationPassOverviewModel extends DataPassesOverviewModel {
2222
/**
2323
* Constructor
24+
* @param {QueryRouter} router router that controls the application's page navigation
25+
* @param {string} pageIdentifier string that indicates what page this model represents
2426
*/
25-
constructor() {
26-
super();
27+
constructor(router, pageIdentifier) {
28+
super(router, pageIdentifier);
2729
this._simulationPass = new ObservableData(RemoteData.notAsked());
2830
this._simulationPass.bubbleTo(this);
2931
}

lib/public/views/Environments/EnvironmentModel.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export class EnvironmentModel extends Observable {
2929
super();
3030

3131
// Sub-models
32-
this._overviewModel = new EnvironmentOverviewModel(model);
32+
this._overviewModel = new EnvironmentOverviewModel(model, 'env-overview');
3333
this._overviewModel.bubbleTo(this);
3434

3535
this._detailsModel = new EnvironmentDetailsModel();

lib/public/views/Environments/Overview/EnvironmentOverviewModel.js

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,24 +28,29 @@ export class EnvironmentOverviewModel extends OverviewPageModel {
2828
/**
2929
* Constructor
3030
* @param {Model} model global model
31+
* @param {string} pageIdentifier string that indicates what page this model represents
3132
*/
32-
constructor(model) {
33+
constructor(model, pageIdentifier) {
3334
super();
3435

35-
this._filteringModel = new FilteringModel({
36-
created: new TimeRangeInputModel(),
37-
runNumbers: new RawTextFilterModel(),
38-
statusHistory: new RawTextFilterModel(),
39-
currentStatus: new SelectionFilterModel({
40-
availableOptions: Object.keys(StatusAcronym).map((status) => ({
41-
value: status,
42-
label: coloredEnvironmentStatusComponent(status),
43-
rawLabel: status,
44-
})),
45-
}),
46-
ids: new RawTextFilterModel(),
47-
});
36+
this._filteringModel = new FilteringModel(
37+
model.router,
38+
{
39+
created: new TimeRangeInputModel(),
40+
runNumbers: new RawTextFilterModel(),
41+
statusHistory: new RawTextFilterModel(),
42+
currentStatus: new SelectionFilterModel({
43+
availableOptions: Object.keys(StatusAcronym).map((status) => ({
44+
value: status,
45+
label: coloredEnvironmentStatusComponent(status),
46+
rawLabel: status,
47+
})),
48+
}),
49+
ids: new RawTextFilterModel(),
50+
},
51+
);
4852

53+
this._filteringModel.pageIdentifier = pageIdentifier;
4954
this._filteringModel.observe(() => this._applyFilters(true));
5055
this._filteringModel.visualChange$?.bubbleTo(this);
5156

@@ -87,10 +92,11 @@ export class EnvironmentOverviewModel extends OverviewPageModel {
8792
/**
8893
* Reset all filtering models
8994
* @param {boolean} fetch Whether to refetch all data after filters have been reset
95+
* @param {boolean} [clearUrl=false] if true filters will be removed from the url
9096
* @return {void}
9197
*/
92-
resetFiltering(fetch = true) {
93-
this._filteringModel.reset();
98+
resetFiltering(fetch = true, clearUrl = false) {
99+
this._filteringModel.reset(false, clearUrl);
94100

95101
if (fetch) {
96102
this._applyFilters(true);

lib/public/views/Home/Overview/HomePageModel.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ export class HomePageModel extends Observable {
2626
*/
2727
constructor(model) {
2828
super();
29-
this._runsOverviewModel = new RunsOverviewModel(model);
29+
this._runsOverviewModel = new RunsOverviewModel(model, 'home');
3030
this._runsOverviewModel.bubbleTo(this);
3131

32-
this._logsOverviewModel = new LogsOverviewModel(model, true);
32+
this._logsOverviewModel = new LogsOverviewModel(model, true, 'home');
3333
this._logsOverviewModel.bubbleTo(this);
3434

35-
this._lhcFillsOverviewModel = new LhcFillsOverviewModel(true);
35+
this._lhcFillsOverviewModel = new LhcFillsOverviewModel(model.router, true, 'home');
3636
this._lhcFillsOverviewModel.bubbleTo(this);
3737
}
3838

0 commit comments

Comments
 (0)