placementt-core 1.400.1097 → 1.400.1098

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.
@@ -1,20 +1,22 @@
1
1
  /**
2
- * globalSearch — one query box, several Algolia indexes.
2
+ * globalSearch — one query box, several indexes.
3
3
  *
4
4
  * The navbar search sends whatever the user typed here. Every index that the
5
5
  * signed-in institute can see is queried in a single round trip, and the answer
6
6
  * comes back keyed by index so the UI can render a section per index.
7
7
  *
8
- * Adding an index is meant to be a single entry in GLOBAL_SEARCH_INDEXES: name
9
- * the Algolia index, say when an institute can see it, restrict it to that
10
- * institute's records, and describe how one hit becomes a row.
8
+ * Adding an index is meant to be a single entry in GLOBAL_SEARCH_INDEXES. Most
9
+ * are Algolia-backed: name the index, say when an institute can see it, restrict
10
+ * it to that institute's records, and describe how one hit becomes a row. A
11
+ * small collection the caller already holds can instead be matched in memory
12
+ * with `local`, as a hub's schools are.
11
13
  */
12
14
  import { InstituteData, UserData } from "../../typeDefinitions";
13
15
  /** How many results a section shows before the user drills into one index. */
14
16
  export declare const GLOBAL_SEARCH_OVERVIEW_HITS = 5;
15
17
  /** How many results one index shows per page once the user has drilled in. */
16
18
  export declare const GLOBAL_SEARCH_INDEX_HITS = 10;
17
- export type GlobalSearchIndexId = "placements" | "employers" | "students";
19
+ export type GlobalSearchIndexId = "placements" | "employers" | "students" | "schools";
18
20
  /** One row, flattened out of whatever the underlying index stores. */
19
21
  export type GlobalSearchHit = {
20
22
  /** The Algolia objectID, which is the Firestore document id. */
@@ -54,15 +56,22 @@ export type GlobalSearchIndexDefinition = {
54
56
  id: GlobalSearchIndexId;
55
57
  label: string;
56
58
  labelSingular: string;
57
- /** The Algolia index name — the unsorted, relevance-ordered replica. */
58
- indexName: string;
59
59
  /** Whether this institute has the product that owns these records. */
60
60
  available: (institute?: InstituteData, user?: UserData) => boolean;
61
+ } & ({
62
+ /** The Algolia index name — the unsorted, relevance-ordered replica. */
63
+ indexName: string;
61
64
  /** Restricts the index to records this user is allowed to see. */
62
65
  filters: (user: UserData, institute?: InstituteData) => string;
63
66
  /** Turns one raw hit into a row. */
64
67
  toHit: (hit: any) => GlobalSearchHit;
65
- };
68
+ } | {
69
+ /**
70
+ * Searched in memory, from records the caller already holds, instead of
71
+ * through Algolia. Returns every match, ranked; `globalSearch` pages it.
72
+ */
73
+ local: (params: GlobalSearchParams) => GlobalSearchHit[];
74
+ });
66
75
  /**
67
76
  * The oIds whose employer databases this institute may read.
68
77
  *
@@ -79,6 +88,21 @@ export declare const employerSearchScopes: (user: UserData, institute?: Institut
79
88
  oId: string;
80
89
  linked: boolean;
81
90
  }[];
91
+ /**
92
+ * A careers hub's linked schools, matched in memory.
93
+ *
94
+ * There is no Algolia index for institutes, and a hub links to tens of schools
95
+ * rather than thousands, so the records Institutes.tsx already holds on
96
+ * OrganisationContext (`trustOrHubSchools`, scoped to what this user may see)
97
+ * are searched directly. Matches on the school's name and email domain, and on
98
+ * its admin's name and email where the caller attached the admin.
99
+ *
100
+ * @param {UserData} user the signed-in hub user.
101
+ * @param {GlobalSearchParams["schools"]} schools the hub's linked schools, keyed by oId.
102
+ * @param {string} query whatever the user typed.
103
+ * @return {GlobalSearchHit[]} every matching school, alphabetically.
104
+ */
105
+ export declare const searchLinkedSchools: (user: UserData, schools: GlobalSearchParams["schools"], query: string) => GlobalSearchHit[];
82
106
  /**
83
107
  * The indexes the navbar search covers.
84
108
  *
@@ -97,6 +121,15 @@ export declare const availableGlobalSearchIndexes: (institute?: InstituteData, u
97
121
  export type GlobalSearchParams = {
98
122
  user: UserData;
99
123
  institute?: InstituteData;
124
+ /**
125
+ * A hub's linked schools, as Institutes.tsx holds them on OrganisationContext.
126
+ * Searched in memory by the "schools" index; nothing else reads it.
127
+ */
128
+ schools?: {
129
+ [oId: string]: InstituteData & {
130
+ adminUser?: UserData;
131
+ };
132
+ };
100
133
  /** Whatever the user typed. Blank returns nothing rather than everything. */
