-
Notifications
You must be signed in to change notification settings - Fork 8
feat: search on all pub page #1290
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
Conversation
| <ContentLayout | ||
| title={ | ||
| <> | ||
| <BookOpen size={24} strokeWidth={1} className="mr-2 text-gray-500" /> Pubs | ||
| </> | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added the contentlayout for the pubs page
| // deferred query to keep track of server updates | ||
| // without this, we can't only check if inputValue !== query.query, | ||
| // which only tells us that the debounce has happened | ||
| const deferredQuery = useDeferredValue(query.query); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is kind of mysterious to me, but this allows me to keep the body of the search at half-opacity until the content has fully refreshed. this could also be done by using useTransition.
| export type KeyCombination = | ||
| | `${Modifiers}+${Keys}` | ||
| | `${Modifiers}+${Modifiers}+${Keys}` | ||
| | `${Modifiers}+${Modifiers}+${Modifiers}+${Keys}` | ||
| | `${Modifiers}+${Modifiers}+${Modifiers}+${Modifiers}+${Keys}`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
niceish type that allows for fully typed Mod+k like strings. doesn't really cover every key (only english-y ones), but that's fine bc it's us defining the shortcuts, and we (mostly) use English keyboards
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a smallish shortcut handler that allows us to define shortcuts kinda easily, and executes them in order of priority.
i wanted this bc on the pubs page we were using CMD+k to focus on the input, while on any other page it should pull up the search dialogue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sadly this doesn't yet incorporate the editor, as the keyboard shortcut logic there is handled by prosemirror itself.
the way i was thinking of doing that was something like:
- expose some hooks to enable/disable certain keys from here
- use this hook in the body of context editor
- pass enable/disable functions to keymap plugin
- have keymap disable those also used by the editor (cmd+k)
ideally it would disable/enable the shortcuts on blur/focus as well, but that might be too fragile. if you have a better idea please let me know!
| /** | ||
| * Separately cache this call because validateRequest can be called with different options | ||
| * leading to cache misses | ||
| */ | ||
| const validateLuciaSession = cache(async (sessionId: string) => { | ||
| const result = await lucia.validateSession(sessionId); | ||
| return result; | ||
| }); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is just a small thing i found when looking at logs:
we sometimes call getPageLoginData and sometimes getLoginData on the same request. i would expect the call to the db to be cached between these two calls, bc getLoginData is called by getPageLoginData and getLoginData is React.cached.
platform/core/lib/authentication/loginData.ts
Lines 17 to 51 in f633a09
| /** | |
| * Get the users login data based on the session cookie | |
| */ | |
| export const getLoginData = cache(async (opts?: ExtraSessionValidationOptions) => { | |
| return validateRequest(opts); | |
| }); | |
| const defaultPageOpts: ExtraSessionValidationOptions & LoginRedirectOpts = { | |
| allowedSessions: [AuthTokenType.generic, AuthTokenType.verifyEmail], | |
| }; | |
| /** | |
| * Get the login data for the current page, and redirect to the login page if the user is not logged in. | |
| */ | |
| export const getPageLoginData = cache( | |
| async (opts?: ExtraSessionValidationOptions & LoginRedirectOpts) => { | |
| const options = opts ?? defaultPageOpts; | |
| const loginData = await getLoginData(options); | |
| if (!loginData.user) { | |
| redirectToLogin(options); | |
| } | |
| if (loginData.session && loginData.session.type === AuthTokenType.verifyEmail) { | |
| const pathname = getPathname(); | |
| redirectToVerify({ | |
| redirectTo: expect(pathname, "pathname is missing for redirectToVerify").toString(), | |
| }); | |
| } | |
| return loginData; | |
| } | |
| ); | |
| export type LoginData = Awaited<ReturnType<typeof getLoginData>>; |
but that doesn't happen, bc we pass in some specific arguments to getLoginData (allowedSessions) when called by getPageLoginData.
this leads to getLoginData to be called with diff arguments most of the time (we usually don't pass anything to getLoginData), leading to cache misses.
So it made more sense to me cache the actual call to the db as well, as that's the main bottleneck and always called with the same arguments (for the same request): the sessionid.
this takes the number of calls to the db for this purpose from 2 to 1 in many cases.
| .$if(Boolean(tsQuery), (qb) => | ||
| qb.select((eb) => [ | ||
| sql<string>`ts_headline( | ||
| '${sql.raw(language)}', | ||
| pt.title, | ||
| ${tsQuery}, | ||
| '${sql.raw(headlineConfig)}' | ||
| )`.as("title"), | ||
| ]) | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure if this should override the normal title, i just did that here bc it's easy, but we could also have it be returned separately
| .$if(Boolean(includeSearchValues), (qb) => | ||
| qb.select((eb) => | ||
| jsonArrayFrom( | ||
| eb | ||
| .selectFrom("pub_values") | ||
| .innerJoin("pub_fields", "pub_fields.id", "pub_values.fieldId") | ||
| .innerJoin("_PubFieldToPubType", (join) => | ||
| join | ||
| .onRef("A", "=", "pub_fields.id") | ||
| .onRef("B", "=", "pt.pubTypeId") | ||
| ) | ||
| .select([ | ||
| "pub_values.value", | ||
| "pub_fields.name", | ||
| "pub_fields.slug", | ||
| "pub_fields.schemaName", | ||
| "_PubFieldToPubType.isTitle", | ||
| sql<string>`ts_headline( | ||
| '${sql.raw(language)}', | ||
| pub_values.value#>>'{}', | ||
| ${tsQuery}, | ||
| '${sql.raw(headlineConfig)}' | ||
| )`.as("highlights"), | ||
| ]) | ||
| .$narrowType<{ | ||
| value: JsonValue; | ||
| // still typed as null in db | ||
| schemaName: CoreSchemaType; | ||
| }>() | ||
| .whereRef("pub_values.pubId", "=", "pt.pubId") | ||
| .where( | ||
| (eb) => sql`to_tsvector(${language}, value#>>'{}') @@ ${tsQuery}` | ||
| ) | ||
| ).as("matchingValues") | ||
| ) | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is kinda expensive i think, especially if theres a lot of long RichText and a lot of values, but i don't have a better solution.
very bad measurements, but searching for comp on legacy was roughly ~80ms each time with this, and ~40ms each time without. im pretty sure most of the extra delay comes from having to both fetch and highlight the really long body of the "Test article" called "The Complexity of Joint Regeneration: How an Advanced Implant Could Fail by Its In Vivo Proven Bone Component".
i worry about how the performance will scale with a ton of these.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but i think we should just leave this for now and leave the performance tuning for after doing some initial legacy migrations. if we find performance really lacking there we could maybe think about including a dedicated fts solution, maybe use redis for that as well, or a meilisearch instance or something.
| .$if(withActionInstances === "count", (qb) => | ||
| qb.select((eb) => | ||
| eb | ||
| .selectFrom("action_instances") | ||
| .whereRef("action_instances.stageId", "=", "stages.id") | ||
| .select((eb) => | ||
| eb.fn.count<number>("action_instances.id").as("actionInstancesCount") | ||
| ) | ||
| .as("actionInstancesCount") | ||
| ) | ||
| ) | ||
| .$if(withActionInstances === "full", (qb) => | ||
| qb.select((eb) => | ||
| jsonArrayFrom( | ||
| eb | ||
| .selectFrom("action_instances") | ||
| .whereRef("action_instances.stageId", "=", "stages.id") | ||
| .selectAll("action_instances") | ||
| ).as("actionInstances") | ||
| ) | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i kind of changes how the data is fetched for the pubs page, which wasn't working properly (see comment there).
| /** | ||
| * Whether to include matched and highlighted values | ||
| * @default true if `search` is defined | ||
| */ | ||
| withSearchValues?: boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
might be redundant to offer this a setting in addition to search, but eh
3mcd
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I love how snappy the search feels, and combined with the newer/condensed pub cards and pagination, the all pubs page is starting to look reaaaally good!
Issue(s) Resolved
Resolves #1197
High-level Explanation of PR
Adds search capabilities to
getPubsWithRelatedValuesand adds a searching filter to/pubs.Not done:
PubCardis still a server component. I'd need to rework it more, which i may do in feat: make pubcard more useable on mobile #1293Video overview (kinda rambly sorry)
pubsearch-overview.mp4
Test Plan
Screenshots (if applicable)
When searching
Changed the pagination a bit when searching, as i didn't want to find out how many results there are in total (that's kinda costly), so I just get
N = perPage + 1items, check ifN > perPage, if so i allow the user to paginate to the next page.There's an argument to make to just remove pagination for searching entirely, and only show the first ~25 results, and the user just needs to provide a better query if they aint happy.
Notes