Skip to content

Commit f6d78a5

Browse files
author
Gordon Martin
committed
BL-9697 Fix publisher/originalPublisher searches
1 parent 3457669 commit f6d78a5

3 files changed

Lines changed: 201 additions & 26 deletions

File tree

src/connection/LibraryQueryHooks.test.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ async function cleanup() {
107107
await deleteUser(fredId, sessionToken);
108108
}
109109

110-
const title1 = "Bloom library 2 test book 1 anunlikelykeyword title";
110+
const title1 = "Bloom library 2 test about 1 anunlikelykeyword title";
111111
const title2 =
112112
" test Enabling writers workshops book about topic:Math in the Pacific from bookdash.org";
113113
const title3 = "Another book with anunlikelykeyword";
@@ -136,6 +136,7 @@ beforeAll(async () => {
136136
"bookshelf:Enabling writers workshops",
137137
],
138138
copyright: "Copyright © 2014, Nicole and bookdash.org",
139+
publisher: "Unlikely Publisher",
139140
});
140141
// This is there specifically to NOT be found by tag searches or the copyright search
141142
// It SHOULD be found by the uploader search, however.
@@ -159,6 +160,8 @@ beforeAll(async () => {
159160
className: "_User",
160161
objectId: fredId,
161162
},
163+
publisher: "Unlikely Publisher",
164+
originalPublisher: "Unlikely Original Publisher",
162165
});
163166
} catch (error) {
164167
console.log(JSON.stringify(error));
@@ -209,8 +212,9 @@ it("retrieves a book with topic:Math in tags, but not one with that string in ti
209212
expect(result.data.results[0].title).toBe(title1);
210213
});
211214

215+
// Previously this test failed because one of the two words was "book", which gets removed.
212216
it("retrieves a book with a quoted string, but not one with the two words separately", async () => {
213-
const result = await getBook({ search: '"test book"' });
217+
const result = await getBook({ search: '"test about"' });
214218
expect(result.data.results.length).toBe(1);
215219
expect(result.data.results[0].title).toBe(title1);
216220
});
@@ -222,3 +226,29 @@ it("retrieves a book with quoted tag value", async () => {
222226
expect(result.data.results.length).toBe(1);
223227
expect(result.data.results[0].title).toBe(title1);
224228
});
229+
230+
it("retrieves a book with quoted publisher value", async () => {
231+
const result = await getBook({
232+
search: 'publisher: "Unlikely Publisher"',
233+
});
234+
expect(result.data.results.length).toBe(2);
235+
expect(result.data.results[0].title).toBe(title1);
236+
expect(result.data.results[1].title).toBe(title3);
237+
});
238+
239+
it("retrieves a book with quoted originalPublisher value", async () => {
240+
const result = await getBook({
241+
search: 'originalPublisher: "Unlikely Original Publisher"',
242+
});
243+
expect(result.data.results.length).toBe(1);
244+
expect(result.data.results[0].title).toBe(title3);
245+
});
246+
247+
it("doesn't crash with a facet non-value", async () => {
248+
const result = await getBook({
249+
search: "publisher:",
250+
});
251+
expect(result.data.results.length).toBe(2);
252+
expect(result.data.results[0].title).toBe(title1);
253+
expect(result.data.results[1].title).toBe(title3);
254+
});

