Global search sort next tournament first

Closes #3328
This commit is contained in:
Kalle
2026-08-08 10:00:15 +03:00
parent 25f230e19b
commit 514a29d4e1
2 changed files with 111 additions and 1 deletions

View File

@@ -0,0 +1,88 @@
import { add, sub } from "date-fns";
import { beforeEach, describe, expect, test } from "vitest";
import * as TournamentFactory from "~/db/seed/factories/TournamentFactory";
import * as UserFactory from "~/db/seed/factories/UserFactory";
import { dateToDatabaseTimestamp } from "~/utils/dates";
import * as TournamentRepository from "./TournamentRepository.server";
const QUERY = "In The Zone";
let author: { id: number };
const createTournament = (name: string, startsAt: Date) =>
TournamentFactory.create({
authorId: author.id,
name,
startTimes: [dateToDatabaseTimestamp(startsAt)],
});
const search = async (limit = 10) =>
(await TournamentRepository.searchByName({ query: QUERY, limit })).map(
(tournament) => tournament.name,
);
describe("TournamentRepository.searchByName", () => {
beforeEach(async () => {
author = await UserFactory.create();
});
test("sorts a tournament that is happening right now first", async () => {
await createTournament(`${QUERY} Tomorrow`, add(new Date(), { days: 1 }));
await createTournament(`${QUERY} Started`, sub(new Date(), { hours: 3 }));
expect(await search()).toEqual([`${QUERY} Started`, `${QUERY} Tomorrow`]);
});
test("sorts the next tournament up before ones that already happened", async () => {
await createTournament(`${QUERY} Yesterday`, sub(new Date(), { days: 2 }));
await createTournament(
`${QUERY} Next Month`,
add(new Date(), { days: 30 }),
);
expect(await search()).toEqual([
`${QUERY} Next Month`,
`${QUERY} Yesterday`,
]);
});
test("sorts the rest by their distance from now", async () => {
await createTournament(
`${QUERY} In 3 Weeks`,
add(new Date(), { weeks: 3 }),
);
await createTournament(
`${QUERY} 2 Weeks Ago`,
sub(new Date(), { weeks: 2 }),
);
await createTournament(
`${QUERY} In 2 Weeks`,
add(new Date(), { weeks: 2 }),
);
await createTournament(
`${QUERY} 3 Weeks Ago`,
sub(new Date(), { weeks: 3 }),
);
expect(await search()).toEqual([
`${QUERY} In 2 Weeks`,
`${QUERY} 2 Weeks Ago`,
`${QUERY} In 3 Weeks`,
`${QUERY} 3 Weeks Ago`,
]);
});
test("keeps the next tournament up in a result set the limit cuts short", async () => {
await createTournament(`${QUERY} Next Year`, add(new Date(), { years: 1 }));
await createTournament(`${QUERY} Last Week`, sub(new Date(), { weeks: 1 }));
await createTournament(
`${QUERY} Last Month`,
sub(new Date(), { days: 30 }),
);
expect(await search(2)).toEqual([
`${QUERY} Next Year`,
`${QUERY} Last Week`,
]);
});
});

View File

@@ -1547,6 +1547,14 @@ export function finalizeWithoutSummary(tournamentId: number) {
.execute();
}
/** How close to its start time a tournament counts as happening right now. */
const TOURNAMENT_ONGOING_WINDOW_IN_SECONDS = 24 * 60 * 60;
/**
* Searches tournaments whose calendar event name contains the query, hidden events excluded.
*
* Ordered so that the tournaments most likely being looked for come first
*/
export async function searchByName({
query,
limit,
@@ -1558,6 +1566,12 @@ export async function searchByName({
minStartTime?: Date;
maxStartTime?: Date;
}) {
const now = databaseTimestampNow();
const distanceFromNow = sql<number>`abs("CalendarEventDate"."startsAt" - ${now})`;
// window function so that the next tournament up is the next one of all the matches,
// not only of the ones that happen to fit in the limit
const nextUpStartsAt = sql<number>`min(case when "CalendarEventDate"."startsAt" - ${now} >= ${TOURNAMENT_ONGOING_WINDOW_IN_SECONDS} then "CalendarEventDate"."startsAt" end) over ()`;
let sqlQuery = db
.selectFrom("Tournament")
.innerJoin("CalendarEvent", "Tournament.id", "CalendarEvent.tournamentId")
@@ -1574,7 +1588,15 @@ export async function searchByName({
])
.where("CalendarEvent.name", "like", `%${query}%`)
.where("CalendarEvent.hidden", "=", 0)
.orderBy("CalendarEventDate.startsAt", "desc")
.orderBy(
sql`case
when ${distanceFromNow} < ${TOURNAMENT_ONGOING_WINDOW_IN_SECONDS} then 0
when "CalendarEventDate"."startsAt" = ${nextUpStartsAt} then 1
else 2
end`,
)
.orderBy(distanceFromNow)
.orderBy("Tournament.id")
.limit(limit);
if (minStartTime) {