placementt-core 1.400.1249 → 1.400.1250
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/features/search/globalSearch.d.ts +43 -0
- package/lib/features/search/globalSearch.js +88 -6
- package/lib/features/search/globalSearch.js.map +1 -1
- package/lib/features/search/globalSearch.test.d.ts +5 -0
- package/lib/features/search/globalSearch.test.js +119 -0
- package/lib/features/search/globalSearch.test.js.map +1 -0
- package/package.json +1 -1
- package/src/features/search/globalSearch.test.ts +131 -0
- package/src/features/search/globalSearch.ts +88 -5
|
@@ -65,6 +65,11 @@ export type GlobalSearchIndexDefinition = {
|
|
|
65
65
|
filters: (user: UserData, institute?: InstituteData) => string;
|
|
66
66
|
/** Turns one raw hit into a row. */
|
|
67
67
|
toHit: (hit: any) => GlobalSearchHit;
|
|
68
|
+
/**
|
|
69
|
+
* Drops hits the filter couldn't, for records that don't carry what the user's
|
|
70
|
+
* access depends on (a placement holds its student's uid, not their details).
|
|
71
|
+
*/
|
|
72
|
+
keepHits?: (hits: any[], user: UserData) => Promise<any[]>;
|
|
68
73
|
} | {
|
|
69
74
|
/**
|
|
70
75
|
* Searched in memory, from records the caller already holds, instead of
|
|
@@ -72,6 +77,44 @@ export type GlobalSearchIndexDefinition = {
|
|
|
72
77
|
*/
|
|
73
78
|
local: (params: GlobalSearchParams) => GlobalSearchHit[];
|
|
74
79
|
});
|
|
80
|
+
export type SearchStudentScope = {
|
|
81
|
+
kind: "all";
|
|
82
|
+
} | {
|
|
83
|
+
kind: "none";
|
|
84
|
+
} | {
|
|
85
|
+
kind: "some";
|
|
86
|
+
field: string;
|
|
87
|
+
values: string[];
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* The students a staff member may find, by the Students list's rule: admins and
|
|
91
|
+
* "all" see everyone, "some" sees students whose `details[studentFilter]` is one of
|
|
92
|
+
* `studentFilterValues`, anyone else sees no students. Non-staff are left to their
|
|
93
|
+
* own search key, which already limits them to their own records.
|
|
94
|
+
*
|
|
95
|
+
* @param {UserData} user the signed-in user.
|
|
96
|
+
* @return {SearchStudentScope} the students they may find.
|
|
97
|
+
*/
|
|
98
|
+
export declare const searchStudentScope: (user: UserData) => SearchStudentScope;
|
|
99
|
+
/**
|
|
100
|
+
* The cohorts whose placements a staff member may find, by the placement page's
|
|
101
|
+
* rule (canViewPlacement in ViewPlacement.tsx).
|
|
102
|
+
*
|
|
103
|
+
* @param {UserData} user the signed-in user.
|
|
104
|
+
* @return {"all"|"none"|string[]} every cohort, none, or these cohort ids.
|
|
105
|
+
*/
|
|
106
|
+
export declare const searchPlacementCohorts: (user: UserData) => "all" | "none" | string[];
|
|
107
|
+
/**
|
|
108
|
+
* Keeps the placement hits whose student is inside a "some" student scope. The
|
|
109
|
+
* placements index holds the student's uid but not their details, so the filter
|
|
110
|
+
* can't do this; each student is read once. A hit with no student, or whose
|
|
111
|
+
* student can't be read, is dropped rather than shown unchecked.
|
|
112
|
+
*
|
|
113
|
+
* @param {any[]} hits placement hits from Algolia.
|
|
114
|
+
* @param {UserData} user the signed-in user.
|
|
115
|
+
* @return {Promise<any[]>} the hits they may see, in order.
|
|
116
|
+
*/
|
|
117
|
+
export declare const keepPlacementHitsInStudentScope: (hits: any[], user: UserData) => Promise<any[]>;
|
|
75
118
|
/**
|
|
76
119
|
* The oIds whose employer databases this institute may read.
|
|
77
120
|
*
|
|
@@ -16,12 +16,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
16
16
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
17
17
|
};
|
|
18
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
-
exports.nonEmptyGlobalSearchSections = exports.totalGlobalSearchHits = exports.globalSearch = exports.availableGlobalSearchIndexes = exports.GLOBAL_SEARCH_INDEXES = exports.searchLinkedSchools = exports.employerSearchScopes = exports.GLOBAL_SEARCH_INDEX_HITS = exports.GLOBAL_SEARCH_OVERVIEW_HITS = void 0;
|
|
19
|
+
exports.nonEmptyGlobalSearchSections = exports.totalGlobalSearchHits = exports.globalSearch = exports.availableGlobalSearchIndexes = exports.GLOBAL_SEARCH_INDEXES = exports.searchLinkedSchools = exports.employerSearchScopes = exports.keepPlacementHitsInStudentScope = exports.searchPlacementCohorts = exports.searchStudentScope = exports.GLOBAL_SEARCH_INDEX_HITS = exports.GLOBAL_SEARCH_OVERVIEW_HITS = void 0;
|
|
20
20
|
const algoliasearch_1 = __importDefault(require("algoliasearch"));
|
|
21
21
|
const util_1 = require("../../firebase/util");
|
|
22
22
|
const algoliaConfig_1 = require("../../algoliaConfig");
|
|
23
23
|
const productResolver_1 = require("../../productResolver");
|
|
24
24
|
const sectors_1 = require("../../sectors/sectors");
|
|
25
|
+
const firebaseQuery_1 = __importDefault(require("../../firebase/firebaseQuery"));
|
|
25
26
|
/** How many results a section shows before the user drills into one index. */
|
|
26
27
|
exports.GLOBAL_SEARCH_OVERVIEW_HITS = 5;
|
|
27
28
|
/** How many results one index shows per page once the user has drilled in. */
|
|
@@ -35,6 +36,66 @@ const matchesEveryTerm = (text, query) => {
|
|
|
35
36
|
};
|
|
36
37
|
/** Packages that run their own students, placements and cohorts. */
|
|
37
38
|
const isSchoolPackage = (institute) => institute?.package === "institutes-one" || institute?.package === "institutes-two";
|
|
39
|
+
/**
|
|
40
|
+
* The students a staff member may find, by the Students list's rule: admins and
|
|
41
|
+
* "all" see everyone, "some" sees students whose `details[studentFilter]` is one of
|
|
42
|
+
* `studentFilterValues`, anyone else sees no students. Non-staff are left to their
|
|
43
|
+
* own search key, which already limits them to their own records.
|
|
44
|
+
*
|
|
45
|
+
* @param {UserData} user the signed-in user.
|
|
46
|
+
* @return {SearchStudentScope} the students they may find.
|
|
47
|
+
*/
|
|
48
|
+
const searchStudentScope = (user) => {
|
|
49
|
+
if (user.userType !== "Staff" || user.userGroup === "admin" || user.viewStudents === "all")
|
|
50
|
+
return { kind: "all" };
|
|
51
|
+
if (user.viewStudents === "some" && user.studentFilter && user.studentFilterValues?.length) {
|
|
52
|
+
return { kind: "some", field: user.studentFilter, values: user.studentFilterValues };
|
|
53
|
+
}
|
|
54
|
+
return { kind: "none" };
|
|
55
|
+
};
|
|
56
|
+
exports.searchStudentScope = searchStudentScope;
|
|
57
|
+
/**
|
|
58
|
+
* The cohorts whose placements a staff member may find, by the placement page's
|
|
59
|
+
* rule (canViewPlacement in ViewPlacement.tsx).
|
|
60
|
+
*
|
|
61
|
+
* @param {UserData} user the signed-in user.
|
|
62
|
+
* @return {"all"|"none"|string[]} every cohort, none, or these cohort ids.
|
|
63
|
+
*/
|
|
64
|
+
const searchPlacementCohorts = (user) => {
|
|
65
|
+
if (user.userType !== "Staff" || user.userGroup === "admin")
|
|
66
|
+
return "all";
|
|
67
|
+
if (user.viewCohorts === "some")
|
|
68
|
+
return user.visibleCohorts?.length ? user.visibleCohorts : "none";
|
|
69
|
+
if (user.viewCohorts === "all" && user.viewStudents !== "none")
|
|
70
|
+
return "all";
|
|
71
|
+
return "none";
|
|
72
|
+
};
|
|
73
|
+
exports.searchPlacementCohorts = searchPlacementCohorts;
|
|
74
|
+
/**
|
|
75
|
+
* Keeps the placement hits whose student is inside a "some" student scope. The
|
|
76
|
+
* placements index holds the student's uid but not their details, so the filter
|
|
77
|
+
* can't do this; each student is read once. A hit with no student, or whose
|
|
78
|
+
* student can't be read, is dropped rather than shown unchecked.
|
|
79
|
+
*
|
|
80
|
+
* @param {any[]} hits placement hits from Algolia.
|
|
81
|
+
* @param {UserData} user the signed-in user.
|
|
82
|
+
* @return {Promise<any[]>} the hits they may see, in order.
|
|
83
|
+
*/
|
|
84
|
+
const keepPlacementHitsInStudentScope = async (hits, user) => {
|
|
85
|
+
const scope = (0, exports.searchStudentScope)(user);
|
|
86
|
+
if (scope.kind === "all")
|
|
87
|
+
return hits;
|
|
88
|
+
if (scope.kind === "none")
|
|
89
|
+
return [];
|
|
90
|
+
const firebaseQuery = new firebaseQuery_1.default();
|
|
91
|
+
const uids = Array.from(new Set(hits.map((hit) => hit.uid).filter(Boolean)));
|
|
92
|
+
const inScope = Object.fromEntries(await Promise.all(uids.map(async (uid) => {
|
|
93
|
+
const student = await firebaseQuery.getDocData(["users", uid]).catch(() => undefined);
|
|
94
|
+
return [uid, Boolean(student && scope.values.includes(String(student.details?.[scope.field] ?? "")))];
|
|
95
|
+
})));
|
|
96
|
+
return hits.filter((hit) => hit.uid && inScope[hit.uid]);
|
|
97
|
+
};
|
|
98
|
+
exports.keepPlacementHitsInStudentScope = keepPlacementHitsInStudentScope;
|
|
38
99
|
/** Packages that keep an employer database of their own. */
|
|
39
100
|
const hasEmployerDatabase = (institute) => isSchoolPackage(institute) || institute?.package === "careersHub";
|
|
40
101
|
/**
|
|
@@ -108,8 +169,13 @@ exports.GLOBAL_SEARCH_INDEXES = [
|
|
|
108
169
|
label: "Placements",
|
|
109
170
|
labelSingular: "placement",
|
|
110
171
|
indexName: "placements",
|
|
111
|
-
available: (institute) => (0, productResolver_1.resolveProducts)(institute).wex,
|
|
112
|
-
filters: (user) =>
|
|
172
|
+
available: (institute, user) => (0, productResolver_1.resolveProducts)(institute).wex && (!user || (0, exports.searchPlacementCohorts)(user) !== "none"),
|
|
173
|
+
filters: (user) => {
|
|
174
|
+
const cohorts = (0, exports.searchPlacementCohorts)(user);
|
|
175
|
+
const cohortClause = Array.isArray(cohorts) ? ` AND (${cohorts.map((c) => `cohort:${(0, util_1.quoteAlgoliaFilterIfNeeded)(c)}`).join(" OR ")})` : "";
|
|
176
|
+
return `oId:${(0, util_1.quoteAlgoliaFilterIfNeeded)(user.oId)} AND draft:false${cohortClause}`;
|
|
177
|
+
},
|
|
178
|
+
keepHits: exports.keepPlacementHitsInStudentScope,
|
|
113
179
|
toHit: (hit) => ({
|
|
114
180
|
id: hit.objectID,
|
|
115
181
|
title: hit.title || hit.name || "Untitled placement",
|
|
@@ -147,8 +213,15 @@ exports.GLOBAL_SEARCH_INDEXES = [
|
|
|
147
213
|
label: "Students",
|
|
148
214
|
labelSingular: "student",
|
|
149
215
|
indexName: "users",
|
|
150
|
-
available: (institute) => isSchoolPackage(institute),
|
|
151
|
-
|
|
216
|
+
available: (institute, user) => isSchoolPackage(institute) && (!user || (0, exports.searchStudentScope)(user).kind !== "none"),
|
|
217
|
+
// Same clause the Students list's table search builds. The search key itself
|
|
218
|
+
// isn't scoped yet, so this narrows what's shown rather than what's allowed.
|
|
219
|
+
filters: (user) => {
|
|
220
|
+
const scope = (0, exports.searchStudentScope)(user);
|
|
221
|
+
const scopeClause = scope.kind === "some" ?
|
|
222
|
+
` AND (${scope.values.map((v) => `details.${scope.field}:${(0, util_1.quoteAlgoliaFilterIfNeeded)(v)}`).join(" OR ")})` : "";
|
|
223
|
+
return `oId:${(0, util_1.quoteAlgoliaFilterIfNeeded)(user.oId)} AND userType:Students${scopeClause}`;
|
|
224
|
+
},
|
|
152
225
|
toHit: (hit) => ({
|
|
153
226
|
id: hit.objectID,
|
|
154
227
|
title: fullName(hit.details?.forename, hit.details?.surname) || hit.email || "Unnamed student",
|
|
@@ -219,6 +292,15 @@ const globalSearch = async (params) => {
|
|
|
219
292
|
return undefined;
|
|
220
293
|
}) :
|
|
221
294
|
[];
|
|
295
|
+
// Drop hits an index's filter couldn't. The count only knows about this page's
|
|
296
|
+
// drops, so further pages can still hold some the total includes.
|
|
297
|
+
const keptResults = await Promise.all(algoliaResults.map(async (result, i) => {
|
|
298
|
+
const keepHits = algoliaIndexes[i].keepHits;
|
|
299
|
+
if (!result || !keepHits)
|
|
300
|
+
return result;
|
|
301
|
+
const hits = await keepHits(result.hits, user);
|
|
302
|
+
return { ...result, hits, nbHits: Math.max(hits.length, result.nbHits - (result.hits.length - hits.length)) };
|
|
303
|
+
}));
|
|
222
304
|
const section = (index, hits, nbHits, nbPages, at) => ({
|
|
223
305
|
id: index.id,
|
|
224
306
|
label: index.label,
|
|
@@ -234,7 +316,7 @@ const globalSearch = async (params) => {
|
|
|
234
316
|
const start = page * hitsPerPage;
|
|
235
317
|
return [index.id, section(index, matches.slice(start, start + hitsPerPage), matches.length, Math.ceil(matches.length / hitsPerPage), page)];
|
|
236
318
|
}
|
|
237
|
-
const result =
|
|
319
|
+
const result = keptResults[algoliaIndexes.indexOf(index)];
|
|
238
320
|
return [index.id, section(index, (result?.hits ?? []).map(index.toHit), result?.nbHits ?? 0, result?.nbPages ?? 0, result?.page ?? page)];
|
|
239
321
|
}));
|
|
240
322
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"globalSearch.js","sourceRoot":"","sources":["../../../src/features/search/globalSearch.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"globalSearch.js","sourceRoot":"","sources":["../../../src/features/search/globalSearch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* globalSearch.test.ts — the navbar search respects a staff member's student and
|
|
4
|
+
* cohort scope (QA 4.2.4).
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
jest.mock("firebase/firestore", () => ({ where: jest.fn(), Timestamp: { now: jest.fn(), fromDate: jest.fn() } }));
|
|
8
|
+
jest.mock("firebase/storage", () => ({ getDownloadURL: jest.fn(), ref: jest.fn() }));
|
|
9
|
+
jest.mock("../../firebase/firebaseConfig", () => ({ db: {}, storage: {}, auth: {}, functions: {} }));
|
|
10
|
+
const mockGetDocData = jest.fn();
|
|
11
|
+
jest.mock("../../firebase/firebaseQuery", () => ({
|
|
12
|
+
__esModule: true,
|
|
13
|
+
default: jest.fn().mockImplementation(() => ({ getDocData: mockGetDocData })),
|
|
14
|
+
}));
|
|
15
|
+
const mockSearch = jest.fn();
|
|
16
|
+
jest.mock("algoliasearch", () => jest.fn(() => ({ initIndex: jest.fn((name) => ({ search: (q, o) => mockSearch(name, q, o) })) })));
|
|
17
|
+
const globalSearch_1 = require("./globalSearch");
|
|
18
|
+
const school = { package: "institutes-one", workExperience: true };
|
|
19
|
+
const staff = (u = {}) => ({
|
|
20
|
+
oId: "sch1", userType: "Staff", product: "institutes", userGroup: "staffGroup", algoliaKey: "key",
|
|
21
|
+
viewCohorts: "all", viewStudents: "all", ...u,
|
|
22
|
+
});
|
|
23
|
+
const scoped = staff({ viewStudents: "some", studentFilter: "year", studentFilterValues: ["10", "11"] });
|
|
24
|
+
const filtersFor = (id, user) => {
|
|
25
|
+
const index = globalSearch_1.GLOBAL_SEARCH_INDEXES.find((i) => i.id === id);
|
|
26
|
+
return index.filters(user);
|
|
27
|
+
};
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
mockGetDocData.mockReset();
|
|
30
|
+
mockSearch.mockReset();
|
|
31
|
+
});
|
|
32
|
+
describe("searchStudentScope", () => {
|
|
33
|
+
it("admins, staff who see all students, and non-staff are unscoped", () => {
|
|
34
|
+
expect((0, globalSearch_1.searchStudentScope)(staff({ userGroup: "admin", viewStudents: "none" })).kind).toBe("all");
|
|
35
|
+
expect((0, globalSearch_1.searchStudentScope)(staff()).kind).toBe("all");
|
|
36
|
+
expect((0, globalSearch_1.searchStudentScope)({ ...scoped, userType: "Students" }).kind).toBe("all");
|
|
37
|
+
});
|
|
38
|
+
it("'some' with a field and values is scoped to them", () => {
|
|
39
|
+
expect((0, globalSearch_1.searchStudentScope)(scoped)).toEqual({ kind: "some", field: "year", values: ["10", "11"] });
|
|
40
|
+
});
|
|
41
|
+
it("'none', unset, or 'some' with no values sees no students", () => {
|
|
42
|
+
expect((0, globalSearch_1.searchStudentScope)(staff({ viewStudents: "none" })).kind).toBe("none");
|
|
43
|
+
expect((0, globalSearch_1.searchStudentScope)(staff({ viewStudents: undefined })).kind).toBe("none");
|
|
44
|
+
expect((0, globalSearch_1.searchStudentScope)(staff({ viewStudents: "some", studentFilter: "year", studentFilterValues: [] })).kind).toBe("none");
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
describe("searchPlacementCohorts", () => {
|
|
48
|
+
it("matches the placement page's cohort rule", () => {
|
|
49
|
+
expect((0, globalSearch_1.searchPlacementCohorts)(staff({ userGroup: "admin", viewCohorts: undefined }))).toBe("all");
|
|
50
|
+
expect((0, globalSearch_1.searchPlacementCohorts)(staff())).toBe("all");
|
|
51
|
+
expect((0, globalSearch_1.searchPlacementCohorts)(staff({ viewCohorts: "some", visibleCohorts: ["c1", "c2"] }))).toEqual(["c1", "c2"]);
|
|
52
|
+
expect((0, globalSearch_1.searchPlacementCohorts)(staff({ viewCohorts: "some", visibleCohorts: [] }))).toBe("none");
|
|
53
|
+
expect((0, globalSearch_1.searchPlacementCohorts)(staff({ viewStudents: "none" }))).toBe("none");
|
|
54
|
+
expect((0, globalSearch_1.searchPlacementCohorts)(staff({ viewCohorts: undefined }))).toBe("none");
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
describe("filters sent to Algolia", () => {
|
|
58
|
+
it("students: unchanged for unscoped staff, narrowed for scoped staff", () => {
|
|
59
|
+
expect(filtersFor("students", staff())).toBe("oId:sch1 AND userType:Students");
|
|
60
|
+
expect(filtersFor("students", scoped)).toBe("oId:sch1 AND userType:Students AND (details.year:10 OR details.year:11)");
|
|
61
|
+
});
|
|
62
|
+
it("students: values with spaces are quoted", () => {
|
|
63
|
+
const formScoped = staff({ viewStudents: "some", studentFilter: "form", studentFilterValues: ["10 A"] });
|
|
64
|
+
expect(filtersFor("students", formScoped)).toBe("oId:sch1 AND userType:Students AND (details.form:'10 A')");
|
|
65
|
+
});
|
|
66
|
+
it("placements: unchanged for all cohorts, narrowed to visible cohorts", () => {
|
|
67
|
+
expect(filtersFor("placements", staff())).toBe("oId:sch1 AND draft:false");
|
|
68
|
+
expect(filtersFor("placements", staff({ viewCohorts: "some", visibleCohorts: ["c1", "c2"] })))
|
|
69
|
+
.toBe("oId:sch1 AND draft:false AND (cohort:c1 OR cohort:c2)");
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
describe("availableGlobalSearchIndexes", () => {
|
|
73
|
+
const ids = (user) => (0, globalSearch_1.availableGlobalSearchIndexes)(school, user).map((i) => i.id);
|
|
74
|
+
it("hides Students from staff who can't view students, and Placements from staff who can't view cohorts", () => {
|
|
75
|
+
expect(ids(staff())).toEqual(expect.arrayContaining(["students", "placements"]));
|
|
76
|
+
expect(ids(staff({ viewStudents: "none" }))).not.toContain("students");
|
|
77
|
+
expect(ids(staff({ viewStudents: "none" }))).not.toContain("placements");
|
|
78
|
+
expect(ids(staff({ viewCohorts: "some", visibleCohorts: [] }))).not.toContain("placements");
|
|
79
|
+
expect(ids(scoped)).toEqual(expect.arrayContaining(["students", "placements"]));
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
describe("keepPlacementHitsInStudentScope", () => {
|
|
83
|
+
const students = {
|
|
84
|
+
s10: { details: { year: "10" } },
|
|
85
|
+
s9: { details: { year: "9" } },
|
|
86
|
+
};
|
|
87
|
+
beforeEach(() => mockGetDocData.mockImplementation(async ([, uid]) => {
|
|
88
|
+
if (!students[uid])
|
|
89
|
+
throw new Error("not found");
|
|
90
|
+
return students[uid];
|
|
91
|
+
}));
|
|
92
|
+
it("keeps only placements of in-scope students, in order, reading each student once", async () => {
|
|
93
|
+
const hits = [{ objectID: "p1", uid: "s10" }, { objectID: "p2", uid: "s9" }, { objectID: "p3", uid: "s10" }, { objectID: "p4" }, { objectID: "p5", uid: "gone" }];
|
|
94
|
+
const kept = await (0, globalSearch_1.keepPlacementHitsInStudentScope)(hits, scoped);
|
|
95
|
+
expect(kept.map((h) => h.objectID)).toEqual(["p1", "p3"]);
|
|
96
|
+
expect(mockGetDocData).toHaveBeenCalledTimes(3); // s10, s9, gone
|
|
97
|
+
});
|
|
98
|
+
it("leaves hits alone for unscoped staff without reading anything", async () => {
|
|
99
|
+
const hits = [{ objectID: "p1", uid: "s9" }];
|
|
100
|
+
expect(await (0, globalSearch_1.keepPlacementHitsInStudentScope)(hits, staff())).toEqual(hits);
|
|
101
|
+
expect(mockGetDocData).not.toHaveBeenCalled();
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
describe("globalSearch", () => {
|
|
105
|
+
it("sends the scoped filters and drops out-of-scope placements from the results and count", async () => {
|
|
106
|
+
mockGetDocData.mockImplementation(async ([, uid]) => ({ details: { year: uid === "s10" ? "10" : "9" } }));
|
|
107
|
+
mockSearch.mockImplementation(async (index) => index === "placements" ?
|
|
108
|
+
{ hits: [{ objectID: "p1", uid: "s10", name: "A" }, { objectID: "p2", uid: "s9", name: "B" }], nbHits: 2, page: 0, nbPages: 1 } :
|
|
109
|
+
{ hits: [{ objectID: "u1", details: { forename: "In", surname: "Scope", year: "10" } }], nbHits: 1, page: 0, nbPages: 1 });
|
|
110
|
+
const results = await (0, globalSearch_1.globalSearch)({ user: scoped, institute: school, query: "finley", indexIds: ["students", "placements"] });
|
|
111
|
+
const sent = Object.fromEntries(mockSearch.mock.calls.map(([index, , options]) => [index, options.filters]));
|
|
112
|
+
expect(sent.users).toBe("oId:sch1 AND userType:Students AND (details.year:10 OR details.year:11)");
|
|
113
|
+
expect(sent.placements).toBe("oId:sch1 AND draft:false");
|
|
114
|
+
expect(results.placements?.hits.map((h) => h.id)).toEqual(["p1"]);
|
|
115
|
+
expect(results.placements?.nbHits).toBe(1);
|
|
116
|
+
expect(results.students?.nbHits).toBe(1);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
//# sourceMappingURL=globalSearch.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"globalSearch.test.js","sourceRoot":"","sources":["../../../src/features/search/globalSearch.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* globalSearch.test.ts — the navbar search respects a staff member's student and
|
|
3
|
+
* cohort scope (QA 4.2.4).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
jest.mock("firebase/firestore", () => ({where: jest.fn(), Timestamp: {now: jest.fn(), fromDate: jest.fn()}}));
|
|
7
|
+
jest.mock("firebase/storage", () => ({getDownloadURL: jest.fn(), ref: jest.fn()}));
|
|
8
|
+
jest.mock("../../firebase/firebaseConfig", () => ({db: {}, storage: {}, auth: {}, functions: {}}));
|
|
9
|
+
|
|
10
|
+
const mockGetDocData = jest.fn();
|
|
11
|
+
jest.mock("../../firebase/firebaseQuery", () => ({
|
|
12
|
+
__esModule: true,
|
|
13
|
+
default: jest.fn().mockImplementation(() => ({getDocData: mockGetDocData})),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
const mockSearch = jest.fn();
|
|
17
|
+
jest.mock("algoliasearch", () => jest.fn(() => ({initIndex: jest.fn((name: string) => ({search: (q: string, o: unknown) => mockSearch(name, q, o)}))})));
|
|
18
|
+
|
|
19
|
+
import {GLOBAL_SEARCH_INDEXES, availableGlobalSearchIndexes, globalSearch, keepPlacementHitsInStudentScope, searchPlacementCohorts, searchStudentScope} from "./globalSearch";
|
|
20
|
+
import {InstituteData, UserData} from "../../typeDefinitions";
|
|
21
|
+
|
|
22
|
+
const school = {package: "institutes-one", workExperience: true} as unknown as InstituteData;
|
|
23
|
+
const staff = (u: Partial<UserData> = {}) => ({
|
|
24
|
+
oId: "sch1", userType: "Staff", product: "institutes", userGroup: "staffGroup", algoliaKey: "key",
|
|
25
|
+
viewCohorts: "all", viewStudents: "all", ...u,
|
|
26
|
+
}) as UserData;
|
|
27
|
+
const scoped = staff({viewStudents: "some", studentFilter: "year", studentFilterValues: ["10", "11"]});
|
|
28
|
+
const filtersFor = (id: string, user: UserData) => {
|
|
29
|
+
const index = GLOBAL_SEARCH_INDEXES.find((i) => i.id === id) as {filters: (u: UserData) => string};
|
|
30
|
+
return index.filters(user);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
mockGetDocData.mockReset();
|
|
35
|
+
mockSearch.mockReset();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("searchStudentScope", () => {
|
|
39
|
+
it("admins, staff who see all students, and non-staff are unscoped", () => {
|
|
40
|
+
expect(searchStudentScope(staff({userGroup: "admin", viewStudents: "none"})).kind).toBe("all");
|
|
41
|
+
expect(searchStudentScope(staff()).kind).toBe("all");
|
|
42
|
+
expect(searchStudentScope({...scoped, userType: "Students"} as UserData).kind).toBe("all");
|
|
43
|
+
});
|
|
44
|
+
it("'some' with a field and values is scoped to them", () => {
|
|
45
|
+
expect(searchStudentScope(scoped)).toEqual({kind: "some", field: "year", values: ["10", "11"]});
|
|
46
|
+
});
|
|
47
|
+
it("'none', unset, or 'some' with no values sees no students", () => {
|
|
48
|
+
expect(searchStudentScope(staff({viewStudents: "none"})).kind).toBe("none");
|
|
49
|
+
expect(searchStudentScope(staff({viewStudents: undefined})).kind).toBe("none");
|
|
50
|
+
expect(searchStudentScope(staff({viewStudents: "some", studentFilter: "year", studentFilterValues: []})).kind).toBe("none");
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("searchPlacementCohorts", () => {
|
|
55
|
+
it("matches the placement page's cohort rule", () => {
|
|
56
|
+
expect(searchPlacementCohorts(staff({userGroup: "admin", viewCohorts: undefined}))).toBe("all");
|
|
57
|
+
expect(searchPlacementCohorts(staff())).toBe("all");
|
|
58
|
+
expect(searchPlacementCohorts(staff({viewCohorts: "some", visibleCohorts: ["c1", "c2"]}))).toEqual(["c1", "c2"]);
|
|
59
|
+
expect(searchPlacementCohorts(staff({viewCohorts: "some", visibleCohorts: []}))).toBe("none");
|
|
60
|
+
expect(searchPlacementCohorts(staff({viewStudents: "none"}))).toBe("none");
|
|
61
|
+
expect(searchPlacementCohorts(staff({viewCohorts: undefined}))).toBe("none");
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("filters sent to Algolia", () => {
|
|
66
|
+
it("students: unchanged for unscoped staff, narrowed for scoped staff", () => {
|
|
67
|
+
expect(filtersFor("students", staff())).toBe("oId:sch1 AND userType:Students");
|
|
68
|
+
expect(filtersFor("students", scoped)).toBe("oId:sch1 AND userType:Students AND (details.year:10 OR details.year:11)");
|
|
69
|
+
});
|
|
70
|
+
it("students: values with spaces are quoted", () => {
|
|
71
|
+
const formScoped = staff({viewStudents: "some", studentFilter: "form", studentFilterValues: ["10 A"]});
|
|
72
|
+
expect(filtersFor("students", formScoped)).toBe("oId:sch1 AND userType:Students AND (details.form:'10 A')");
|
|
73
|
+
});
|
|
74
|
+
it("placements: unchanged for all cohorts, narrowed to visible cohorts", () => {
|
|
75
|
+
expect(filtersFor("placements", staff())).toBe("oId:sch1 AND draft:false");
|
|
76
|
+
expect(filtersFor("placements", staff({viewCohorts: "some", visibleCohorts: ["c1", "c2"]})))
|
|
77
|
+
.toBe("oId:sch1 AND draft:false AND (cohort:c1 OR cohort:c2)");
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("availableGlobalSearchIndexes", () => {
|
|
82
|
+
const ids = (user: UserData) => availableGlobalSearchIndexes(school, user).map((i) => i.id);
|
|
83
|
+
it("hides Students from staff who can't view students, and Placements from staff who can't view cohorts", () => {
|
|
84
|
+
expect(ids(staff())).toEqual(expect.arrayContaining(["students", "placements"]));
|
|
85
|
+
expect(ids(staff({viewStudents: "none"}))).not.toContain("students");
|
|
86
|
+
expect(ids(staff({viewStudents: "none"}))).not.toContain("placements");
|
|
87
|
+
expect(ids(staff({viewCohorts: "some", visibleCohorts: []}))).not.toContain("placements");
|
|
88
|
+
expect(ids(scoped)).toEqual(expect.arrayContaining(["students", "placements"]));
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe("keepPlacementHitsInStudentScope", () => {
|
|
93
|
+
const students: {[uid: string]: UserData} = {
|
|
94
|
+
s10: {details: {year: "10"}} as unknown as UserData,
|
|
95
|
+
s9: {details: {year: "9"}} as unknown as UserData,
|
|
96
|
+
};
|
|
97
|
+
beforeEach(() => mockGetDocData.mockImplementation(async ([, uid]: string[]) => {
|
|
98
|
+
if (!students[uid]) throw new Error("not found");
|
|
99
|
+
return students[uid];
|
|
100
|
+
}));
|
|
101
|
+
|
|
102
|
+
it("keeps only placements of in-scope students, in order, reading each student once", async () => {
|
|
103
|
+
const hits = [{objectID: "p1", uid: "s10"}, {objectID: "p2", uid: "s9"}, {objectID: "p3", uid: "s10"}, {objectID: "p4"}, {objectID: "p5", uid: "gone"}];
|
|
104
|
+
const kept = await keepPlacementHitsInStudentScope(hits, scoped);
|
|
105
|
+
expect(kept.map((h) => h.objectID)).toEqual(["p1", "p3"]);
|
|
106
|
+
expect(mockGetDocData).toHaveBeenCalledTimes(3); // s10, s9, gone
|
|
107
|
+
});
|
|
108
|
+
it("leaves hits alone for unscoped staff without reading anything", async () => {
|
|
109
|
+
const hits = [{objectID: "p1", uid: "s9"}];
|
|
110
|
+
expect(await keepPlacementHitsInStudentScope(hits, staff())).toEqual(hits);
|
|
111
|
+
expect(mockGetDocData).not.toHaveBeenCalled();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe("globalSearch", () => {
|
|
116
|
+
it("sends the scoped filters and drops out-of-scope placements from the results and count", async () => {
|
|
117
|
+
mockGetDocData.mockImplementation(async ([, uid]: string[]) => ({details: {year: uid === "s10" ? "10" : "9"}}));
|
|
118
|
+
mockSearch.mockImplementation(async (index: string) => index === "placements" ?
|
|
119
|
+
{hits: [{objectID: "p1", uid: "s10", name: "A"}, {objectID: "p2", uid: "s9", name: "B"}], nbHits: 2, page: 0, nbPages: 1} :
|
|
120
|
+
{hits: [{objectID: "u1", details: {forename: "In", surname: "Scope", year: "10"}}], nbHits: 1, page: 0, nbPages: 1});
|
|
121
|
+
|
|
122
|
+
const results = await globalSearch({user: scoped, institute: school, query: "finley", indexIds: ["students", "placements"]});
|
|
123
|
+
|
|
124
|
+
const sent = Object.fromEntries(mockSearch.mock.calls.map(([index, , options]) => [index, (options as {filters: string}).filters]));
|
|
125
|
+
expect(sent.users).toBe("oId:sch1 AND userType:Students AND (details.year:10 OR details.year:11)");
|
|
126
|
+
expect(sent.placements).toBe("oId:sch1 AND draft:false");
|
|
127
|
+
expect(results.placements?.hits.map((h) => h.id)).toEqual(["p1"]);
|
|
128
|
+
expect(results.placements?.nbHits).toBe(1);
|
|
129
|
+
expect(results.students?.nbHits).toBe(1);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -18,6 +18,7 @@ import {capitaliseWords, quoteAlgoliaFilterIfNeeded} from "../../firebase/util";
|
|
|
18
18
|
import {ALGOLIA_APP_ID} from "../../algoliaConfig";
|
|
19
19
|
import {resolveProducts} from "../../productResolver";
|
|
20
20
|
import {formatSectors} from "../../sectors/sectors";
|
|
21
|
+
import FirebaseQuery from "../../firebase/firebaseQuery";
|
|
21
22
|
|
|
22
23
|
/** How many results a section shows before the user drills into one index. */
|
|
23
24
|
export const GLOBAL_SEARCH_OVERVIEW_HITS = 5;
|
|
@@ -74,6 +75,11 @@ export type GlobalSearchIndexDefinition = {
|
|
|
74
75
|
filters: (user: UserData, institute?: InstituteData) => string,
|
|
75
76
|
/** Turns one raw hit into a row. */
|
|
76
77
|
toHit: (hit: any) => GlobalSearchHit,
|
|
78
|
+
/**
|
|
79
|
+
* Drops hits the filter couldn't, for records that don't carry what the user's
|
|
80
|
+
* access depends on (a placement holds its student's uid, not their details).
|
|
81
|
+
*/
|
|
82
|
+
keepHits?: (hits: any[], user: UserData) => Promise<any[]>,
|
|
77
83
|
} | {
|
|
78
84
|
/**
|
|
79
85
|
* Searched in memory, from records the caller already holds, instead of
|
|
@@ -98,6 +104,62 @@ const matchesEveryTerm = (text: string, query: string) => {
|
|
|
98
104
|
const isSchoolPackage = (institute?: InstituteData) =>
|
|
99
105
|
institute?.package === "institutes-one" || institute?.package === "institutes-two";
|
|
100
106
|
|
|
107
|
+
export type SearchStudentScope = {kind: "all"} | {kind: "none"} | {kind: "some", field: string, values: string[]};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The students a staff member may find, by the Students list's rule: admins and
|
|
111
|
+
* "all" see everyone, "some" sees students whose `details[studentFilter]` is one of
|
|
112
|
+
* `studentFilterValues`, anyone else sees no students. Non-staff are left to their
|
|
113
|
+
* own search key, which already limits them to their own records.
|
|
114
|
+
*
|
|
115
|
+
* @param {UserData} user the signed-in user.
|
|
116
|
+
* @return {SearchStudentScope} the students they may find.
|
|
117
|
+
*/
|
|
118
|
+
export const searchStudentScope = (user: UserData): SearchStudentScope => {
|
|
119
|
+
if (user.userType !== "Staff" || user.userGroup === "admin" || user.viewStudents === "all") return {kind: "all"};
|
|
120
|
+
if (user.viewStudents === "some" && user.studentFilter && user.studentFilterValues?.length) {
|
|
121
|
+
return {kind: "some", field: user.studentFilter, values: user.studentFilterValues};
|
|
122
|
+
}
|
|
123
|
+
return {kind: "none"};
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The cohorts whose placements a staff member may find, by the placement page's
|
|
128
|
+
* rule (canViewPlacement in ViewPlacement.tsx).
|
|
129
|
+
*
|
|
130
|
+
* @param {UserData} user the signed-in user.
|
|
131
|
+
* @return {"all"|"none"|string[]} every cohort, none, or these cohort ids.
|
|
132
|
+
*/
|
|
133
|
+
export const searchPlacementCohorts = (user: UserData): "all"|"none"|string[] => {
|
|
134
|
+
if (user.userType !== "Staff" || user.userGroup === "admin") return "all";
|
|
135
|
+
if (user.viewCohorts === "some") return user.visibleCohorts?.length ? user.visibleCohorts : "none";
|
|
136
|
+
if (user.viewCohorts === "all" && user.viewStudents !== "none") return "all";
|
|
137
|
+
return "none";
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Keeps the placement hits whose student is inside a "some" student scope. The
|
|
142
|
+
* placements index holds the student's uid but not their details, so the filter
|
|
143
|
+
* can't do this; each student is read once. A hit with no student, or whose
|
|
144
|
+
* student can't be read, is dropped rather than shown unchecked.
|
|
145
|
+
*
|
|
146
|
+
* @param {any[]} hits placement hits from Algolia.
|
|
147
|
+
* @param {UserData} user the signed-in user.
|
|
148
|
+
* @return {Promise<any[]>} the hits they may see, in order.
|
|
149
|
+
*/
|
|
150
|
+
export const keepPlacementHitsInStudentScope = async (hits: any[], user: UserData): Promise<any[]> => {
|
|
151
|
+
const scope = searchStudentScope(user);
|
|
152
|
+
if (scope.kind === "all") return hits;
|
|
153
|
+
if (scope.kind === "none") return [];
|
|
154
|
+
const firebaseQuery = new FirebaseQuery();
|
|
155
|
+
const uids = Array.from(new Set(hits.map((hit) => hit.uid).filter(Boolean))) as string[];
|
|
156
|
+
const inScope = Object.fromEntries(await Promise.all(uids.map(async (uid) => {
|
|
157
|
+
const student = await firebaseQuery.getDocData(["users", uid]).catch(() => undefined) as UserData|undefined;
|
|
158
|
+
return [uid, Boolean(student && scope.values.includes(String(student.details?.[scope.field] ?? "")))];
|
|
159
|
+
})));
|
|
160
|
+
return hits.filter((hit) => hit.uid && inScope[hit.uid]);
|
|
161
|
+
};
|
|
162
|
+
|
|
101
163
|
/** Packages that keep an employer database of their own. */
|
|
102
164
|
const hasEmployerDatabase = (institute?: InstituteData) =>
|
|
103
165
|
isSchoolPackage(institute) || institute?.package === "careersHub";
|
|
@@ -180,8 +242,13 @@ export const GLOBAL_SEARCH_INDEXES: GlobalSearchIndexDefinition[] = [
|
|
|
180
242
|
label: "Placements",
|
|
181
243
|
labelSingular: "placement",
|
|
182
244
|
indexName: "placements",
|
|
183
|
-
available: (institute) => resolveProducts(institute).wex,
|
|
184
|
-
filters: (user) =>
|
|
245
|
+
available: (institute, user) => resolveProducts(institute).wex && (!user || searchPlacementCohorts(user) !== "none"),
|
|
246
|
+
filters: (user) => {
|
|
247
|
+
const cohorts = searchPlacementCohorts(user);
|
|
248
|
+
const cohortClause = Array.isArray(cohorts) ? ` AND (${cohorts.map((c) => `cohort:${quoteAlgoliaFilterIfNeeded(c)}`).join(" OR ")})` : "";
|
|
249
|
+
return `oId:${quoteAlgoliaFilterIfNeeded(user.oId)} AND draft:false${cohortClause}`;
|
|
250
|
+
},
|
|
251
|
+
keepHits: keepPlacementHitsInStudentScope,
|
|
185
252
|
toHit: (hit) => ({
|
|
186
253
|
id: hit.objectID,
|
|
187
254
|
title: hit.title || hit.name || "Untitled placement",
|
|
@@ -226,8 +293,15 @@ export const GLOBAL_SEARCH_INDEXES: GlobalSearchIndexDefinition[] = [
|
|
|
226
293
|
label: "Students",
|
|
227
294
|
labelSingular: "student",
|
|
228
295
|
indexName: "users",
|
|
229
|
-
available: (institute) => isSchoolPackage(institute),
|
|
230
|
-
|
|
296
|
+
available: (institute, user) => isSchoolPackage(institute) && (!user || searchStudentScope(user).kind !== "none"),
|
|
297
|
+
// Same clause the Students list's table search builds. The search key itself
|
|
298
|
+
// isn't scoped yet, so this narrows what's shown rather than what's allowed.
|
|
299
|
+
filters: (user) => {
|
|
300
|
+
const scope = searchStudentScope(user);
|
|
301
|
+
const scopeClause = scope.kind === "some" ?
|
|
302
|
+
` AND (${scope.values.map((v) => `details.${scope.field}:${quoteAlgoliaFilterIfNeeded(v)}`).join(" OR ")})` : "";
|
|
303
|
+
return `oId:${quoteAlgoliaFilterIfNeeded(user.oId)} AND userType:Students${scopeClause}`;
|
|
304
|
+
},
|
|
231
305
|
toHit: (hit) => ({
|
|
232
306
|
id: hit.objectID,
|
|
233
307
|
title: fullName(hit.details?.forename, hit.details?.surname) || hit.email || "Unnamed student",
|
|
@@ -322,6 +396,15 @@ export const globalSearch = async (params: GlobalSearchParams): Promise<GlobalSe
|
|
|
322
396
|
}) :
|
|
323
397
|
[];
|
|
324
398
|
|
|
399
|
+
// Drop hits an index's filter couldn't. The count only knows about this page's
|
|
400
|
+
// drops, so further pages can still hold some the total includes.
|
|
401
|
+
const keptResults = await Promise.all(algoliaResults.map(async (result, i) => {
|
|
402
|
+
const keepHits = algoliaIndexes[i].keepHits;
|
|
403
|
+
if (!result || !keepHits) return result;
|
|
404
|
+
const hits = await keepHits(result.hits, user);
|
|
405
|
+
return {...result, hits, nbHits: Math.max(hits.length, result.nbHits - (result.hits.length - hits.length))};
|
|
406
|
+
}));
|
|
407
|
+
|
|
325
408
|
const section = (index: GlobalSearchIndexDefinition, hits: GlobalSearchHit[], nbHits: number, nbPages: number, at: number): GlobalSearchIndexResults => ({
|
|
326
409
|
id: index.id,
|
|
327
410
|
label: index.label,
|
|
@@ -338,7 +421,7 @@ export const globalSearch = async (params: GlobalSearchParams): Promise<GlobalSe
|
|
|
338
421
|
const start = page * hitsPerPage;
|
|
339
422
|
return [index.id, section(index, matches.slice(start, start + hitsPerPage), matches.length, Math.ceil(matches.length / hitsPerPage), page)];
|
|
340
423
|
}
|
|
341
|
-
const result =
|
|
424
|
+
const result = keptResults[algoliaIndexes.indexOf(index)] as {hits?: any[], nbHits?: number, page?: number, nbPages?: number}|undefined;
|
|
342
425
|
return [index.id, section(index, (result?.hits ?? []).map(index.toHit), result?.nbHits ?? 0, result?.nbPages ?? 0, result?.page ?? page)];
|
|
343
426
|
})) as GlobalSearchResults;
|
|
344
427
|
};
|