101
134
  query: string;
102
135
  /** Restrict to specific indexes — how the drill-down asks for one index. */
@@ -108,6 +141,9 @@ export type GlobalSearchParams = {
108
141
  /**
109
142
  * Search every index this institute can see, in one round trip.
110
143
  *
144
+ * Algolia-backed indexes go out together in one multipleQueries batch; local
145
+ * ones are matched from the records passed in and paged here to the same shape.
146
+ *
111
147
  * Indexes with no matches are still returned, with `nbHits: 0` and no hits, so
112
148
  * a drill-down can say "no results" rather than render nothing. The overview UI
113
149
  * is expected to skip empty sections itself.
@@ -115,7 +151,7 @@ export type GlobalSearchParams = {
115
151
  * @param {GlobalSearchParams} params the search.
116
152
  * @return {Promise<GlobalSearchResults>} results keyed by index id.
117
153
  */
118
- export declare const globalSearch: ({ user, institute, query, indexIds, hitsPerPage, page, }: GlobalSearchParams) => Promise<GlobalSearchResults>;
154
+ export declare const globalSearch: (params: GlobalSearchParams) => Promise<GlobalSearchResults>;
119
155
  /**
120
156
  * The total across every section — what the header count is drawn from.
121
157
  *
@@ -1,20 +1,22 @@
1
1
  "use strict";
2
2
  /**
3
- * globalSearch — one query box, several Algolia indexes.
3
+ * globalSearch — one query box, several indexes.
4
4
  *
5
5
  * The navbar search sends whatever the user typed here. Every index that the
6
6
  * signed-in institute can see is queried in a single round trip, and the answer
7
7
  * comes back keyed by index so the UI can render a section per index.
8
8
  *
9
- * Adding an index is meant to be a single entry in GLOBAL_SEARCH_INDEXES: name
10
- * the Algolia index, say when an institute can see it, restrict it to that
11
- * institute's records, and describe how one hit becomes a row.
9
+ * Adding an index is meant to be a single entry in GLOBAL_SEARCH_INDEXES. Most
10
+ * are Algolia-backed: name the index, say when an institute can see it, restrict
11
+ * it to that institute's records, and describe how one hit becomes a row. A
12
+ * small collection the caller already holds can instead be matched in memory
13
+ * with `local`, as a hub's schools are.
12
14
  */
13
15
  var __importDefault = (this && this.__importDefault) || function (mod) {
14
16
  return (mod && mod.__esModule) ? mod : { "default": mod };
15
17
  };
16
18
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.nonEmptyGlobalSearchSections = exports.totalGlobalSearchHits = exports.globalSearch = exports.availableGlobalSearchIndexes = exports.GLOBAL_SEARCH_INDEXES = 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.GLOBAL_SEARCH_INDEX_HITS = exports.GLOBAL_SEARCH_OVERVIEW_HITS = void 0;
18
20
  const algoliasearch_1 = __importDefault(require("algoliasearch"));
19
21
  const util_1 = require("../../firebase/util");
20
22
  const algoliaConfig_1 = require("../../algoliaConfig");
@@ -25,6 +27,11 @@ exports.GLOBAL_SEARCH_OVERVIEW_HITS = 5;
25
27
  exports.GLOBAL_SEARCH_INDEX_HITS = 10;
26
28
  const fullName = (forename, surname) => [forename, surname].filter(Boolean).join(" ").trim();
27
29
  const joinParts = (...parts) => parts.filter(Boolean).join(" · ") || undefined;
30
+ /** Every whitespace-separated term of the query appears somewhere in the text. */
31
+ const matchesEveryTerm = (text, query) => {
32
+ const haystack = text.toLowerCase();
33
+ return query.toLowerCase().split(/\s+/).filter(Boolean).every((term) => haystack.includes(term));
34
+ };
28
35
  /** Packages that run their own students, placements and cohorts. */
29
36
  const isSchoolPackage = (institute) => institute?.package === "institutes-one" || institute?.package === "institutes-two";
30
37
  /** Packages that keep an employer database of their own. */
@@ -54,6 +61,40 @@ const employerSearchScopes = (user, institute) => {
54
61
  ];
55
62
  };
