Skip to content

Commit f0df221

Browse files
committed
Refactor search, escape query strings for Tantivy
Revert "Add betafeatures" This reverts commit 87145e9c30e63a516739c342b42111d045d516de.
1 parent 8cfaa10 commit f0df221

File tree

8 files changed

+114
-57
lines changed

8 files changed

+114
-57
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ This changelog covers all three packages, as they are (for now) updated as a who
1414
- Add `store.preloadClassesAndProperties` and remove `urls.properties.getAll` and `urls.classes.getAll`. This enables using `atomic-data-browser` without relying on `atomicdata.dev` being available.
1515
- Fix Dialogue form #308
1616
- Fix Race condition of `store.getResourceAsync` #309
17+
- Refactor search, escape query strings for Tantivy
1718

1819
## v0.35.0
1920

data-browser/src/components/SearchFilter.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export function ClassFilter({ filters, setFilters }): JSX.Element {
1818
// Set the filters to the default values of the properties
1919
setFilters({
2020
...filters,
21-
'is-a': klass,
21+
[urls.properties.isA]: klass,
2222
});
2323
}, [klass, JSON.stringify(filters)]);
2424

data-browser/src/routes/SearchRoute.tsx

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,18 @@ export function Search(): JSX.Element {
1818
const [query] = useSearchQuery();
1919
const { drive } = useSettings();
2020
const { scope } = useQueryScopeHandler();
21-
const [filters, setFilters] = useState({
22-
// 'is-a': classes.document,
23-
});
21+
const [filters, setFilters] = useState({});
22+
const [enableFilter, setEnableFilter] = useState(false);
23+
24+
useHotkeys(
25+
'f12',
26+
e => {
27+
e.preventDefault();
28+
setEnableFilter(!enableFilter);
29+
},
30+
[enableFilter],
31+
);
32+
2433
const [showFilter, setShowFilter] = useState(false);
2534

2635
const [selectedIndex, setSelected] = useState(0);
@@ -115,10 +124,12 @@ export function Search(): JSX.Element {
115124
)}
116125
</span>
117126
</Heading>
118-
<Button onClick={() => setShowFilter(!showFilter)}>
119-
<FaFilter />
120-
Filter
121-
</Button>
127+
{enableFilter && (
128+
<Button onClick={() => setShowFilter(!showFilter)}>
129+
<FaFilter />
130+
Filter
131+
</Button>
132+
)}
122133
</div>
123134
{showFilter && (
124135
<ClassFilter setFilters={setFilters} filters={filters} />

lib/src/endpoints.ts

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -9,51 +9,3 @@ export function importJsonAdString(
99
) {
1010
return store.postToServer(importerUrl, jsonAdString);
1111
}
12-
13-
export interface SearchOpts {
14-
/** Fetch full resources instead of subjects */
15-
include?: boolean;
16-
/** Max of how many results to return */
17-
limit?: number;
18-
/** Subject of resource to scope the search to. This should be a parent of the resources you're looking for. */
19-
scope?: string;
20-
/** Property-Value pair of set filters. For now, use the `shortname` of the property as the key. */
21-
filters?: {
22-
[propertyShortname: string]: string;
23-
};
24-
}
25-
26-
/** Uses Tantivy query syntax */
27-
function buildFilterString(filters: { [key: string]: string }): string {
28-
return Object.entries(filters)
29-
.map(([key, value]) => {
30-
return value && value.length > 0 && `${key}:"${value}"`;
31-
})
32-
.join(' AND ');
33-
}
34-
35-
/** Returns the URL of the search query. Fetch that and you get your results! */
36-
export function buildSearchSubject(
37-
store: Store,
38-
query: string,
39-
opts: SearchOpts = {},
40-
) {
41-
const { include = false, limit = 30, scope, filters } = opts;
42-
const url = new URL(store.getServerUrl());
43-
url.pathname = 'search';
44-
query && url.searchParams.set('q', query);
45-
include && url.searchParams.set('include', include.toString());
46-
limit && url.searchParams.set('limit', limit.toString());
47-
// Only add filters if there are any keys, and if any key is defined
48-
const hasFilters =
49-
filters &&
50-
Object.keys(filters).length > 0 &&
51-
Object.values(filters).filter(v => v && v.length > 0).length > 0;
52-
hasFilters && url.searchParams.set('filters', buildFilterString(filters));
53-
54-
if (scope) {
55-
url.searchParams.set('parent', scope);
56-
}
57-
58-
return url.toString();
59-
}

lib/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export * from './error.js';
3838
export * from './endpoints.js';
3939
export * from './datatypes.js';
4040
export * from './parse.js';
41+
export * from './search.js';
4142
export * from './resource.js';
4243
export * from './store.js';
4344
export * from './value.js';

lib/src/search.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { escapeTantivyKey } from './search.js';
2+
3+
const testTuples = [
4+
['https://test', 'https\\://test'],
5+
['https://test.com', 'https\\://test\\.com'],
6+
];
7+
8+
describe('search.ts', () => {
9+
it('Handles resources without an ID', () => {
10+
for (const [input, output] of testTuples) {
11+
expect(escapeTantivyKey(input)).toBe(output);
12+
}
13+
});
14+
});

lib/src/search.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { Store } from './index.js';
2+
3+
export interface SearchOpts {
4+
/** Fetch full resources instead of subjects */
5+
include?: boolean;
6+
/** Max of how many results to return */
7+
limit?: number;
8+
/** Subject of resource to scope the search to. This should be a parent of the resources you're looking for. */
9+
scope?: string;
10+
/** Property-Value pair of set filters. For now, use the `shortname` of the property as the key. */
11+
filters?: {
12+
[propertyShortname: string]: string;
13+
};
14+
}
15+
16+
// https://github.com/quickwit-oss/tantivy/blob/064518156f570ee2aa03cf63be6d5605a96d6285/query-grammar/src/query_grammar.rs#L19
17+
const specialCharsTantivy = [
18+
'+',
19+
'^',
20+
'`',
21+
':',
22+
'{',
23+
'}',
24+
'"',
25+
'[',
26+
']',
27+
'(',
28+
')',
29+
'!',
30+
'\\',
31+
'*',
32+
' ',
33+
// The dot is escaped, even though it's not in Tantivy's list.
34+
'.',
35+
];
36+
37+
/** escape the key conform to Tantivy syntax, escaping all specialCharsTantivy */
38+
export function escapeTantivyKey(key: string) {
39+
return key.replace(
40+
new RegExp(`([${specialCharsTantivy.join('\\')}])`, 'g'),
41+
'\\$1',
42+
);
43+
}
44+
45+
/** Uses Tantivy query syntax */
46+
function buildFilterString(filters: { [key: string]: string }): string {
47+
return Object.entries(filters)
48+
.map(([key, value]) => {
49+
return value && value.length > 0 && `${escapeTantivyKey(key)}:"${value}"`;
50+
})
51+
.join(' AND ');
52+
}
53+
54+
/** Returns the URL of the search query. Fetch that and you get your results! */
55+
export function buildSearchSubject(
56+
store: Store,
57+
query: string,
58+
opts: SearchOpts = {},
59+
) {
60+
const { include = false, limit = 30, scope, filters } = opts;
61+
const url = new URL(store.getServerUrl());
62+
url.pathname = 'search';
63+
query && url.searchParams.set('q', query);
64+
include && url.searchParams.set('include', include.toString());
65+
limit && url.searchParams.set('limit', limit.toString());
66+
// Only add filters if there are any keys, and if any key is defined
67+
const hasFilters =
68+
filters &&
69+
Object.keys(filters).length > 0 &&
70+
Object.values(filters).filter(v => v && v.length > 0).length > 0;
71+
hasFilters && url.searchParams.set('filters', buildFilterString(filters));
72+
73+
if (scope) {
74+
url.searchParams.set('parent', scope);
75+
}
76+
77+
return url.toString();
78+
}

lib/src/store.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ describe('Store', () => {
3232
});
3333

3434
it('accepts a custom fetch implementation', async () => {
35-
const testResourceSubject = 'https://example.com/test';
35+
const testResourceSubject = 'https://atomicdata.dev';
3636

3737
const customFetch = jest.fn(
3838
async (url: RequestInfo | URL, options: RequestInit | undefined) => {

0 commit comments

Comments
 (0)