fix: scope polling to widgets, fix retry, guard dates, throttle umami
- Remove global refetchInterval; scope to widget queries via setQueryDefaults - Fix error boundary retry: correct queryKey to [["widget"]], move invalidation to onReset - Add toValidDate() helper in @homarr/common for cache hydration safety - Guard date formatting in media-releases and media-requests against non-Date values - Set umami refetchInterval to 60s via shared umamiQueryOptions constant - Simplify dns-hole mutations to invalidate() instead of manual optimistic setData
This commit is contained in:
11 files changed
+84
-114
No files matched your search
@@ -56,54 +56,11 @@ export default function DnsHoleControlsWidget({
|
||||
});
|
||||
const utils = clientApi.useUtils();
|
||||
|
||||
// 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 }) => {
|
||||
utils.widget.dnsHole.summary.setData(
|
||||
{
|
||||
integrationIds,
|
||||
},
|
||||
(prevData) => {
|
||||
if (!prevData) return [];
|
||||
|
||||
return prevData.map((item) =>
|
||||
item.integration.id === integrationId
|
||||
? {
|
||||
...item,
|
||||
summary: {
|
||||
...item.summary,
|
||||
status: error ? "disabled" : "enabled",
|
||||
},
|
||||
}
|
||||
: item,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
onSettled: () => void utils.widget.dnsHole.summary.invalidate(),
|
||||
});
|
||||
const { mutate: disableDns } = clientApi.widget.dnsHole.disable.useMutation({
|
||||
onSettled: (_, error, { integrationId }) => {
|
||||
utils.widget.dnsHole.summary.setData(
|
||||
{
|
||||
integrationIds,
|
||||
},
|
||||
(prevData) => {
|
||||
if (!prevData) return [];
|
||||
|
||||
return prevData.map((item) =>
|
||||
item.integration.id === integrationId
|
||||
? {
|
||||
...item,
|
||||
summary: {
|
||||
...item.summary,
|
||||
status: error ? "enabled" : "disabled",
|
||||
},
|
||||
}
|
||||
: item,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
onSettled: () => void utils.widget.dnsHole.summary.invalidate(),
|
||||
});
|
||||
const toggleDns = (integrationId: string) => {
|
||||
const integrationStatus = summaries.find(({ integration }) => integration.id === integrationId);
|
||||
|
||||
@@ -6,7 +6,7 @@ import { IconBook, IconCalendar, IconClock, IconStarFilled } from "@tabler/icons
|
||||
|
||||
import type { RouterOutputs } from "@homarr/api";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { getMantineColor } from "@homarr/common";
|
||||
import { getMantineColor, toValidDate } from "@homarr/common";
|
||||
import { getIconUrl } from "@homarr/definitions";
|
||||
import type { MediaRelease } from "@homarr/integrations/types";
|
||||
import { mediaTypeConfigurations } from "@homarr/integrations/types";
|
||||
@@ -39,6 +39,19 @@ interface ItemProps {
|
||||
options: WidgetComponentProps<"mediaReleases">["options"];
|
||||
}
|
||||
|
||||
const formatReleaseDate = (value: unknown, locale: string) => {
|
||||
const date = toValidDate(value);
|
||||
if (!date) return "—";
|
||||
return Intl.DateTimeFormat(locale, {
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: false,
|
||||
}).format(date);
|
||||
};
|
||||
|
||||
const Item = ({ item, options }: ItemProps) => {
|
||||
const locale = useCurrentLocale();
|
||||
const t = useI18n();
|
||||
@@ -93,14 +106,7 @@ const Item = ({ item, options }: ItemProps) => {
|
||||
<Group gap={6} style={{ rowGap: 0 }}>
|
||||
<Info
|
||||
icon={IconCalendar}
|
||||
label={Intl.DateTimeFormat(locale, {
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
hour12: false,
|
||||
}).format(item.releaseDate)}
|
||||
label={formatReleaseDate(item.releaseDate, locale)}
|
||||
/>
|
||||
{length !== undefined && (
|
||||
<>
|
||||
|
||||
@@ -19,6 +19,7 @@ import { IconSearch, IconThumbDown, IconThumbUp } from "@tabler/icons-react";
|
||||
import type { RouterInputs, RouterOutputs } from "@homarr/api";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useRequiredBoard } from "@homarr/boards/context";
|
||||
import { toValidDate } from "@homarr/common";
|
||||
import type { MediaRequestStatus } from "@homarr/integrations/types";
|
||||
import { mediaAvailabilityConfiguration, mediaRequestStatusConfiguration } from "@homarr/integrations/types";
|
||||
import { openMediaRequestSearch } from "@homarr/spotlight";
|
||||
@@ -134,7 +135,7 @@ const MediaRequestCard = ({ request, isTiny, options }: MediaRequestCardProps) =
|
||||
<Group justify="space-between" gap="xs" className="mediaRequests-list-item-top-group">
|
||||
<Group gap="xs">
|
||||
<Text className="mediaRequests-list-item-media-year" size="xs">
|
||||
{request.airDate?.getFullYear() ?? t("toBeDetermined")}
|
||||
{toValidDate(request.airDate)?.getFullYear() ?? t("toBeDetermined")}
|
||||
</Text>
|
||||
{!isTiny && (
|
||||
<Badge
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
import { UmamiEventsContent } from "./umami-events-content";
|
||||
import { UmamiTopPagesContent, UmamiTopReferrersContent } from "./umami-top-list";
|
||||
import { formatTimeFrameLabel, formatXLabel } from "./umami-utils";
|
||||
import { formatTimeFrameLabel, formatXLabel, umamiQueryOptions } from "./umami-utils";
|
||||
import type { TimeFrame } from "./umami-utils";
|
||||
|
||||
interface UmamiContentProps {
|
||||
@@ -40,19 +40,20 @@ export function UmamiContent({
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const tickColor = colorScheme === "dark" ? "#c1c2c5" : "#495057";
|
||||
|
||||
const { data: results = [] } = clientApi.widget.umami.getVisitorStats.useQuery({
|
||||
integrationIds,
|
||||
websiteId,
|
||||
timeFrame,
|
||||
eventName,
|
||||
});
|
||||
const { data: results = [] } = clientApi.widget.umami.getVisitorStats.useQuery(
|
||||
{ integrationIds, websiteId, timeFrame, eventName },
|
||||
umamiQueryOptions,
|
||||
);
|
||||
|
||||
const activeVisitorsInput = { integrationId: integrationIds[0] ?? "", websiteId };
|
||||
const { data: activeVisitors = 0 } = clientApi.widget.umami.getActiveVisitors.useQuery(activeVisitorsInput);
|
||||
const { data: activeVisitors = 0 } = clientApi.widget.umami.getActiveVisitors.useQuery(
|
||||
activeVisitorsInput,
|
||||
umamiQueryOptions,
|
||||
);
|
||||
|
||||
const { data: multiEventSeries } = clientApi.widget.umami.getMultiEventTimeSeries.useQuery(
|
||||
{ integrationId: integrationIds[0] ?? "", websiteId, timeFrame, eventNames: [...eventNames].toSorted() },
|
||||
{ enabled: viewMode === "events" && eventNames.length > 0 },
|
||||
{ enabled: viewMode === "events" && eventNames.length > 0, ...umamiQueryOptions },
|
||||
);
|
||||
|
||||
const multiEventTotal = multiEventSeries
|
||||
|
||||
@@ -7,7 +7,7 @@ import { clientApi } from "@homarr/api/client";
|
||||
import type { UmamiEventSeries } from "@homarr/integrations/types";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
import { EVENT_COLORS, formatXLabel } from "./umami-utils";
|
||||
import { EVENT_COLORS, formatXLabel, umamiQueryOptions } from "./umami-utils";
|
||||
|
||||
interface UmamiEventsContentProps {
|
||||
integrationIds: string[];
|
||||
@@ -28,12 +28,10 @@ export function UmamiEventsContent({
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const tickColor = colorScheme === "dark" ? "#c1c2c5" : "#495057";
|
||||
|
||||
const { data: series = [] } = clientApi.widget.umami.getMultiEventTimeSeries.useQuery({
|
||||
integrationId: integrationIds[0] ?? "",
|
||||
websiteId,
|
||||
timeFrame,
|
||||
eventNames: [...eventNames].toSorted(),
|
||||
});
|
||||
const { data: series = [] } = clientApi.widget.umami.getMultiEventTimeSeries.useQuery(
|
||||
{ integrationId: integrationIds[0] ?? "", websiteId, timeFrame, eventNames: [...eventNames].toSorted() },
|
||||
umamiQueryOptions,
|
||||
);
|
||||
|
||||
if (eventNames.length === 0) {
|
||||
return (
|
||||
|
||||
@@ -6,6 +6,8 @@ import { clientApi } from "@homarr/api/client";
|
||||
import type { UmamiMetricItem } from "@homarr/integrations/types";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
import { umamiQueryOptions } from "./umami-utils";
|
||||
|
||||
interface UmamiTopListProps {
|
||||
integrationIds: string[];
|
||||
websiteId: string;
|
||||
@@ -15,23 +17,19 @@ interface UmamiTopListProps {
|
||||
|
||||
export function UmamiTopPagesContent({ integrationIds, websiteId, timeFrame, limit }: UmamiTopListProps) {
|
||||
const t = useScopedI18n("widget.umami");
|
||||
const { data = [] } = clientApi.widget.umami.getTopPages.useQuery({
|
||||
integrationId: integrationIds[0] ?? "",
|
||||
websiteId,
|
||||
timeFrame,
|
||||
limit,
|
||||
});
|
||||
const { data = [] } = clientApi.widget.umami.getTopPages.useQuery(
|
||||
{ integrationId: integrationIds[0] ?? "", websiteId, timeFrame, limit },
|
||||
umamiQueryOptions,
|
||||
);
|
||||
return <UmamiTopList items={data} heading={t("option.viewMode.option.topPages")} emptyLabel={t("topPages.direct")} />;
|
||||
}
|
||||
|
||||
export function UmamiTopReferrersContent({ integrationIds, websiteId, timeFrame, limit }: UmamiTopListProps) {
|
||||
const t = useScopedI18n("widget.umami");
|
||||
const { data = [] } = clientApi.widget.umami.getTopReferrers.useQuery({
|
||||
integrationId: integrationIds[0] ?? "",
|
||||
websiteId,
|
||||
timeFrame,
|
||||
limit,
|
||||
});
|
||||
const { data = [] } = clientApi.widget.umami.getTopReferrers.useQuery(
|
||||
{ integrationId: integrationIds[0] ?? "", websiteId, timeFrame, limit },
|
||||
umamiQueryOptions,
|
||||
);
|
||||
return (
|
||||
<UmamiTopList
|
||||
items={data}
|
||||
|
||||
@@ -2,6 +2,8 @@ import type { useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
import { timeFrameValues } from "./index";
|
||||
|
||||
export const umamiQueryOptions = { refetchInterval: 60_000 } as const;
|
||||
|
||||
export const EVENT_COLORS = ["blue.5", "orange.5", "green.5", "red.5", "violet.5", "teal.5", "yellow.5", "pink.5"];
|
||||
|
||||
export function formatXLabel(timestamp: string, timeFrame: string): string {
|
||||
|
||||
Reference in new issue
Block a user