56
63
  exports.employerSearchScopes = employerSearchScopes;
64
+ /**
65
+ * A careers hub's linked schools, matched in memory.
66
+ *
67
+ * There is no Algolia index for institutes, and a hub links to tens of schools
68
+ * rather than thousands, so the records Institutes.tsx already holds on
69
+ * OrganisationContext (`trustOrHubSchools`, scoped to what this user may see)
70
+ * are searched directly. Matches on the school's name and email domain, and on
71
+ * its admin's name and email where the caller attached the admin.
72
+ *
73
+ * @param {UserData} user the signed-in hub user.
74
+ * @param {GlobalSearchParams["schools"]} schools the hub's linked schools, keyed by oId.
75
+ * @param {string} query whatever the user typed.
76
+ * @return {GlobalSearchHit[]} every matching school, alphabetically.
77
+ */
78
+ const searchLinkedSchools = (user, schools = {}, query) => Object.entries(schools)
79
+ .filter(([, school]) => matchesEveryTerm([
80
+ school.name,
81
+ school.emailDomain,
82
+ fullName(school.adminUser?.details?.forename, school.adminUser?.details?.surname),
83
+ school.adminUser?.email,
84
+ ].filter(Boolean).join(" "), query))
85
+ .sort(([, a], [, b]) => (a.name || "").localeCompare(b.name || ""))
86
+ .map(([oId, school]) => {
87
+ const status = school.careersHubLinks?.[user.oId]?.status;
88
+ return {
89
+ id: oId,
90
+ title: school.name || "Unnamed school",
91
+ subtitle: joinParts(fullName(school.adminUser?.details?.forename, school.adminUser?.details?.surname) || undefined, school.adminUser?.email),
92
+ meta: status && (0, util_1.capitaliseWords)(status),
93
+ path: `/institutes/schools/viewSchool/${oId}`,
94
+ raw: school,
95
+ };
96
+ });
97
+ exports.searchLinkedSchools = searchLinkedSchools;
57
98
  /**
58
99
  * The indexes the navbar search covers.
59
100
  *
@@ -116,6 +157,15 @@ exports.GLOBAL_SEARCH_INDEXES = [
116
157
  raw: hit,
117
158
  }),
118
159
  },
160
+ {
161
+ id: "schools",
162
+ label: "Schools",
163
+ labelSingular: "school",
164
+ // Hubs only for now. A trust's schools open a different page, so they
165
+ // are a separate entry with its own path once wanted.
166
+ available: (institute) => institute?.package === "careersHub",
167
+ local: ({ user, schools, query }) => (0, exports.searchLinkedSchools)(user, schools, query),
168
+ },
119
169
  ];
120
170
  /**
121
171
  * The indexes this institute can search, in the order they should be shown.
@@ -126,9 +176,13 @@ exports.GLOBAL_SEARCH_INDEXES = [
126
176
  */
127
177
  const availableGlobalSearchIndexes = (institute, user) => exports.GLOBAL_SEARCH_INDEXES.filter((index) => index.available(institute, user));
128
178
  exports.availableGlobalSearchIndexes = availableGlobalSearchIndexes;
