Skip to content

Commit 292959c

Browse files
authored
Merge pull request #163 from AliceO2Group/feat/expanded-filters
Expanded filters
2 parents 7267d8d + b358524 commit 292959c

15 files changed

Lines changed: 644 additions & 55 deletions

File tree

lib/database/seeders/20200426144121-users.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ module.exports = {
1818
external_id: 123,
1919
name: 'John Doe',
2020
},
21+
{
22+
id: 2,
23+
external_id: 456,
24+
name: 'Jan Jansen',
25+
},
2126
]),
2227

2328
down: (queryInterface, _Sequelize) => queryInterface.bulkDelete('users', null, {}),

lib/database/seeders/20200508094502-logs.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ module.exports = {
1919
origin: 'human',
2020
user_id: 1,
2121
text: 'Power interruption due to unplugged wire.',
22+
created_at: new Date('2000-01-02 12:00:00 PM'),
2223
},
2324
{
2425
title: 'Second entry',
@@ -28,6 +29,7 @@ module.exports = {
2829
text: 'Detected particle ABC123',
2930
parent_log_id: 1,
3031
root_log_id: 1,
32+
created_at: new Date(),
3133
},
3234
{
3335
title: 'Third entry',
@@ -37,22 +39,25 @@ module.exports = {
3739
text: 'Cake at the particle accelerator!',
3840
parent_log_id: 1,
3941
root_log_id: 1,
42+
created_at: new Date(),
4043
},
4144
{
4245
title: 'Fourth entry',
4346
subtype: 'comment',
4447
origin: 'human',
45-
user_id: 1,
48+
user_id: 2,
4649
text: 'The cake is a lie!',
4750
parent_log_id: 2,
4851
root_log_id: 1,
52+
created_at: new Date(),
4953
},
5054
{
5155
title: 'Fifth entry',
5256
subtype: 'run',
5357
origin: 'process',
54-
user_id: 1,
58+
user_id: 2,
5559
text: 'Run #1 stopped',
60+
created_at: new Date(),
5661
},
5762
]),
5863

lib/database/utilities/QueryBuilder.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,30 @@ module.exports = (sequelize) => {
162162
return this.queryBuilder;
163163
}
164164

165+
/**
166+
* Sets a substring filter using the provided value.
167+
*
168+
* @param {symbol} value The required value.
169+
* @returns {QueryBuilder} The current QueryBuilder instance.
170+
*/
171+
substring(value) {
172+
if (!this.queryBuilder.options.where) {
173+
this.queryBuilder.options.where = {};
174+
}
175+
176+
if (this.notFlag) {
177+
this.queryBuilder.options.where[this.column] = {
178+
[Op.notLike]: `%${value}%`,
179+
};
180+
} else {
181+
this.queryBuilder.options.where[this.column] = {
182+
[Op.substring]: value,
183+
};
184+
}
185+
186+
return this.queryBuilder;
187+
}
188+
165189
/**
166190
* Sets the operation.
167191
*

lib/domain/dtos/GetAllLogsDto.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,28 @@ const customJoi = Joi.extend((joi) => ({
2121
coerce: (value) => ({ value: value.split ? value.split(',') : value }),
2222
}));
2323

24+
const CreatedFilterDto = Joi.object({
25+
from: Joi.date().max(new Date().setHours(0, 0, 0, 0)),
26+
to: Joi.date().max(new Date().setHours(23, 59, 59, 999)).when('from', {
27+
is: Joi.exist(),
28+
then: Joi.date().min(Joi.ref('from')),
29+
}),
30+
});
31+
2432
const TagFilterDto = Joi.object({
2533
values: customJoi.stringArray().items(EntityIdDto).single().required(),
2634
operation: Joi.string().valid('and', 'or').required(),
2735
});
2836

2937
const FilterDto = Joi.object({
38+
title: Joi.string().trim(),
39+
author: Joi.string().trim(),
40+
created: CreatedFilterDto,
41+
tag: TagFilterDto,
3042
origin: Joi.string()
3143
.valid('human', 'process'),
3244
parentLog: EntityIdDto,
3345
rootLog: EntityIdDto,
34-
tag: TagFilterDto,
3546
});
3647

3748
const SortDto = Joi.object({

lib/public/components/Filters/author.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,15 @@
1414
import { h } from '/js/src/index.js';
1515

1616
/**
17-
* TODO
18-
* @return {vnode} TODO
17+
* Returns the author filter component
18+
* @param {Object} model The global model object
19+
* @return {vnode} A text box that lets the user look for logs with a specific author
1920
*/
20-
const authorFilter = () => h('', 'TODO');
21+
const authorFilter = (model) => h('input.w-75', {
22+
type: 'text',
23+
id: 'authorFilterText',
24+
value: model.logs.getAuthorFilter(),
25+
oninput: (e) => model.logs.setAuthorFilter(e.target.value),
26+
}, '');
2127