src/connection/LibraryQueryHooks.ts

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -777,7 +777,7 @@ export function splitString(
777777
// these would be things like "system:Incoming"
778778
allTagsInDatabase: string[]
779779
): { otherSearchTerms: string; specialParts: string[] } {
780-
/* JH/AP removed April 6 202 because tags was optional (fine),
780+
/* JH/AP removed April 6 2020 because tags was optional (fine),
781781
but then this method would essentially bail out if you didn't provide tags.
782782
783783
if (!allTagsInDatabase) {
@@ -794,6 +794,8 @@ export function splitString(
794794
"phash:",
795795
"level:",
796796
"feature:",
797+
"originalPublisher:", // must come before "publisher:", since "originalPublisher:" includes the other as a substring
798+
"publisher:",
797799
];
798800

799801
const possibleParts = [...facets, ...allTagsInDatabase];
@@ -814,13 +816,16 @@ export function splitString(
814816
gotOne = true;
815817
let end = index + possiblePart.length;
816818
const isFacet = facets.includes(possiblePart);
819+
let part = otherSearchTerms.substring(index, end);
817820
if (isFacet) {
818-
end = otherSearchTerms.indexOf(" ", end);
819-
if (end < 0) {
820-
end = otherSearchTerms.length;
821-
}
821+
const result = getFacetPartWithOrWithoutQuotes(
822+
otherSearchTerms,
823+
index,
824+
end
825+
);
826+
part = result.part;
827+
end = result.end;
822828
}
823-
let part = otherSearchTerms.substring(index, end);
824829
// If possibleParts contains an exact match for the part, we'll keep that case.
825830
// So for example if we have both system:Incoming and system:incoming, it's
826831
// possible to search for either. Otherwise, we want to switch to the case
@@ -847,6 +852,43 @@ export function splitString(
847852
return { otherSearchTerms, specialParts };
848853
}
849854

855+
function getFacetPartWithOrWithoutQuotes(
856+
searchString: string,
857+
startIndex: number,
858+
endIndex: number
859+
): { part: string; end: number } {
860+
const facet = searchString.substring(startIndex, endIndex); // e.g. publisher:
861+
const len = searchString.length;
862+
// Handle corner case of nothing following the facet's "colon".
863+
if (len === endIndex) {
864+
return { part: facet, end: endIndex };
865+
}
866+
// Start looking for the value of the facet.
867+
let start = endIndex;
868+
let end: number;
869+
let facetPart: string;
870+
if (searchString[start] === '"') {
871+
// handle double quote subcase
872+
start++; // don't include the quotes in our result
873+
end = searchString.indexOf('"', start);
874+
if (end < 0) {
875+
end = len;
876+
}
877+
facetPart = searchString.substring(start, end);
878+
end = start + facetPart.length;
879+
if (end < len && searchString[end] === '"') {
880+
end++;
881+
}
882+
} else {
883+
end = searchString.indexOf(" ", start);
884+
if (end < 0) {
885+
end = len;
886+
}
887+
facetPart = searchString.substring(start, end);
888+
}
889+
return { part: facet + facetPart, end };
890+
}
891+
850892
function regexCaseSensitive(value: string) {
851893
return {
852894
$regex: processRegExp(value),
@@ -1151,11 +1193,15 @@ export function constructParseBookQuery(
11511193
};
11521194
}
11531195

1154-
delete params.where.publisher;
1196+
// We (gjm and jt) aren't sure why these two "delete" lines were ever needed, but now that we
1197+
// add params for both publisher and originalPublisher, it doesn't work to delete them here.
1198+
// If removing the delete lines causes a problem, we'll need to look for a different solution.
1199+
1200+
//delete params.where.publisher;
11551201
if (f.publisher) {
11561202
params.where.publisher = f.publisher;
11571203
}
1158-
delete params.where.originalPublisher;
1204+
//delete params.where.originalPublisher;
11591205
if (f.originalPublisher) {
11601206
params.where.originalPublisher = f.originalPublisher;
11611207
}

src/connection/SplitString.test.ts

Lines changed: 115 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ it("a mixture of otherSearchTerms and special parts", () => {
1616
]);
1717
expect(otherSearchTerms).toEqual("dogs cats");
1818
expect(specialParts.length).toBe(1);
19-
expect(specialParts[0]).toBe("topic:Animals");
19+
expect(specialParts.includes("topic:Animals"));
2020
});
2121

2222
it("topic at start", () => {
@@ -30,7 +30,7 @@ it("topic at start", () => {
3030
]);
3131
expect(otherSearchTerms).toEqual("dogs cats");
3232
expect(specialParts.length).toBe(1);
33-
expect(specialParts[0]).toBe("topic:Animals");
33+
expect(specialParts.includes("topic:Animals"));
3434
});
3535

3636
it("topic at end", () => {
@@ -44,7 +44,7 @@ it("topic at end", () => {
4444
]);
4545
expect(otherSearchTerms).toEqual("dogs cats");
4646
expect(specialParts.length).toBe(1);
47-
expect(specialParts[0]).toBe("topic:Animals");
47+
expect(specialParts.includes("topic:Animals"));
4848
});
4949

5050
it("topic and bookshelf name and otherSearchTerms in quotes", () => {
@@ -61,8 +61,8 @@ it("topic and bookshelf name and otherSearchTerms in quotes", () => {
6161
);
6262
expect(otherSearchTerms).toEqual('dogs "black birds"');
6363
expect(specialParts.length).toBe(2);
64-
expect(specialParts[0]).toBe("topic:Animal stories");
65-
expect(specialParts[1]).toBe("bookshelf:enabling writers workshop");
64+
expect(specialParts.includes("topic:Animal stories"));
65+
expect(specialParts.includes("bookshelf:enabling writers workshop"));
6666
});
6767

6868
it("ignores various unhelpful spaces", () => {
@@ -75,10 +75,8 @@ it("ignores various unhelpful spaces", () => {
7575
);
7676
expect(otherSearchTerms).toEqual('dogs "black birds"');
7777
expect(specialParts.length).toBe(2);
78-
// Note that the order in which the parts are extracted depends on their order
79-
// in the topic list, not in the input string.
80-
expect(specialParts[0]).toBe("topic:Math");
81-
expect(specialParts[1]).toBe("bookshelf:enabling writers workshop");
78+
expect(specialParts.includes("topic:Math"));
79+
expect(specialParts.includes("bookshelf:enabling writers workshop"));
8280
});
8381

8482
it("finds uploader and copyright", () => {
@@ -92,10 +90,8 @@ it("finds uploader and copyright", () => {
9290
]);
9391
expect(otherSearchTerms).toEqual("dogs");
9492
expect(specialParts.length).toBe(2);
95-
// Note that the order in which the parts are extracted depends on their order
96-
// in the topic list, not in the input string.
97-
expect(specialParts[0]).toBe("uploader:fred@example");
98-
expect(specialParts[1]).toBe("copyright:sil.org");
93+
expect(specialParts.includes("uploader:fred@example"));
94+
expect(specialParts.includes("copyright:sil.org"));
9995
});
10096

10197
it("corrects case, but not if both cases are valid", () => {
@@ -111,7 +107,110 @@ it("corrects case, but not if both cases are valid", () => {
111107
]);
112108
expect(otherSearchTerms).toEqual("cats");
113109
expect(specialParts.length).toBe(3);
114-
expect(specialParts[0]).toBe("topic:Health");
115-
expect(specialParts[1]).toBe("topic:math");
116-
expect(specialParts[2]).toBe("topic:Math");
110+
expect(specialParts.includes("topic:Health"));
111+
expect(specialParts.includes("topic:math"));
112+
expect(specialParts.includes("topic:Math"));
113+
});
114+
115+
it("handles publisher and original publisher", () => {
116+
const {
117+
otherSearchTerms,
118+
specialParts,
119+
} = splitString("frogs publisher: Pratham originalPublisher:Bob", [
120+
"something irrelevant",
121+
"topic:Math",
122+
]);
123+
expect(otherSearchTerms).toEqual("frogs");
124+
expect(specialParts.length).toBe(2);
125+
expect(specialParts.includes("originalPublisher:Bob"));
126+
expect(specialParts.includes("publisher:Pratham"));
127+
});
128+
129+
it("handles publisher and original publisher with spaces", () => {
130+
const {
131+
otherSearchTerms,
132+
specialParts,
133+
} = splitString(
134+
'frogs publisher: "African Storybook Project" originalPublisher:"Book Dash"',
135+
["something irrelevant", "topic:Math"]
136+
);
137+
expect(otherSearchTerms).toEqual("frogs");
138+
expect(specialParts.length).toBe(2);
139+
expect(specialParts.includes("originalPublisher:Book Dash"));
140+
expect(specialParts.includes("publisher:African Storybook Project"));
141+
});
142+
143+
it("handles corner case of facet with no content", () => {
144+
const { otherSearchTerms, specialParts } = splitString("frogs publisher:", [
145+
"something irrelevant",
146+
"topic:Math",
147+
]);
148+
expect(otherSearchTerms).toEqual("frogs");
149+
expect(specialParts.length).toBe(1);
150+
expect(specialParts.includes("publisher:"));
151+
});
152+
153+
it("handles facet corner case with quotes, but no space", () => {
154+
const {
155+
otherSearchTerms,
156+
specialParts,
157+
} = splitString('frogs publisher:"Pratham"', [
158+
"something irrelevant",
159+
"topic:Math",
160+
]);
161+
expect(otherSearchTerms).toEqual("frogs");
162+
expect(specialParts.length).toBe(1);
163+
expect(specialParts.includes("publisher:Pratham"));
164+
});
165+
166+
it("doesn't crash on missing facet final quote", () => {
167+
const {
168+
otherSearchTerms,
169+
specialParts,
170+
} = splitString('frogs publisher: "Book Dash', [
171+
"something irrelevant",
172+
"topic:Math",
173+
]);
174+
expect(otherSearchTerms).toEqual("frogs");
175+
expect(specialParts.length).toBe(1);
176+
expect(specialParts.includes("publisher:Book Dash"));
177+
});
178+
179+
it("doesn't crash on missing facet leading quote", () => {
180+
const {
181+
otherSearchTerms,
182+
specialParts,
183+
} = splitString('frogs publisher: Book Dash"', [
184+
"something irrelevant",
185+
"topic:Math",
186+
]);
187+
expect(otherSearchTerms).toEqual('frogs Dash"');
188+
expect(specialParts.length).toBe(1);
189+
expect(specialParts.includes("publisher:Book"));
190+
});
191+
192+
it("doesn't crash on mismatched facet quotes", () => {
193+
const {
194+
otherSearchTerms,
195+
specialParts,
196+
} = splitString('frogs publisher: "Book Dash" "another', [
197+
"something irrelevant",
198+
"topic:Math",
199+
]);
200+
expect(otherSearchTerms).toEqual('frogs "another');
201+
expect(specialParts.length).toBe(1);
202+
expect(specialParts.includes("publisher:Book Dash"));
203+
});
204+
205+
it("doesn't crash with facet empry string search", () => {
206+
const {
207+
otherSearchTerms,
208+
specialParts,
209+
} = splitString('frogs publisher: ""', [
210+
"something irrelevant",
211+
"topic:Math",
212+
]);
213+
expect(otherSearchTerms).toEqual("frogs");
214+
expect(specialParts.length).toBe(1);
215+
expect(specialParts.includes("publisher:"));
117216
});

0 commit comments

Comments
 (0)