refactor: replace serverdata with suspense query (#1265)

* refactor: replace serverdata with suspense query

* fix: deepsource issues
This commit is contained in:
Meier Lukas
2024-10-11 23:47:07 +02:00
committed by GitHub
parent 511c9a4dbb
commit 0f8d9edb3e
41 changed files with 288 additions and 646 deletions

View File

@@ -39,16 +39,25 @@ export default function DnsHoleControlsWidget({
options,
integrationIds,
isEditMode,
serverData,
}: WidgetComponentProps<typeof widgetKind>) {
// DnsHole integrations with interaction permissions
const integrationsWithInteractions = useIntegrationsWithInteractAccess()
.map(({ id }) => id)
.filter((id) => integrationIds.includes(id));
// Initial summaries, null summary means disconnected, undefined status means processing
const [summaries, setSummaries] = useState(serverData?.initialData ?? []);
const [summaries] = clientApi.widget.dnsHole.summary.useSuspenseQuery(
{
widgetKind: "dnsHoleControls",
integrationIds,
},
{
refetchOnMount: false,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
retry: false,
},
);
const utils = clientApi.useUtils();
// Subscribe to summary updates
clientApi.widget.dnsHole.subscribeToSummary.useSubscription(
{
@@ -57,8 +66,20 @@ export default function DnsHoleControlsWidget({
},
{
onData: (data) => {
setSummaries((prevSummaries) =>
prevSummaries.map((summary) => (summary.integration.id === data.integration.id ? data : summary)),
utils.widget.dnsHole.summary.setData(
{
widgetKind: "dnsHoleControls",
integrationIds,
},
(prevData) => {
if (!prevData) return undefined;
const newData = prevData.map((summary) =>
summary.integration.id === data.integration.id ? { ...summary, summary: data.summary } : summary,
);
return newData;
},
);
},
},
@@ -67,39 +88,77 @@ export default function DnsHoleControlsWidget({
// Mutations for dnsHole state, set to undefined on click, and change again on settle
const { mutate: enableDns } = clientApi.widget.dnsHole.enable.useMutation({
onSettled: (_, error, { integrationId }) => {
setSummaries((prevSummaries) =>
prevSummaries.map((data) => ({
...data,
summary:
data.integration.id === integrationId && data.summary
? { ...data.summary, status: error ? "disabled" : "enabled" }
: data.summary,
})),
utils.widget.dnsHole.summary.setData(
{
widgetKind: "dnsHoleControls",
integrationIds,
},
(prevData) => {
if (!prevData) return [];
return prevData.map((item) =>
item.integration.id === integrationId && item.summary
? {
...item,
summary: {
...item.summary,
status: error ? "disabled" : "enabled",
},
}
: item,
);
},
);
},
});
const { mutate: disableDns } = clientApi.widget.dnsHole.disable.useMutation({
onSettled: (_, error, { integrationId }) => {
setSummaries((prevSummaries) =>
prevSummaries.map((data) => ({
...data,
summary:
data.integration.id === integrationId && data.summary
? { ...data.summary, status: error ? "enabled" : "disabled" }
: data.summary,
})),
utils.widget.dnsHole.summary.setData(
{
widgetKind: "dnsHoleControls",
integrationIds,
},
(prevData) => {
if (!prevData) return [];
return prevData.map((item) =>
item.integration.id === integrationId && item.summary
? {
...item,
summary: {
...item.summary,
status: error ? "enabled" : "disabled",
},
}
: item,
);
},
);
},
});
const toggleDns = (integrationId: string) => {
const integrationStatus = summaries.find(({ integration }) => integration.id === integrationId);
if (!integrationStatus?.summary?.status) return;
setSummaries((prevSummaries) =>
prevSummaries.map((data) => ({
...data,
summary:
data.integration.id === integrationId && data.summary ? { ...data.summary, status: undefined } : data.summary,
})),
utils.widget.dnsHole.summary.setData(
{
widgetKind: "dnsHoleControls",
integrationIds,
},
(prevData) => {
if (!prevData) return [];
return prevData.map((item) =>
item.integration.id === integrationId && item.summary
? {
...item,
summary: {
...item.summary,
status: undefined,
},
}
: item,
);
},
);
if (integrationStatus.summary.status === "enabled") {
disableDns({ integrationId, duration: 0 });

View File

@@ -7,7 +7,7 @@ import { optionsBuilder } from "../../options";
export const widgetKind = "dnsHoleControls";
export const { definition, componentLoader, serverDataLoader } = createWidgetDefinition(widgetKind, {
export const { definition, componentLoader } = createWidgetDefinition(widgetKind, {
icon: IconDeviceGamepad,
options: optionsBuilder.from((factory) => ({
showToggleAllButtons: factory.switch({
@@ -21,6 +21,4 @@ export const { definition, componentLoader, serverDataLoader } = createWidgetDef
message: (t) => t("widget.dnsHoleControls.error.internalServerError"),
},
},
})
.withServerData(() => import("./serverData"))
.withDynamicImport(() => import("./component"));
}).withDynamicImport(() => import("./component"));

View File

@@ -1,29 +0,0 @@
"use server";
import { api } from "@homarr/api/server";
import { widgetKind } from ".";
import type { WidgetProps } from "../../definition";
export default async function getServerDataAsync({ integrationIds }: WidgetProps<typeof widgetKind>) {
if (integrationIds.length === 0) {
return {
initialData: [],
};
}
try {
const currentDns = await api.widget.dnsHole.summary({
widgetKind,
integrationIds,
});
return {
initialData: currentDns,
};
} catch {
return {
initialData: [],
};
}
}

View File

@@ -1,6 +1,6 @@
"use client";
import { useMemo, useState } from "react";
import { useMemo } from "react";
import type { BoxProps } from "@mantine/core";
import { Avatar, AvatarGroup, Box, Card, Flex, Stack, Text, Tooltip } from "@mantine/core";
import { useElementSize } from "@mantine/hooks";
@@ -20,12 +20,20 @@ import { widgetKind } from ".";
import type { WidgetComponentProps, WidgetProps } from "../../definition";
import { NoIntegrationSelectedError } from "../../errors";
export default function DnsHoleSummaryWidget({
options,
integrationIds,
serverData,
}: WidgetComponentProps<typeof widgetKind>) {
const [summaries, setSummaries] = useState(serverData?.initialData ?? []);
export default function DnsHoleSummaryWidget({ options, integrationIds }: WidgetComponentProps<typeof widgetKind>) {
const [summaries] = clientApi.widget.dnsHole.summary.useSuspenseQuery(
{
widgetKind,
integrationIds,
},
{
refetchOnMount: false,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
retry: false,
},
);
const utils = clientApi.useUtils();
const t = useI18n();
@@ -36,8 +44,21 @@ export default function DnsHoleSummaryWidget({
},
{
onData: (data) => {
setSummaries((prevSummaries) =>
prevSummaries.map((summary) => (summary.integration.id === data.integration.id ? data : summary)),
utils.widget.dnsHole.summary.setData(
{
widgetKind,
integrationIds,
},
(prevData) => {
if (!prevData) {
return undefined;
}
const newData = prevData.map((item) =>
item.integration.id === data.integration.id ? { ...item, summary: data.summary } : item,
);
return newData;
},
);
},
},
@@ -46,17 +67,10 @@ export default function DnsHoleSummaryWidget({
const data = useMemo(
() =>
summaries
.filter(
(
pair,
): pair is {
integration: typeof pair.integration;
timestamp: typeof pair.timestamp;
summary: DnsHoleSummary;
} => pair.summary !== null && Math.abs(dayjs(pair.timestamp).diff()) < 30000,
)
.flatMap(({ summary }) => summary),
[summaries, serverData],
.filter((pair) => Math.abs(dayjs(pair.timestamp).diff()) < 30000)
.flatMap(({ summary }) => summary)
.filter((summary) => summary !== null),
[summaries],
);
if (integrationIds.length === 0) {

View File

@@ -7,7 +7,7 @@ import { optionsBuilder } from "../../options";
export const widgetKind = "dnsHoleSummary";
export const { definition, componentLoader, serverDataLoader } = createWidgetDefinition(widgetKind, {
export const { definition, componentLoader } = createWidgetDefinition(widgetKind, {
icon: IconAd,
options: optionsBuilder.from((factory) => ({
usePiHoleColors: factory.switch({
@@ -28,6 +28,4 @@ export const { definition, componentLoader, serverDataLoader } = createWidgetDef
message: (t) => t("widget.dnsHoleSummary.error.internalServerError"),
},
},
})
.withServerData(() => import("./serverData"))
.withDynamicImport(() => import("./component"));
}).withDynamicImport(() => import("./component"));

View File

@@ -1,29 +0,0 @@
"use server";
import { api } from "@homarr/api/server";
import { widgetKind } from ".";
import type { WidgetProps } from "../../definition";
export default async function getServerDataAsync({ integrationIds }: WidgetProps<typeof widgetKind>) {
if (integrationIds.length === 0) {
return {
initialData: [],
};
}
try {
const currentDns = await api.widget.dnsHole.summary({
widgetKind,
integrationIds,
});
return {
initialData: currentDns,
};
} catch {
return {
initialData: [],
};
}
}