2228
export default authorFilter;

lib/public/components/Filters/created.js

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,37 @@
1313

1414
import { h } from '/js/src/index.js';
1515

16+
let today = new Date();
17+
today.setMinutes(today.getMinutes() - today.getTimezoneOffset());
18+
[today] = today.toISOString().split('T');
19+
1620
/**
17-
* TODO
18-
* @return {vnode} TODO
21+
* Returns the creation date filter components
22+
* @param {Object} model The global model object
23+
* @return {vnode} Two date selection boxes to control the minimum and maximum creation dates for the log filters
1924
*/
20-
const createdFilter = () => h('', 'TODO');
25+
const createdFilter = (model) => {
26+
const createdFrom = model.logs.getCreatedFilterFrom();
27+
const createdTo = model.logs.getCreatedFilterTo();
28+
return h('', [
29+
h('.f6', 'From:'),
30+
h('input.w-75.mv1', {
31+
type: 'date',
32+
id: 'createdFilterFrom',
33+
max: createdTo || today,
34+
value: createdFrom,
35+
onchange: (e) => model.logs.setCreatedFilter('createdFilterFrom', e.target.value, e.target.validity.valid),
36+
}, ''),
37+
h('.f6', 'To:'),
38+
h('input.w-75.mv1', {
39+
type: 'date',
40+
id: 'createdFilterTo',
41+
min: createdFrom,
42+
max: today,
43+
value: createdTo,
44+
onchange: (e) => model.logs.setCreatedFilter('createdFilterTo', e.target.value, e.target.validity.valid),
45+
}, ''),
46+
]);
47+
};
2148

2249
export default createdFilter;

lib/public/components/Filters/title.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,15 @@
1414
import { h } from '/js/src/index.js';
1515

1616
/**
17-
* TODO
18-
* @return {vnode} TODO
17+
* Returns the title filter component
18+
* @param {Object} model The global model object
19+
* @return {vnode} A text box that allows the user to enter a title substring to match against all logs
1920
*/
20-
const titleFilter = () => h('', 'TODO');
21+
const titleFilter = (model) => h('input.w-75', {
22+
type: 'text',
23+
id: 'titleFilterText',
24+
value: model.logs.getTitleFilter(),
25+
oninput: (e) => model.logs.setTitleFilter(e.target.value),
26+
}, '');
2127

2228
export default titleFilter;

lib/public/views/Logs/ActiveColumns/index.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
import titleFilter from '../../../components/Filters/title.js';
1515
import authorFilter from '../../../components/Filters/author.js';
16-
import creationTimeFilter from '../../../components/Filters/created.js';
16+
import createdFilter from '../../../components/Filters/created.js';
1717
import tagsFilter from '../../../components/Filters/tags.js';
1818

