Skip to content

Commit 4e0c52e

Browse files
committed
feat: Add functionality to filter by title
1 parent 274174d commit 4e0c52e

12 files changed

Lines changed: 239 additions & 68 deletions

File tree

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: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,13 @@ const TagFilterDto = Joi.object({
3535
});
3636

3737
const FilterDto = Joi.object({
38+
title: Joi.string().trim(),
39+
created: CreatedFilterDto,
40+
tag: TagFilterDto,
3841
origin: Joi.string()
3942
.valid('human', 'process'),
4043
parentLog: EntityIdDto,
4144
rootLog: EntityIdDto,
42-
created: CreatedFilterDto,
43-
tag: TagFilterDto,
4445
});
4546

4647
const SortDto = Joi.object({

lib/public/components/Filters/created.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,25 +23,25 @@ today.setMinutes(today.getMinutes() - today.getTimezoneOffset());
2323
* @return {vnode} Two date selection boxes to control the minimum and maximum creation dates for the log filters
2424
*/
2525
const createdFilter = (model) => {
26-
const createdFrom = model.logs.getCreatedFrom();
27-
const createdTo = model.logs.getCreatedTo();
26+
const createdFrom = model.logs.getCreatedFilterFrom();
27+
const createdTo = model.logs.getCreatedFilterTo();
2828
return h('', [
2929
h('.f6', 'From:'),
30-
h('input.mv1', {
30+
h('input.w-75.mv1', {
3131
type: 'date',
3232
id: 'createdFilterFrom',
3333
max: createdTo || today,
3434
value: createdFrom,
35-
onchange: (e) => model.logs.setCreatedFilter('createdFrom', e.target.value, e.target.validity.valid),
35+
onchange: (e) => model.logs.setCreatedFilter('createdFilterFrom', e.target.value, e.target.validity.valid),
3636
}, ''),
3737
h('.f6', 'To:'),
38-
h('input.mv1', {
38+
h('input.w-75.mv1', {
3939
type: 'date',
4040
id: 'createdFilterTo',
4141
min: createdFrom,
4242
max: today,
4343
value: createdTo,
44-
onchange: (e) => model.logs.setCreatedFilter('createdTo', e.target.value, e.target.validity.valid),
44+
onchange: (e) => model.logs.setCreatedFilter('createdFilterTo', e.target.value, e.target.validity.valid),
4545
}, ''),
4646
]);
4747
};

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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ 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',

lib/public/views/Logs/Logs.js

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,15 @@ export default class Overview extends Observable {
5555
const params = {
5656
'page[offset]': offset,
5757
'page[limit]': this.logsPerPage,
58-
...this.createdFrom && { 'filter[created][from]': new Date(`${this.createdFrom}T00:00:00.000`).getTime() },
59-
...this.createdTo && { 'filter[created][to]': new Date(`${this.createdTo}T23:59:59.999`).getTime() },
58+
...this.titleFilterText && {
59+
'filter[title]': this.titleFilterText,
60+
},
61+
...this.createdFilterFrom && {
62+
'filter[created][from]': new Date(`${this.createdFilterFrom}T00:00:00.000`).getTime(),
63+
},
64+
...this.createdFilterTo && {
65+
'filter[created][to]': new Date(`${this.createdFilterTo}T23:59:59.999`).getTime(),
66+
},
6067
...this.tagFilterValues.length > 0 && {
6168
'filter[tag][values]': this.tagFilterValues.join(),
6269
'filter[tag][operation]': this.tagFilterOperation.toLowerCase(),
@@ -264,20 +271,41 @@ export default class Overview extends Observable {
264271
return this.expandedFilters.includes(targetKey);
265272
}
266273

274+
/**
275+
* Returns the current title substring filter
276+
* @returns {String} The current title substring filter
277+
*/
278+
getTitleFilter() {
279+
return this.titleFilterText;
280+
}
281+
282+
/**
283+
* Sets the title substring filter if no new inputs were detected for 200 milliseconds
284+
* @param {String} newTitle The title substring to apply to the filter
285+
* @returns {undefined}
286+
*/
287+
setTitleFilter(newTitle) {
288+
clearTimeout(this.titleFilterDebounce);
289+
this.titleFilterDebounce = setTimeout(() => {
290+
this.titleFilterText = newTitle.trim();
291+
this.fetchAllLogs();
292+
}, 200);
293+
}
294+
267295
/**
268296
* Returns the current minimum creation datetime
269297
* @returns {Integer} The current minimum creation datetime
270298
*/
271-
getCreatedFrom() {
272-
return this.createdFrom;
299+
getCreatedFilterFrom() {
300+
return this.createdFilterFrom;
273301
}
274302

275303
/**
276304
* Returns the current maximum creation datetime
277305
* @returns {Integer} The current maximum creation datetime
278306
*/
279-
getCreatedTo() {
280-
return this.createdTo;
307+
getCreatedFilterTo() {
308+
return this.createdFilterTo;
281309
}
282310

283311
/**
@@ -391,8 +419,10 @@ export default class Overview extends Observable {
391419
this.collapsedColumns = [];
392420

393421
this.expandedFilters = [];
394-
this.createdFrom = null;
395-
this.createdTo = null;
422+
this.titleFilterText = '';
423+
this.titleFilterDebounce = null;
424+
this.createdFilterFrom = null;
425+
this.createdFilterTo = null;
396426
this.tagFilterOperation = 'AND';
397427
this.tagFilterValues = [];
398428
this.moreTags = false;

lib/usecases/log/GetAllLogsUseCase.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ class GetAllLogsUseCase {
3939
const { filter, page = {}, sort = { id: 'desc' } } = query;
4040

4141
if (filter) {
42-
const { created, origin, parentLog, rootLog } = filter;
42+
const { title, created, origin, parentLog, rootLog } = filter;
43+
44+
if (title) {
45+
queryBuilder.where('title').substring(title);
46+
}
4347

4448
if (created) {
4549
const from = created.from !== undefined ? created.from : 0;

spec/openapi-source.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,10 @@ components:
754754
description: Specifies the log related filter requirements for a request.
755755
type: object
756756
properties:
757+
title:
758+
description: Specifies the title related filter requirement for a request.
759+
type: string
760+
example: abc
757761
created:
758762
$ref: '#/components/schemas/FilterLogsCreatedOptions'
759763
tag:

spec/openapi.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Generated on Thu, 16 Jul 2020 18:29:30 GMT
1+
# Generated on Thu, 16 Jul 2020 20:40:35 GMT
22

33
openapi: 3.0.0
44
info:
@@ -818,6 +818,10 @@ components:
818818
$ref: '#/components/schemas/EntityId'
819819
tag:
820820
$ref: '#/components/schemas/FilterLogsTagOptions'
821+
title:
822+
description: Specifies the title related filter requirement for a request.
823+
type: string
824+
example: abc
821825
additionalProperties: false
822826
FilterLogsTagOptions:
823827
description: Specifies the tag related filter requirements for a request.

test/database/utilities/QueryBuilder.test.js

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ module.exports = () => {
105105
});
106106

107107
describe('oneOf', () => {
108-
it('should return a single entity with the provided id', async () => {
108+
it('should return entities with the provided id', async () => {
109109
const queryBuilder = new QueryBuilder();
110110
queryBuilder.where('id').oneOf('1', 2);
111111
queryBuilder.orderBy('id', 'asc');
@@ -119,42 +119,80 @@ module.exports = () => {
119119
});
120120

121121
describe('startsWith', () => {
122-
it('should return only entities starting with "Log"', async () => {
122+
it('should return only entities starting with "First"', async () => {
123123
const queryBuilder = new QueryBuilder();
124-
queryBuilder.where('title').startsWith('Log');
124+
queryBuilder.where('title').startsWith('First');
125125
queryBuilder.orderBy('id', 'asc');
126126

127127
const result = await LogRepository.findAll(queryBuilder);
128128
expect(result).to.not.be.null;
129+
result.forEach(({ title }) => {
130+
expect(title.startsWith('First')).to.be.true;
131+
});
129132
});
130133

131-
it('should return only entities not starting with "Log"', async () => {
134+
it('should return only entities not starting with "First"', async () => {
132135
const queryBuilder = new QueryBuilder();
133-
queryBuilder.where('title').not().startsWith('Log');
136+
queryBuilder.where('title').not().startsWith('First');
134137
queryBuilder.orderBy('id', 'asc');
135138

136139
const result = await LogRepository.findAll(queryBuilder);
137140
expect(result).to.not.be.null;
141+
result.forEach(({ title }) => {
142+
expect(title.startsWith('First')).to.be.false;
143+
});
138144
});
139145
});
140146

141147
describe('endsWith', () => {
142-
it('should return only entities ending with "log"', async () => {
148+
it('should return only entities ending with "entry"', async () => {
149+
const queryBuilder = new QueryBuilder();
150+
queryBuilder.where('title').endsWith('entry');
151+
queryBuilder.orderBy('id', 'asc');
152+
153+
const result = await LogRepository.findAll(queryBuilder);
154+
expect(result).to.not.be.null;
155+
result.forEach(({ title }) => {
156+
expect(title.endsWith('entry')).to.be.true;
157+
});
158+
});
159+
160+
it('should return only entities not ending with "entry"', async () => {
161+
const queryBuilder = new QueryBuilder();
162+
queryBuilder.where('title').not().endsWith('entry');
163+
queryBuilder.orderBy('id', 'asc');
164+
165+
const result = await LogRepository.findAll(queryBuilder);
166+
expect(result).to.not.be.null;
167+
result.forEach(({ title }) => {
168+
expect(title.endsWith('entry')).to.be.false;
169+
});
170+
});
171+
});
172+
173+
describe('substring', () => {
174+
it('should return only entities containing "entr"', async () => {
143175
const queryBuilder = new QueryBuilder();
144-
queryBuilder.where('title').endsWith('log');
176+
queryBuilder.where('title').substring('entr');
145177
queryBuilder.orderBy('id', 'asc');
146178

147179
const result = await LogRepository.findAll(queryBuilder);
148180
expect(result).to.not.be.null;
181+
result.forEach(({ title }) => {
182+
expect(title.includes('entr')).to.be.true;
183+
});
149184
});
150185

151-
it('should return only entities not ending with "log"', async () => {
186+
it('should return only entities not containing "og"', async () => {
152187
const queryBuilder = new QueryBuilder();
153-
queryBuilder.where('title').not().endsWith('log');
188+
queryBuilder.where('title').not().substring('entr');
154189
queryBuilder.orderBy('id', 'asc');
155190

156191
const result = await LogRepository.findAll(queryBuilder);
157192
expect(result).to.not.be.null;
193+
result.forEach(({ title }) => {
194+
expect(title.includes('entr')).to.be.false;
195+
});
158196
});
159197
});
160198
});

0 commit comments

Comments
 (0)