Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

All: gsoc project - Add search filters #3213

Merged
merged 51 commits into from
Aug 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
77d21d0
TDD: Write initial tests
naviji May 14, 2020
bd8e45d
Implement title and body filter
naviji May 14, 2020
0f77856
Implement all previous filters
naviji May 14, 2020
271366c
Implement tag filter eg.tag:important tag:office
naviji May 15, 2020
ba67455
Replace complicated regex with queryParser
naviji May 17, 2020
e805ae5
Move new files to services/searchengine
naviji May 18, 2020
7a2f03f
Refactor with fast SQL query
naviji May 19, 2020
2ddc890
Implement notebook filter eg.notebook:notebook1
naviji May 20, 2020
5c6ca58
Implement date filters (created/updated):YYYYMMDD
naviji May 20, 2020
09ea6cc
Implement smart values (day/week/month/year)
naviji May 21, 2020
bb2f0c5
Implement filter todo:(true/false/*)
naviji May 21, 2020
efecc70
Add filters is and iscompleted
naviji May 29, 2020
3560f4b
Upgrade notebook filter to handle multiple notebooks
naviji May 29, 2020
4a892ef
Upgrade date syntax with partial dates and ranges
naviji Jun 6, 2020
39ac3f2
Fix all existing errors
naviji Jun 10, 2020
f9a8f93
Remove explicit and/or
naviji Jun 11, 2020
636489a
Add -title and -body filters
naviji Jun 11, 2020
acde458
Add -notebook filters
naviji Jun 11, 2020
2729df4
Simplify created/updated with new syntax
naviji Jun 11, 2020
d59db8d
Add support for multiple filters for type and iscompleted
naviji Jun 12, 2020
d453149
Fix negated tag filter (intersect to union)
naviji Jun 12, 2020
234aabe
Add support for any
naviji Jun 12, 2020
f84dc1b
Fix -tag filter when using any
naviji Jun 13, 2020
61c4fe6
Fix -title -body and -text with any
naviji Jun 13, 2020
7eaeafb
Implement tag:* and -tag:*
naviji Jun 17, 2020
7c94e9d
Implement filters for latitude, longitude, altitude
naviji Jun 17, 2020
e189487
Add filtering by MIME type eg. resource:image/jpeg
naviji Jun 18, 2020
b74aa12
Make code clean and readable
naviji Jun 21, 2020
e70943f
Format SQL queries
naviji Jun 24, 2020
fc1b436
Add migration
naviji Jun 24, 2020
eecad62
Add indexes, change filter order
naviji Jun 24, 2020
f36211a
Add validation
naviji Jul 1, 2020
2b2b618
Change time to local
naviji Jul 5, 2020
2cfc33a
Fix null notes error
naviji Jul 7, 2020
2e5f211
Ignore dashes in the word
naviji Jul 17, 2020
7ff5f6f
Update validation
naviji Jul 17, 2020
e57710c
Make relation enum
naviji Jul 17, 2020
1663cea
Refactor tag and resource filter
naviji Jul 17, 2020
d23fbee
Refactor type ,iscompleted, location and date filter
naviji Jul 18, 2020
908b532
Refactor text filter
naviji Jul 18, 2020
84669fc
Warn instead of error
naviji Jul 18, 2020
2792f72
Add filter sourceurl
naviji Jul 26, 2020
aaca2b6
Refactor again
naviji Jul 28, 2020
ab2fc4f
Make recommended changes
naviji Jul 29, 2020
cc20240
Fix incorrect highlighting
naviji Jul 29, 2020
f6c492d
Fix dashes in title/body
naviji Jul 30, 2020
25bd077
Add timeUtils test
naviji Aug 4, 2020
fcf0a1e
Catch error
naviji Aug 6, 2020
8348279
Add explicit bases in parseInt
naviji Aug 6, 2020
f099ca0
Remove redundant filterParser call
naviji Aug 6, 2020
13c0408
Remove unncessary files
naviji Aug 7, 2020
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
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ ReactNativeClient/lib/services/ResourceEditWatcher/index.js
ReactNativeClient/lib/services/ResourceEditWatcher/reducer.js
ReactNativeClient/lib/services/rest/actionApi.desktop.js
ReactNativeClient/lib/services/rest/errors.js
ReactNativeClient/lib/services/searchengine/filterParser.js
ReactNativeClient/lib/services/searchengine/queryBuilder.js
ReactNativeClient/lib/services/SettingUtils.js
ReactNativeClient/lib/services/synchronizer/gui/useSyncTargetUpgrade.js
ReactNativeClient/lib/services/synchronizer/LockHandler.js
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ ReactNativeClient/lib/services/ResourceEditWatcher/index.js
ReactNativeClient/lib/services/ResourceEditWatcher/reducer.js
ReactNativeClient/lib/services/rest/actionApi.desktop.js
ReactNativeClient/lib/services/rest/errors.js
ReactNativeClient/lib/services/searchengine/filterParser.js
ReactNativeClient/lib/services/searchengine/queryBuilder.js
ReactNativeClient/lib/services/SettingUtils.js
ReactNativeClient/lib/services/synchronizer/gui/useSyncTargetUpgrade.js
ReactNativeClient/lib/services/synchronizer/LockHandler.js
Expand Down
155 changes: 155 additions & 0 deletions CliClient/tests/filterParser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/* eslint-disable no-unused-vars */

require('app-module-path').addPath(__dirname);
const filterParser = require('lib/services/searchengine/filterParser.js').default;
// import filterParser from 'lib/services/searchengine/filterParser.js';

const makeTerm = (name, value, negated) => { return { name, value, negated }; };
describe('filterParser should be correct filter for keyword', () => {
it('title', () => {
const searchString = 'title: something';
expect(filterParser(searchString)).toContain(makeTerm('title', 'something', false));
});

it('negated title', () => {
const searchString = '-title: something';
expect(filterParser(searchString)).toContain(makeTerm('title', 'something', true));
});

it('body', () => {
const searchString = 'body:something';
expect(filterParser(searchString)).toContain(makeTerm('body', 'something', false));
});

it('negated body', () => {
const searchString = '-body:something';
expect(filterParser(searchString)).toContain(makeTerm('body', 'something', true));
});

it('title and body', () => {
const searchString = 'title:testTitle body:testBody';
expect(filterParser(searchString)).toContain(makeTerm('title', 'testTitle', false));
expect(filterParser(searchString)).toContain(makeTerm('body', 'testBody', false));
});

it('title with multiple words', () => {
const searchString = 'title:"word1 word2" body:testBody';
expect(filterParser(searchString)).toContain(makeTerm('title', 'word1', false));
expect(filterParser(searchString)).toContain(makeTerm('title', 'word2', false));
expect(filterParser(searchString)).toContain(makeTerm('body', 'testBody', false));
});

it('body with multiple words', () => {
const searchString = 'title:testTitle body:"word1 word2"';
expect(filterParser(searchString)).toContain(makeTerm('title', 'testTitle', false));
expect(filterParser(searchString)).toContain(makeTerm('body', 'word1', false));
expect(filterParser(searchString)).toContain(makeTerm('body', 'word2', false));
});

it('single word text', () => {
const searchString = 'joplin';
expect(filterParser(searchString)).toContain(makeTerm('text', '"joplin"', false));
});

it('multi word text', () => {
const searchString = 'scott joplin';
expect(filterParser(searchString)).toContain(makeTerm('text', '"scott"', false));
expect(filterParser(searchString)).toContain(makeTerm('text', '"joplin"', false));
});

it('negated word text', () => {
const searchString = 'scott -joplin';
expect(filterParser(searchString)).toContain(makeTerm('text', '"scott"', false));
expect(filterParser(searchString)).toContain(makeTerm('text', '"joplin"', true));
});

it('phrase text search', () => {
const searchString = '"scott joplin"';
expect(filterParser(searchString)).toContain(makeTerm('text', '"scott joplin"', false));
});

it('multi word body', () => {
const searchString = 'body:"foo bar"';
expect(filterParser(searchString)).toContain(makeTerm('body', 'foo', false));
expect(filterParser(searchString)).toContain(makeTerm('body', 'bar', false));
});

it('negated tag queries', () => {
const searchString = '-tag:mozart';
expect(filterParser(searchString)).toContain(makeTerm('tag', 'mozart', true));
});


it('created after', () => {
const searchString = 'created:20151218'; // YYYYMMDD
expect(filterParser(searchString)).toContain(makeTerm('created', '20151218', false));
});

it('created before', () => {
const searchString = '-created:20151218'; // YYYYMMDD
expect(filterParser(searchString)).toContain(makeTerm('created', '20151218', true));
});

it('any', () => {
const searchString = 'any:1 tag:123';
expect(filterParser(searchString)).toContain(makeTerm('any', '1', false));
expect(filterParser(searchString)).toContain(makeTerm('tag', '123', false));
});

it('wildcard tags', () => {
let searchString = 'tag:*';
expect(filterParser(searchString)).toContain(makeTerm('tag', '%', false));

searchString = '-tag:*';
expect(filterParser(searchString)).toContain(makeTerm('tag', '%', true));

searchString = 'tag:bl*sphemy';
expect(filterParser(searchString)).toContain(makeTerm('tag', 'bl%sphemy', false));
});

it('wildcard notebooks', () => {
const searchString = 'notebook:my*notebook';
expect(filterParser(searchString)).toContain(makeTerm('notebook', 'my%notebook', false));
});

it('wildcard MIME types', () => {
const searchString = 'resource:image/*';
expect(filterParser(searchString)).toContain(makeTerm('resource', 'image/%', false));
});

it('sourceurl', () => {
let searchString = 'sourceurl:https://www.google.com';
expect(filterParser(searchString)).toContain(makeTerm('sourceurl', 'https://www.google.com', false));

searchString = 'sourceurl:https://www.google.com -sourceurl:https://www.facebook.com';
expect(filterParser(searchString)).toContain(makeTerm('sourceurl', 'https://www.google.com', false));
expect(filterParser(searchString)).toContain(makeTerm('sourceurl', 'https://www.facebook.com', true));
});

it('handle invalid filters', () => {
let searchString = 'titletitle:123';
expect(() => filterParser(searchString)).toThrow(new Error('Invalid filter: titletitle'));

searchString = 'invalid:abc';
expect(() => filterParser(searchString)).toThrow(new Error('Invalid filter: invalid'));

searchString = ':abc';
expect(() => filterParser(searchString)).toThrow(new Error('Invalid filter: '));

searchString = 'type:blah';
expect(() => filterParser(searchString)).toThrow(new Error('The value of filter "type" must be "note" or "todo"'));

searchString = '-type:note';
expect(() => filterParser(searchString)).toThrow(new Error('type can\'t be negated'));

searchString = 'iscompleted:blah';
expect(() => filterParser(searchString)).toThrow(new Error('The value of filter "iscompleted" must be "1" or "0"'));

searchString = '-notebook:n1';
expect(() => filterParser(searchString)).toThrow(new Error('notebook can\'t be negated'));

searchString = '-iscompleted:1';
expect(() => filterParser(searchString)).toThrow(new Error('iscompleted can\'t be negated'));

});
});
naviji marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 1 addition & 1 deletion CliClient/tests/models_ItemChange.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ require('app-module-path').addPath(__dirname);

const { time } = require('lib/time-utils.js');
const { asyncTest, fileContentEqual, revisionService, setupDatabase, setupDatabaseAndSynchronizer, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const SearchEngine = require('lib/services/SearchEngine');
const SearchEngine = require('lib/services/searchengine/SearchEngine');
const ResourceService = require('lib/services/ResourceService');
const ItemChangeUtils = require('lib/services/ItemChangeUtils');
const Note = require('lib/models/Note');
Expand Down
2 changes: 1 addition & 1 deletion CliClient/tests/services_ResourceService.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const fs = require('fs-extra');
const ArrayUtils = require('lib/ArrayUtils');
const ObjectUtils = require('lib/ObjectUtils');
const { shim } = require('lib/shim.js');
const SearchEngine = require('lib/services/SearchEngine');
const SearchEngine = require('lib/services/searchengine/SearchEngine');

process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
Expand Down
3 changes: 1 addition & 2 deletions CliClient/tests/services_SearchEngine.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ require('app-module-path').addPath(__dirname);

const { time } = require('lib/time-utils.js');
const { fileContentEqual, setupDatabase, setupDatabaseAndSynchronizer, asyncTest, db, synchronizer, fileApi, sleep, clearDatabase, switchClient, syncTargetId, objectsEqual, checkThrowAsync } = require('test-utils.js');
const SearchEngine = require('lib/services/SearchEngine');
const SearchEngine = require('lib/services/searchengine/SearchEngine');
const Note = require('lib/models/Note');
const ItemChange = require('lib/models/ItemChange');
const Setting = require('lib/models/Setting');
Expand Down Expand Up @@ -389,5 +389,4 @@ describe('services_SearchEngine', function() {
expect((await engine.search('"- [ ]"', { searchType: SearchEngine.SEARCH_TYPE_BASIC })).length).toBe(1);
expect((await engine.search('"[ ]"', { searchType: SearchEngine.SEARCH_TYPE_BASIC })).length).toBe(2);
}));

});
Loading