1919
/**
@@ -32,21 +32,21 @@ const activeColumns = (model) => ({
3232
visible: true,
3333
size: 'cell-l',
3434
expand: true,
35-
filter: titleFilter(),
35+
filter: titleFilter(model),
3636
},
3737
author: {
3838
name: 'Author',
3939
visible: true,
4040
size: 'cell-l',
4141
format: (author) => author.name,
42-
filter: authorFilter(),
42+
filter: authorFilter(model),
4343
},
4444
createdAt: {
4545
name: 'Created',
4646
visible: true,
4747
size: 'cell-l',
4848
format: (date) => new Date(date).toLocaleString(),
49-
filter: creationTimeFilter(),
49+
filter: createdFilter(model),
5050
},
5151
tags: {
5252
name: 'Tags',

lib/public/views/Logs/Logs.js

Lines changed: 110 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export default class Overview extends Observable {
2929
this.model = model;
3030

3131
this.clearLogs();
32+
this.resetFilters(false);
3233

3334
this.createdLog = RemoteData.NotAsked();
3435
this.title = '';
@@ -55,6 +56,18 @@ export default class Overview extends Observable {
5556
const params = {
5657
'page[offset]': offset,
5758
'page[limit]': this.logsPerPage,
59+
...this.titleFilterText && {
60+
'filter[title]': this.titleFilterText,
61+
},
62+
...this.authorFilterText && {
63+
'filter[author]': this.authorFilterText,
64+
},
65+
...this.createdFilterFrom && {
66+
'filter[created][from]': new Date(`${this.createdFilterFrom}T00:00:00.000`).getTime(),
67+
},
68+
...this.createdFilterTo && {
69+
'filter[created][to]': new Date(`${this.createdFilterTo}T23:59:59.999`).getTime(),
70+
},
5871
...this.tagFilterValues.length > 0 && {
5972
'filter[tag][values]': this.tagFilterValues.join(),
6073
'filter[tag][operation]': this.tagFilterOperation.toLowerCase(),
@@ -262,6 +275,78 @@ export default class Overview extends Observable {
262275
return this.expandedFilters.includes(targetKey);
263276
}
264277

278+
/**
279+
* Returns the current title substring filter
280+
* @returns {String} The current title substring filter
281+
*/
282+
getTitleFilter() {
283+
return this.titleFilterText;
284+
}
285+
286+
/**
287+
* Sets the title substring filter if no new inputs were detected for 200 milliseconds
288+
* @param {String} newTitle The title substring to apply to the filter
289+
* @returns {undefined}
290+
*/
291+
setTitleFilter(newTitle) {
292+
clearTimeout(this.titleFilterDebounce);
293+
this.titleFilterDebounce = setTimeout(() => {
294+
this.titleFilterText = newTitle.trim();
295+
this.fetchAllLogs();
296+
}, 200);
297+
}
298+
299+
/**
300+
* Returns the current author substring filter
301+
* @returns {String} The current author substring filter
302+
*/
303+
getAuthorFilter() {
304+
return this.authorFilterText;
305+
}
306+
307+
/**
308+
* Sets the author substring filter if no new inputs were detected for 200 milliseconds
309+
* @param {String} newAuthor The author substring to apply to the filter
310+
* @returns {undefined}
311+
*/
312+
setAuthorFilter(newAuthor) {
313+
clearTimeout(this.authorFilterDebounce);
314+
this.authorFilterDebounce = setTimeout(() => {
315+
this.authorFilterText = newAuthor.trim();
316+
this.fetchAllLogs();
317+
}, 200);
318+
}
319+
320+
/**
321+
* Returns the current minimum creation datetime
322+
* @returns {Integer} The current minimum creation datetime
323+
*/
324+
getCreatedFilterFrom() {
325+
return this.createdFilterFrom;
326+
}
327+
328+
/**
329+
* Returns the current maximum creation datetime
330+
* @returns {Integer} The current maximum creation datetime
331+
*/
332+
getCreatedFilterTo() {
333+
return this.createdFilterTo;
334+
}
335+
336+
/**
337+
* Set a datetime for the creation datetime filter
338+
* @param {String} key The filter value to apply the datetime to
339+
* @param {Object} date The datetime to be applied to the creation datetime filter
340+
* @param {Boolean} valid Whether the inserted date passes validity check
341+
* @returns {undefined}
342+
*/
343+
setCreatedFilter(key, date, valid) {
344+
if (valid) {
345+
this[key] = date;
346+
this.fetchAllLogs();
347+
}
348+
}
349+
265350
/**
266351
* Add a tag to the filter
267352
* @param {string} tag The tag to be added to the filter criteria
@@ -312,6 +397,30 @@ export default class Overview extends Observable {
312397
this.notify();
313398
}
314399

400+
/**
401+
* Returns all filters to their default (empty) values
402+
* @param {Boolean} fetch Whether to refetch all logs after filters have been reset
403+
* @return {undefined}
404+
*/
405+
resetFilters(fetch = true) {
406+
this.titleFilterText = '';
407+
this.titleFilterDebounce = null;
408+
409+
this.authorFilterText = '';
410+
this.authorFilterDebounce = null;
411+
412+
this.createdFilterFrom = null;
413+
this.createdFilterTo = null;
414+
415+
this.tagFilterOperation = 'AND';
416+
this.tagFilterValues = [];
417+
this.moreTags = false;
418+
419+
if (fetch) {
420+
this.fetchAllLogs();
421+
}
422+
}
423+
315424
/**
316425
* Toggles the visibility of the menu within the log amounts dropdown
317426
* @return {Boolean} The new state of the amounts dropdown
@@ -351,17 +460,13 @@ export default class Overview extends Observable {
351460
}
352461

353462
/**
354-
* Sets all data related to the Logs to `NotAsked`.
463+
* Sets all data related to the Logs to `NotAsked` and clears pagination settings.
355464
* @returns {undefined}
356465
*/
357466
clearLogs() {
358467
this.logs = RemoteData.NotAsked();
359468
this.collapsedColumns = [];
360-
361469
this.expandedFilters = [];
362-
this.tagFilterValues = [];
363-
this.tagFilterOperation = 'AND';
364-
this.moreTags = false;
365470

366471
this.amountDropdownVisible = false;
367472
this.logsPerPage = 10;

0 commit comments

Comments
 (0)