179
+ const isAlgoliaBacked = (index) => "indexName" in index;
129
180
  /**
130
181
  * Search every index this institute can see, in one round trip.
131
182
  *
183
+ * Algolia-backed indexes go out together in one multipleQueries batch; local
184
+ * ones are matched from the records passed in and paged here to the same shape.
185
+ *
132
186
  * Indexes with no matches are still returned, with `nbHits: 0` and no hits, so
133
187
  * a drill-down can say "no results" rather than render nothing. The overview UI
134
188
  * is expected to skip empty sections itself.
@@ -136,35 +190,44 @@ exports.availableGlobalSearchIndexes = availableGlobalSearchIndexes;
136
190
  * @param {GlobalSearchParams} params the search.
137
191
  * @return {Promise<GlobalSearchResults>} results keyed by index id.
138
192
  */
139
- const globalSearch = async ({ user, institute, query, indexIds, hitsPerPage = exports.GLOBAL_SEARCH_OVERVIEW_HITS, page = 0, }) => {
193
+ const globalSearch = async (params) => {
194
+ const { user, institute, query, indexIds, hitsPerPage = exports.GLOBAL_SEARCH_OVERVIEW_HITS, page = 0 } = params;
140
195
  const trimmed = query?.trim();
141
- if (!trimmed || !user?.algoliaKey)
196
+ if (!trimmed || !user)
142
197
  return {};
143
198
  const indexes = (0, exports.availableGlobalSearchIndexes)(institute, user)
144
199
  .filter((index) => !indexIds || indexIds.includes(index.id));
145
200
  if (!indexes.length)
146
201
  return {};
147
- const client = (0, algoliasearch_1.default)(algoliaConfig_1.ALGOLIA_APP_ID, user.algoliaKey);
148
- const { results } = await client.multipleQueries(indexes.map((index) => ({
149
- indexName: index.indexName,
150
- query: trimmed,
151
- params: {
152
- filters: index.filters(user, institute),
153
- hitsPerPage,
154
- page,
155
- },
156
- })));
157
- return Object.fromEntries(indexes.map((index, i) => {
158
- const result = results[i];
159
- return [index.id, {
160
- id: index.id,
161
- label: index.label,
162
- labelSingular: index.labelSingular,
163
- nbHits: result?.nbHits ?? 0,
164
- page: result?.page ?? page,
165
- nbPages: result?.nbPages ?? 0,
166
- hits: (result?.hits ?? []).map(index.toHit),
167
- }];
202
+ const algoliaIndexes = indexes.filter(isAlgoliaBacked);
203
+ const algoliaResults = algoliaIndexes.length && user.algoliaKey ?
204
+ (await (0, algoliasearch_1.default)(algoliaConfig_1.ALGOLIA_APP_ID, user.algoliaKey).multipleQueries(algoliaIndexes.map((index) => ({
205
+ indexName: index.indexName,
206
+ query: trimmed,
207
+ params: {
208
+ filters: index.filters(user, institute),
209
+ hitsPerPage,
210
+ page,
211
+ },
212
+ })))).results :
213
+ [];
214
+ const section = (index, hits, nbHits, nbPages, at) => ({
215
+ id: index.id,
216
+ label: index.label,
217
+ labelSingular: index.labelSingular,
218
+ nbHits,
219
+ page: at,
220
+ nbPages,
221
+ hits,
222
+ });
223
+ return Object.fromEntries(indexes.map((index) => {
224
+ if (!isAlgoliaBacked(index)) {
225
+ const matches = index.local({ ...params, query: trimmed });
226
+ const start = page * hitsPerPage;
227
+ return [index.id, section(index, matches.slice(start, start + hitsPerPage), matches.length, Math.ceil(matches.length / hitsPerPage), page)];
228
+ }
229
+ const result = algoliaResults[algoliaIndexes.indexOf(index)];
230
+ return [index.id, section(index, (result?.hits ?? []).map(index.toHit), result?.nbHits ?? 0, result?.nbPages ?? 0, result?.page ?? page)];
168
231
  }));
169
232
  };
170
233
  exports.globalSearch = globalSearch;
@@ -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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"}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "private": false,
3
3
  "name": "placementt-core",
4
4
  "author": "Placementt",
5
- "version": "1.400.1097",
5
+ "version": "1.400.1098",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/index.d.ts",
8
8
  "scripts": {
@@ -1,18 +1,20 @@
1
1
  /**
2
- * globalSearch — one query box, several Algolia indexes.
2
+ * globalSearch — one query box, several indexes.
3
3
  *
4
4
  * The navbar search sends whatever the user typed here. Every index that the
5
5
  * signed-in institute can see is queried in a single round trip, and the answer
6
6
  * comes back keyed by index so the UI can render a section per index.
7
7
  *
8
- * Adding an index is meant to be a single entry in GLOBAL_SEARCH_INDEXES: name
9
- * the Algolia index, say when an institute can see it, restrict it to that
10
- * institute's records, and describe how one hit becomes a row.
8
+ * Adding an index is meant to be a single entry in GLOBAL_SEARCH_INDEXES. Most
9
+ * are Algolia-backed: name the index, say when an institute can see it, restrict
10
+ * it to that institute's records, and describe how one hit becomes a row. A
11
+ * small collection the caller already holds can instead be matched in memory
12
+ * with `local`, as a hub's schools are.
11
13
  */
12
14
 
13
15
  import algoliasearch from "algoliasearch";
14
16
  import {InstituteData, UserData} from "../../typeDefinitions";
15
- import {quoteAlgoliaFilterIfNeeded} from "../../firebase/util";
17
+ import {capitaliseWords, quoteAlgoliaFilterIfNeeded} from "../../firebase/util";
16
18
  import {ALGOLIA_APP_ID} from "../../algoliaConfig";
17
19
  import {resolveProducts} from "../../productResolver";
18
20
 
@@ -22,7 +24,7 @@ export const GLOBAL_SEARCH_OVERVIEW_HITS = 5;
22
24
  /** How many results one index shows per page once the user has drilled in. */
23
25
  export const GLOBAL_SEARCH_INDEX_HITS = 10;
24
26
 
25
- export type GlobalSearchIndexId = "placements"|"employers"|"students";
27
+ export type GlobalSearchIndexId = "placements"|"employers"|"students"|"schools";
26
28
 
27
29
  /** One row, flattened out of whatever the underlying index stores. */
28
30
  export type GlobalSearchHit = {
@@ -62,15 +64,22 @@ export type GlobalSearchIndexDefinition = {
62
64
  id: GlobalSearchIndexId,
63
65
  label: string,
64
66
  labelSingular: string,
65
- /** The Algolia index name — the unsorted, relevance-ordered replica. */
66
- indexName: string,
67
67
  /** Whether this institute has the product that owns these records. */
68
68
  available: (institute?: InstituteData, user?: UserData) => boolean,
69
+ } & ({
70
+ /** The Algolia index name — the unsorted, relevance-ordered replica. */
71
+ indexName: string,
69
72
  /** Restricts the index to records this user is allowed to see. */
70
73
  filters: (user: UserData, institute?: InstituteData) => string,
71
74
  /** Turns one raw hit into a row. */
72
75
  toHit: (hit: any) => GlobalSearchHit,
73
- };
76
+ } | {
77
+ /**
78
+ * Searched in memory, from records the caller already holds, instead of
79
+ * through Algolia. Returns every match, ranked; `globalSearch` pages it.
80
+ */
81
+ local: (params: GlobalSearchParams) => GlobalSearchHit[],
82
+ });
74
83
 
75
84
  const fullName = (forename?: string, surname?: string) =>
76
85
  [forename, surname].filter(Boolean).join(" ").trim();
@@ -78,6 +87,12 @@ const fullName = (forename?: string, surname?: string) =>
78
87
  const joinParts = (...parts: (string|undefined|false)[]) =>
79
88
  parts.filter(Boolean).join(" · ") || undefined;
80
89
 
90
+ /** Every whitespace-separated term of the query appears somewhere in the text. */
91
+ const matchesEveryTerm = (text: string, query: string) => {
92
+ const haystack = text.toLowerCase();
93
+ return query.toLowerCase().split(/\s+/).filter(Boolean).every((term) => haystack.includes(term));
94
+ };
95
+
81
96
  /** Packages that run their own students, placements and cohorts. */
82
97
  const isSchoolPackage = (institute?: InstituteData) =>
83
98
  institute?.package === "institutes-one" || institute?.package === "institutes-two";
@@ -114,6 +129,44 @@ export const employerSearchScopes = (user: UserData, institute?: InstituteData):
114
129
  ];
115
130
  };
116
131
 
132
+ /**
133
+ * A careers hub's linked schools, matched in memory.
134
+ *
135
+ * There is no Algolia index for institutes, and a hub links to tens of schools
136
+ * rather than thousands, so the records Institutes.tsx already holds on
137
+ * OrganisationContext (`trustOrHubSchools`, scoped to what this user may see)
138
+ * are searched directly. Matches on the school's name and email domain, and on
139
+ * its admin's name and email where the caller attached the admin.
140
+ *
141
+ * @param {UserData} user the signed-in hub user.
142
+ * @param {GlobalSearchParams["schools"]} schools the hub's linked schools, keyed by oId.
143
+ * @param {string} query whatever the user typed.
144
+ * @return {GlobalSearchHit[]} every matching school, alphabetically.
145
+ */
146
+ export const searchLinkedSchools = (user: UserData, schools: GlobalSearchParams["schools"] = {}, query: string): GlobalSearchHit[] =>
147
+ Object.entries(schools)
148
+ .filter(([, school]) => matchesEveryTerm([
149
+ school.name,
150
+ school.emailDomain,
151
+ fullName(school.adminUser?.details?.forename, school.adminUser?.details?.surname),
152
+ school.adminUser?.email,
153
+ ].filter(Boolean).join(" "), query))
154
+ .sort(([, a], [, b]) => (a.name || "").localeCompare(b.name || ""))
155
+ .map(([oId, school]) => {
156
+ const status = school.careersHubLinks?.[user.oId]?.status;
157
+ return {
158
+ id: oId,
159
+ title: school.name || "Unnamed school",
160
+ subtitle: joinParts(
161
+ fullName(school.adminUser?.details?.forename, school.adminUser?.details?.surname) || undefined,
162
+ school.adminUser?.email,
163
+ ),
164
+ meta: status && capitaliseWords(status),
165
+ path: `/institutes/schools/viewSchool/${oId}`,
166
+ raw: school,
167
+ };
168
+ });
169
+
117
170
  /**
118
171
  * The indexes the navbar search covers.
119
172
  *
@@ -183,6 +236,15 @@ export const GLOBAL_SEARCH_INDEXES: GlobalSearchIndexDefinition[] = [
183
236
  raw: hit,
184
237
  }),
185
238
  },
239
+ {
240
+ id: "schools",
241
+ label: "Schools",
242
+ labelSingular: "school",
243
+ // Hubs only for now. A trust's schools open a different page, so they
244
+ // are a separate entry with its own path once wanted.
245
+ available: (institute) => institute?.package === "careersHub",
246
+ local: ({user, schools, query}) => searchLinkedSchools(user, schools, query),
247
+ },
186
248
  ];
187
249
 
188
250
  /**
@@ -198,6 +260,11 @@ export const availableGlobalSearchIndexes = (institute?: InstituteData, user?: U
198
260
  export type GlobalSearchParams = {
199
261
  user: UserData,
200
262
  institute?: InstituteData,
263
+ /**
264
+ * A hub's linked schools, as Institutes.tsx holds them on OrganisationContext.
265
+ * Searched in memory by the "schools" index; nothing else reads it.
266
+ */
267
+ schools?: {[oId: string]: InstituteData&{adminUser?: UserData}},
201
268
  /** Whatever the user typed. Blank returns nothing rather than everything. */
202
269
  query: string,
203
270
  /** Restrict to specific indexes — how the drill-down asks for one index. */
@@ -207,9 +274,16 @@ export type GlobalSearchParams = {
207
274
  page?: number,
208
275
  };
209
276
 
277
+ type AlgoliaBackedIndex = Extract<GlobalSearchIndexDefinition, {indexName: string}>;
278
+
279
+ const isAlgoliaBacked = (index: GlobalSearchIndexDefinition): index is AlgoliaBackedIndex => "indexName" in index;
280
+
210
281
  /**
211
282
  * Search every index this institute can see, in one round trip.
212
283
  *
284
+ * Algolia-backed indexes go out together in one multipleQueries batch; local
285
+ * ones are matched from the records passed in and paged here to the same shape.
286
+ *
213
287
  * Indexes with no matches are still returned, with `nbHits: 0` and no hits, so
214
288
  * a drill-down can say "no results" rather than render nothing. The overview UI
215
289
  * is expected to skip empty sections itself.
@@ -217,45 +291,47 @@ export type GlobalSearchParams = {
217
291
  * @param {GlobalSearchParams} params the search.
218
292
  * @return {Promise<GlobalSearchResults>} results keyed by index id.
219
293
  */
220
- export const globalSearch = async ({
221
- user,
222
- institute,
223
- query,
224
- indexIds,
225
- hitsPerPage = GLOBAL_SEARCH_OVERVIEW_HITS,
226
- page = 0,
227
- }: GlobalSearchParams): Promise<GlobalSearchResults> => {
294
+ export const globalSearch = async (params: GlobalSearchParams): Promise<GlobalSearchResults> => {
295
+ const {user, institute, query, indexIds, hitsPerPage = GLOBAL_SEARCH_OVERVIEW_HITS, page = 0} = params;
228
296
  const trimmed = query?.trim();
229
- if (!trimmed || !user?.algoliaKey) return {};
297
+ if (!trimmed || !user) return {};
230
298
 
231
299
  const indexes = availableGlobalSearchIndexes(institute, user)
232
300
  .filter((index) => !indexIds || indexIds.includes(index.id));
233
301
 
234
302
  if (!indexes.length) return {};
235
303
 
236
- const client = algoliasearch(ALGOLIA_APP_ID, user.algoliaKey);
304
+ const algoliaIndexes = indexes.filter(isAlgoliaBacked);
305
+ const algoliaResults = algoliaIndexes.length && user.algoliaKey ?
306
+ (await algoliasearch(ALGOLIA_APP_ID, user.algoliaKey).multipleQueries(algoliaIndexes.map((index) => ({
307
+ indexName: index.indexName,
308
+ query: trimmed,
309
+ params: {
310
+ filters: index.filters(user, institute),
311
+ hitsPerPage,
312
+ page,
313
+ },
314
+ })))).results :
315
+ [];
237
316
 
238
- const {results} = await client.multipleQueries(indexes.map((index) => ({
239
- indexName: index.indexName,
240
- query: trimmed,
241
- params: {
242
- filters: index.filters(user, institute),
243
- hitsPerPage,
244
- page,
245
- },
246
- })));
317
+ const section = (index: GlobalSearchIndexDefinition, hits: GlobalSearchHit[], nbHits: number, nbPages: number, at: number): GlobalSearchIndexResults => ({
318
+ id: index.id,
319
+ label: index.label,
320
+ labelSingular: index.labelSingular,
321
+ nbHits,
322
+ page: at,
323
+ nbPages,
324
+ hits,
325
+ });
247
326
 
248
- return Object.fromEntries(indexes.map((index, i) => {
249
- const result = results[i] as {hits?: any[], nbHits?: number, page?: number, nbPages?: number}|undefined;
250
- return [index.id, {
251
- id: index.id,
252
- label: index.label,
253
- labelSingular: index.labelSingular,
254
- nbHits: result?.nbHits ?? 0,
255
- page: result?.page ?? page,
256
- nbPages: result?.nbPages ?? 0,
257
- hits: (result?.hits ?? []).map(index.toHit),
258
- } as GlobalSearchIndexResults];
327
+ return Object.fromEntries(indexes.map((index) => {
328
+ if (!isAlgoliaBacked(index)) {
329
+ const matches = index.local({...params, query: trimmed});
330
+ const start = page * hitsPerPage;
331
+ return [index.id, section(index, matches.slice(start, start + hitsPerPage), matches.length, Math.ceil(matches.length / hitsPerPage), page)];
332
+ }
333
+ const result = algoliaResults[algoliaIndexes.indexOf(index)] as {hits?: any[], nbHits?: number, page?: number, nbPages?: number}|undefined;
334
+ return [index.id, section(index, (result?.hits ?? []).map(index.toHit), result?.nbHits ?? 0, result?.nbPages ?? 0, result?.page ?? page)];
259
335
  })) as GlobalSearchResults;
260
336
  };
261
337