refactor: improve perf by calling api in parallel in clientLoaders (#312)

* refactor: improve perf by calling api in parallel in clientLoaders

* fix: dependent api calls
This commit is contained in:
Nico 2026-01-05 21:10:00 +01:00 committed by Nicolas Meienberger
parent 13428224e8
commit f0e956783a
7 changed files with 59 additions and 25 deletions

View file

@ -21,11 +21,13 @@ import { StatusDot } from "~/client/components/status-dot";
import { formatDistanceToNow } from "date-fns";
import { Link } from "react-router";
import { cn } from "~/client/lib/utils";
import type { GetScheduleMirrorsResponse } from "~/client/api-client";
type Props = {
scheduleId: number;
primaryRepositoryId: string;
repositories: Repository[];
initialData: GetScheduleMirrorsResponse;
};
type MirrorAssignment = {
@ -36,13 +38,14 @@ type MirrorAssignment = {
lastCopyError: string | null;
};
export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, repositories }: Props) => {
export const ScheduleMirrorsConfig = ({ scheduleId, primaryRepositoryId, repositories, initialData }: Props) => {
const [assignments, setAssignments] = useState<Map<string, MirrorAssignment>>(new Map());
const [hasChanges, setHasChanges] = useState(false);
const [isAddingNew, setIsAddingNew] = useState(false);
const { data: currentMirrors } = useQuery({
...getScheduleMirrorsOptions({ path: { scheduleId: scheduleId.toString() } }),
initialData,
});
const { data: compatibility } = useQuery({

View file

@ -14,10 +14,12 @@ import {
} from "~/client/api-client/@tanstack/react-query.gen";
import { parseError } from "~/client/lib/errors";
import type { NotificationDestination } from "~/client/lib/types";
import type { GetScheduleNotificationsResponse } from "~/client/api-client";
type Props = {
scheduleId: number;
destinations: NotificationDestination[];
initialData: GetScheduleNotificationsResponse;
};
type NotificationAssignment = {
@ -28,13 +30,14 @@ type NotificationAssignment = {
notifyOnFailure: boolean;
};
export const ScheduleNotificationsConfig = ({ scheduleId, destinations }: Props) => {
export const ScheduleNotificationsConfig = ({ scheduleId, destinations, initialData }: Props) => {
const [assignments, setAssignments] = useState<Map<number, NotificationAssignment>>(new Map());
const [hasChanges, setHasChanges] = useState(false);
const [isAddingNew, setIsAddingNew] = useState(false);
const { data: currentAssignments } = useQuery({
...getScheduleNotificationsOptions({ path: { scheduleId: scheduleId.toString() } }),
initialData,
});
const updateNotifications = useMutation({

View file

@ -30,7 +30,13 @@ import { ScheduleSummary } from "../components/schedule-summary";
import type { Route } from "./+types/backup-details";
import { SnapshotFileBrowser } from "../components/snapshot-file-browser";
import { SnapshotTimeline } from "../components/snapshot-timeline";
import { getBackupSchedule, listNotificationDestinations, listRepositories } from "~/client/api-client";
import {
getBackupSchedule,
getScheduleMirrors,
getScheduleNotifications,
listNotificationDestinations,
listRepositories,
} from "~/client/api-client";
import { ScheduleNotificationsConfig } from "../components/schedule-notifications-config";
import { ScheduleMirrorsConfig } from "../components/schedule-mirrors-config";
import { cn } from "~/client/lib/utils";
@ -53,13 +59,23 @@ export function meta(_: Route.MetaArgs) {
}
export const clientLoader = async ({ params }: Route.LoaderArgs) => {
const schedule = await getBackupSchedule({ path: { scheduleId: params.id } });
const notifs = await listNotificationDestinations();
const repos = await listRepositories();
const [schedule, notifs, repos, scheduleNotifs, mirrors] = await Promise.all([
getBackupSchedule({ path: { scheduleId: params.id } }),
listNotificationDestinations(),
listRepositories(),
getScheduleNotifications({ path: { scheduleId: params.id } }),
getScheduleMirrors({ path: { scheduleId: params.id } }),
]);
if (!schedule.data) return redirect("/backups");
return { schedule: schedule.data, notifs: notifs.data, repos: repos.data };
return {
schedule: schedule.data,
notifs: notifs.data,
repos: repos.data,
scheduleNotifs: scheduleNotifs.data,
scheduleMirrors: mirrors.data,
};
};
export default function ScheduleDetailsPage({ params, loaderData }: Route.ComponentProps) {
@ -240,13 +256,18 @@ export default function ScheduleDetailsPage({ params, loaderData }: Route.Compon
schedule={schedule}
/>
<div className={cn({ hidden: !loaderData.notifs?.length })}>
<ScheduleNotificationsConfig scheduleId={schedule.id} destinations={loaderData.notifs ?? []} />
<ScheduleNotificationsConfig
scheduleId={schedule.id}
destinations={loaderData.notifs ?? []}
initialData={loaderData.scheduleNotifs ?? []}
/>
</div>
<div className={cn({ hidden: !loaderData.repos?.length || loaderData.repos.length < 2 })}>
<ScheduleMirrorsConfig
scheduleId={schedule.id}
primaryRepositoryId={schedule.repositoryId}
repositories={loaderData.repos ?? []}
initialData={loaderData.scheduleMirrors ?? []}
/>
</div>
<SnapshotTimeline

View file

@ -33,8 +33,7 @@ export function meta(_: Route.MetaArgs) {
}
export const clientLoader = async () => {
const volumes = await listVolumes();
const repositories = await listRepositories();
const [volumes, repositories] = await Promise.all([listVolumes(), listRepositories()]);
if (volumes.data && repositories.data) return { volumes: volumes.data, repositories: repositories.data };
return { volumes: [], repositories: [] };

View file

@ -24,15 +24,20 @@ export function meta({ params }: Route.MetaArgs) {
export const clientLoader = async ({ params }: Route.ClientLoaderArgs) => {
const schedule = await getBackupSchedule({ path: { scheduleId: params.id } });
if (!schedule.data) return redirect("/backups");
const repositoryId = schedule.data.repository.id;
const snapshot = await getSnapshotDetails({
path: { id: repositoryId, snapshotId: params.snapshotId },
});
if (!snapshot.data) return redirect(`/backups/${params.id}`);
const [snapshot, repository] = await Promise.all([
getSnapshotDetails({
path: {
id: schedule.data.repositoryId,
snapshotId: params.snapshotId,
},
}),
getRepository({ path: { id: schedule.data.repositoryId } }),
]);
const repository = await getRepository({ path: { id: repositoryId } });
if (!snapshot.data) return redirect(`/backups/${params.id}`);
if (!repository.data) return redirect(`/backups/${params.id}`);
return {

View file

@ -23,12 +23,12 @@ export function meta({ params }: Route.MetaArgs) {
}
export const clientLoader = async ({ params }: Route.ClientLoaderArgs) => {
const snapshot = await getSnapshotDetails({
path: { id: params.id, snapshotId: params.snapshotId },
});
if (!snapshot.data) return redirect("/repositories");
const [snapshot, repository] = await Promise.all([
getSnapshotDetails({ path: { id: params.id, snapshotId: params.snapshotId } }),
getRepository({ path: { id: params.id } }),
]);
const repository = await getRepository({ path: { id: params.id } });
if (!snapshot.data) return redirect("/repositories");
if (!repository.data) return redirect(`/repositories`);
return { snapshot: snapshot.data, id: params.id, repository: repository.data, snapshotId: params.snapshotId };

View file

@ -26,11 +26,14 @@ export function meta({ params }: Route.MetaArgs) {
}
export const clientLoader = async ({ params }: Route.ClientLoaderArgs) => {
const snapshot = getSnapshotDetails({
path: { id: params.id, snapshotId: params.snapshotId },
});
const [snapshot, repository] = await Promise.all([
getSnapshotDetails({
path: { id: params.id, snapshotId: params.snapshotId },
}),
getRepository({ path: { id: params.id } }),
]);
const repository = await getRepository({ path: { id: params.id } });
if (!snapshot.data) return redirect(`/repositories/${params.id}`);
if (!repository.data) return redirect("/repositories");
return { snapshot: snapshot, repository: repository.data };