mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-14 15:16:09 -05:00
User results page optimizations (#2575)
This commit is contained in:
@@ -432,11 +432,9 @@ const withMaxEventStartTime = (eb: ExpressionBuilder<DB, "CalendarEvent">) => {
|
||||
.whereRef("CalendarEventDate.eventId", "=", "CalendarEvent.id")
|
||||
.as("startTime");
|
||||
};
|
||||
export function findResultsByUserId(
|
||||
userId: number,
|
||||
{ showHighlightsOnly = false }: { showHighlightsOnly?: boolean } = {},
|
||||
) {
|
||||
let calendarEventResultsQuery = db
|
||||
|
||||
const baseCalendarEventResultsQuery = (userId: number) =>
|
||||
db
|
||||
.selectFrom("CalendarEventResultPlayer")
|
||||
.innerJoin(
|
||||
"CalendarEventResultTeam",
|
||||
@@ -453,7 +451,33 @@ export function findResultsByUserId(
|
||||
.onRef("UserResultHighlight.teamId", "=", "CalendarEventResultTeam.id")
|
||||
.on("UserResultHighlight.userId", "=", userId),
|
||||
)
|
||||
.select(({ eb, fn }) => [
|
||||
.where("CalendarEventResultPlayer.userId", "=", userId);
|
||||
|
||||
const baseTournamentResultsQuery = (userId: number) =>
|
||||
db
|
||||
.selectFrom("TournamentResult")
|
||||
.innerJoin(
|
||||
"TournamentTeam",
|
||||
"TournamentTeam.id",
|
||||
"TournamentResult.tournamentTeamId",
|
||||
)
|
||||
.innerJoin(
|
||||
"CalendarEvent",
|
||||
"CalendarEvent.tournamentId",
|
||||
"TournamentResult.tournamentId",
|
||||
)
|
||||
.where("TournamentResult.userId", "=", userId);
|
||||
|
||||
export function findResultsByUserId(
|
||||
userId: number,
|
||||
{
|
||||
showHighlightsOnly = false,
|
||||
limit,
|
||||
offset,
|
||||
}: { showHighlightsOnly?: boolean; limit?: number; offset?: number } = {},
|
||||
) {
|
||||
let calendarEventResultsQuery = baseCalendarEventResultsQuery(userId).select(
|
||||
({ eb, fn }) => [
|
||||
"CalendarEvent.id as eventId",
|
||||
sql<number>`null`.as("tournamentId"),
|
||||
"CalendarEventResultTeam.placement",
|
||||
@@ -486,22 +510,11 @@ export function findResultsByUserId(
|
||||
]),
|
||||
),
|
||||
).as("mates"),
|
||||
])
|
||||
.where("CalendarEventResultPlayer.userId", "=", userId);
|
||||
],
|
||||
);
|
||||
|
||||
let tournamentResultsQuery = db
|
||||
.selectFrom("TournamentResult")
|
||||
.innerJoin(
|
||||
"TournamentTeam",
|
||||
"TournamentTeam.id",
|
||||
"TournamentResult.tournamentTeamId",
|
||||
)
|
||||
.innerJoin(
|
||||
"CalendarEvent",
|
||||
"CalendarEvent.tournamentId",
|
||||
"TournamentResult.tournamentId",
|
||||
)
|
||||
.select(({ eb }) => [
|
||||
let tournamentResultsQuery = baseTournamentResultsQuery(userId).select(
|
||||
({ eb }) => [
|
||||
sql<number>`null`.as("eventId"),
|
||||
"TournamentResult.tournamentId",
|
||||
"TournamentResult.placement",
|
||||
@@ -529,8 +542,8 @@ export function findResultsByUserId(
|
||||
)
|
||||
.where("TournamentResult2.userId", "!=", userId),
|
||||
).as("mates"),
|
||||
])
|
||||
.where("TournamentResult.userId", "=", userId);
|
||||
],
|
||||
);
|
||||
|
||||
if (showHighlightsOnly) {
|
||||
calendarEventResultsQuery = calendarEventResultsQuery.where(
|
||||
@@ -545,11 +558,53 @@ export function findResultsByUserId(
|
||||
);
|
||||
}
|
||||
|
||||
return calendarEventResultsQuery
|
||||
let query = calendarEventResultsQuery
|
||||
.unionAll(tournamentResultsQuery)
|
||||
.orderBy("startTime", "desc")
|
||||
.$narrowType<{ startTime: NotNull }>()
|
||||
.execute();
|
||||
.$narrowType<{ startTime: NotNull }>();
|
||||
|
||||
if (limit !== undefined) {
|
||||
query = query.limit(limit);
|
||||
}
|
||||
|
||||
if (offset !== undefined) {
|
||||
query = query.offset(offset);
|
||||
}
|
||||
|
||||
return query.execute();
|
||||
}
|
||||
|
||||
export async function countResultsByUserId(
|
||||
userId: number,
|
||||
{ showHighlightsOnly = false }: { showHighlightsOnly?: boolean } = {},
|
||||
) {
|
||||
let calendarEventResultsQuery = baseCalendarEventResultsQuery(userId).select(
|
||||
({ fn }) => [fn.countAll<number>().as("count")],
|
||||
);
|
||||
|
||||
let tournamentResultsQuery = baseTournamentResultsQuery(userId).select(
|
||||
({ fn }) => [fn.countAll<number>().as("count")],
|
||||
);
|
||||
|
||||
if (showHighlightsOnly) {
|
||||
calendarEventResultsQuery = calendarEventResultsQuery.where(
|
||||
"UserResultHighlight.userId",
|
||||
"is not",
|
||||
null,
|
||||
);
|
||||
tournamentResultsQuery = tournamentResultsQuery.where(
|
||||
"TournamentResult.isHighlight",
|
||||
"=",
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
const [calendarEventResults, tournamentResults] = await Promise.all([
|
||||
calendarEventResultsQuery.executeTakeFirst(),
|
||||
tournamentResultsQuery.executeTakeFirst(),
|
||||
]);
|
||||
|
||||
return (calendarEventResults?.count ?? 0) + (tournamentResults?.count ?? 0);
|
||||
}
|
||||
|
||||
export async function hasHighlightedResultsByUserId(userId: number) {
|
||||
|
||||
@@ -20,7 +20,7 @@ import type { UserResultsLoaderData } from "../loaders/u.$identifier.results.ser
|
||||
import { ParticipationPill } from "./ParticipationPill";
|
||||
|
||||
export type UserResultsTableProps = {
|
||||
results: UserResultsLoaderData["results"];
|
||||
results: UserResultsLoaderData["results"]["value"];
|
||||
id: string;
|
||||
hasHighlightCheckboxes?: boolean;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { LoaderFunctionArgs, SerializeFrom } from "@remix-run/node";
|
||||
import { redirect } from "@remix-run/node";
|
||||
import * as UserRepository from "~/features/user-page/UserRepository.server";
|
||||
import { notFoundIfFalsy, parseSafeSearchParams } from "~/utils/remix.server";
|
||||
import { RESULTS_PER_PAGE } from "../user-page-constants";
|
||||
import { userResultsPageSearchParamsSchema } from "../user-page-schemas";
|
||||
|
||||
export type UserResultsLoaderData = SerializeFrom<typeof loader>;
|
||||
@@ -30,10 +32,45 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
|
||||
showHighlightsOnly = false;
|
||||
}
|
||||
|
||||
return {
|
||||
results: await UserRepository.findResultsByUserId(userId, {
|
||||
const page = parsedSearchParams.success ? parsedSearchParams.data.page : 1;
|
||||
|
||||
const [results, totalCount] = await Promise.all([
|
||||
UserRepository.findResultsByUserId(userId, {
|
||||
showHighlightsOnly,
|
||||
...(isChoosingHighlights
|
||||
? {}
|
||||
: { limit: RESULTS_PER_PAGE, offset: (page - 1) * RESULTS_PER_PAGE }),
|
||||
}),
|
||||
UserRepository.countResultsByUserId(userId, { showHighlightsOnly }),
|
||||
]);
|
||||
|
||||
const maxPage = Math.ceil(totalCount / RESULTS_PER_PAGE);
|
||||
|
||||
redirectIfPageOutOfBounds({ request, page, maxPage });
|
||||
|
||||
return {
|
||||
results: {
|
||||
value: results,
|
||||
currentPage: page,
|
||||
pages: maxPage,
|
||||
},
|
||||
hasHighlightedResults,
|
||||
};
|
||||
};
|
||||
|
||||
function redirectIfPageOutOfBounds({
|
||||
request,
|
||||
page,
|
||||
maxPage,
|
||||
}: {
|
||||
request: Request;
|
||||
page: number;
|
||||
maxPage: number;
|
||||
}) {
|
||||
if (page <= maxPage || page === 1) return;
|
||||
|
||||
const url = new URL(request.url);
|
||||
const searchParams = new URLSearchParams(url.searchParams);
|
||||
searchParams.set("page", String(maxPage));
|
||||
throw redirect(`${url.pathname}?${searchParams.toString()}`);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export default function ResultHighlightsEditPage() {
|
||||
<legend>{t("user:results.highlights.explanation")}</legend>
|
||||
<UserResultsTable
|
||||
id="user-results-highlight-selection"
|
||||
results={data.results}
|
||||
results={data.results.value}
|
||||
hasHighlightCheckboxes
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useLoaderData, useMatches, useSearchParams } from "@remix-run/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { LinkButton } from "~/components/elements/Button";
|
||||
import { Pagination } from "~/components/Pagination";
|
||||
import { useUser } from "~/features/auth/core/user";
|
||||
import { UserResultsTable } from "~/features/user-page/components/UserResultsTable";
|
||||
import invariant from "~/utils/invariant";
|
||||
@@ -22,6 +23,13 @@ export default function UserResultsPage() {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const showAll = searchParams.get("all") === "true";
|
||||
|
||||
const setPage = (page: number) => {
|
||||
setSearchParams((params) => {
|
||||
params.set("page", String(page));
|
||||
return params;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="stack lg">
|
||||
<div className="stack horizontal justify-between items-center">
|
||||
@@ -38,7 +46,16 @@ export default function UserResultsPage() {
|
||||
</LinkButton>
|
||||
) : null}
|
||||
</div>
|
||||
<UserResultsTable id="user-results-table" results={data.results} />
|
||||
<UserResultsTable id="user-results-table" results={data.results.value} />
|
||||
{data.results.pages > 1 ? (
|
||||
<Pagination
|
||||
currentPage={data.results.currentPage}
|
||||
pagesCount={data.results.pages}
|
||||
nextPage={() => setPage(data.results.currentPage + 1)}
|
||||
previousPage={() => setPage(data.results.currentPage - 1)}
|
||||
setPage={setPage}
|
||||
/>
|
||||
) : null}
|
||||
{data.hasHighlightedResults ? (
|
||||
<SendouButton
|
||||
variant="minimal"
|
||||
@@ -46,6 +63,7 @@ export default function UserResultsPage() {
|
||||
onPress={() =>
|
||||
setSearchParams((params) => {
|
||||
params.set("all", showAll ? "false" : "true");
|
||||
params.delete("page");
|
||||
|
||||
return params;
|
||||
})
|
||||
|
||||
@@ -11,6 +11,7 @@ export const USER = {
|
||||
};
|
||||
|
||||
export const MATCHES_PER_SEASONS_PAGE = 8;
|
||||
export const RESULTS_PER_PAGE = 25;
|
||||
export const DEFAULT_BUILD_SORT = ["WEAPON_POOL", "UPDATED_AT"] as const;
|
||||
|
||||
export const CUSTOM_CSS_VAR_COLORS = [
|
||||
|
||||
@@ -160,5 +160,6 @@ export const adminTabActionSchema = z.union([
|
||||
]);
|
||||
|
||||
export const userResultsPageSearchParamsSchema = z.object({
|
||||
all: z.stringbool(),
|
||||
all: z.stringbool().catch(false),
|
||||
page: z.coerce.number().min(1).max(1_000).catch(1),
|
||||
});
|
||||
|
||||
9
migrations/099-missing-user-results-index.js
Normal file
9
migrations/099-missing-user-results-index.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export function up(db) {
|
||||
db.transaction(() => {
|
||||
db.prepare(
|
||||
/* sql */ `
|
||||
create index calendar_event_date_event_id_start_time on "CalendarEventDate"("eventId", "startTime" desc)
|
||||
`,
|
||||
).run();
|
||||
})();
|
||||
}
|
||||
Reference in New Issue
Block a user