Skip to content

Commit

Permalink
Show 'health check' for backend URL in settings page (#376)
Browse files Browse the repository at this point in the history
Also improves the behavior of when backend URLs change. It's still not
perfect though.
  • Loading branch information
chrisbenincasa authored Apr 25, 2024
1 parent 79ffd87 commit d12da0a
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 28 deletions.
2 changes: 2 additions & 0 deletions types/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ export const VersionApiResponseSchema = z.object({
nodejs: z.string(),
});

export type VersionApiResponse = z.infer<typeof VersionApiResponseSchema>;

export const BaseErrorSchema = z.object({
message: z.string(),
});
28 changes: 22 additions & 6 deletions web/src/components/TunarrApiContext.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { ReactNode, createContext, useEffect, useState } from 'react';
import { ReactNode, createContext, useEffect } from 'react';
import { createApiClient } from '../external/api';
import useStore from '../store/index.ts';
import { useSettings } from '../store/settings/selectors';
import { QueryClient } from '@tanstack/react-query';
import { isUndefined } from 'lodash-es';

// HACK ALERT
// Read zustand state out-of-band here (i.e. not in a hook) because we
Expand All @@ -21,17 +23,31 @@ export const getApiClient = () => apiClient;

export const TunarrApiContext = createContext(apiClient);

export function TunarrApiProvider({ children }: { children: ReactNode }) {
export function TunarrApiProvider({
children,
queryClient,
}: {
children: ReactNode;
queryClient: QueryClient;
}) {
const { backendUri } = useSettings();
const [api, setApi] = useState(apiClient);

useEffect(() => {
apiClient = createApiClient(backendUri);
setApi(apiClient);
// Only do this if something actually changed
if (
(backendUri.length === 0 && !isUndefined(apiClient.baseURL)) ||
(backendUri.length > 0 && isUndefined(apiClient.baseURL))
) {
apiClient = createApiClient(backendUri);
}
}, [backendUri]);

useEffect(() => {
queryClient.invalidateQueries().catch(console.warn);
}, [backendUri, queryClient]);

return (
<TunarrApiContext.Provider value={api}>
<TunarrApiContext.Provider value={apiClient}>
{children}
</TunarrApiContext.Provider>
);
Expand Down
13 changes: 10 additions & 3 deletions web/src/hooks/useApiQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import {
UseQueryResult,
useQuery,
} from '@tanstack/react-query';
import { getApiClient } from '../components/TunarrApiContext';
import { ApiClient } from '../external/api';
import { useTunarrApi } from './useTunarrApi';

export function useApiQuery<
TQueryFnData = unknown,
Expand All @@ -27,11 +27,18 @@ export function useApiQuery<
},
queryClient?: QueryClient,
): UseQueryResult<TData, TError> {
const apiClient = useTunarrApi();
// NOTE that this query also depends on the backendUrl used to
// create the API client, but we explicitly don't include it in the
// queryKey here because:
// 1. it makes the types super unwieldy
// 2. we do a mass cache invalidation in the tunarr API context when
// the backend URL changes
// 3. it keeps query keys simple for when we have to do more fine-grained
// invalidation (e.g. post-mutates)
return useQuery(
{
...options,
queryFn: (args) => options.queryFn(apiClient, args),
queryFn: (args) => options.queryFn(getApiClient(), args),
},
queryClient,
);
Expand Down
15 changes: 0 additions & 15 deletions web/src/hooks/useTunarrApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,5 @@ import { useContext } from 'react';
import { TunarrApiContext } from '../components/TunarrApiContext';

export const useTunarrApi = () => {
// const { backendUri } = useSettings();
// const [api, setApi] = useState(createApiClient(backendUri));
// const queryClient = useQueryClient();

// useEffect(() => {
// setApi(createApiClient(backendUri));
// // We have to reset everything when the backend URL changes!
// queryClient.resetQueries().catch(console.warn);
// }, [backendUri, queryClient]);

// return api;
return useContext(TunarrApiContext);
};

// export const useWithTunarrApi = () => {
// const useTunarrApi()
// }
12 changes: 10 additions & 2 deletions web/src/hooks/useVersion.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import { UseQueryOptions } from '@tanstack/react-query';
import { useApiQuery } from './useApiQuery.ts';
import { VersionApiResponse } from '@tunarr/types/api';

export const useVersion = () => {
export const useVersion = (
extraOpts: Omit<
UseQueryOptions<VersionApiResponse>,
'queryKey' | 'queryFn'
> = {},
) => {
return useApiQuery({
queryKey: ['version'],
queryFn: (apiClient) => {
return apiClient.getServerVersions();
},
staleTime: 30 * 1000,
...extraOpts,
staleTime: extraOpts.staleTime ?? 30 * 1000,
});
};
2 changes: 1 addition & 1 deletion web/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const queryClient = new QueryClient({ queryCache });

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<TunarrApiProvider>
<TunarrApiProvider queryClient={queryClient}>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DndProvider backend={HTML5Backend}>
<QueryClientProvider client={queryClient}>
Expand Down
30 changes: 29 additions & 1 deletion web/src/pages/settings/GeneralSettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,19 @@ import Button from '@mui/material/Button';
import { useSettings } from '../../store/settings/selectors.ts';
import { Controller, useForm } from 'react-hook-form';
import { attempt, isEmpty, isError } from 'lodash-es';
import { Box, Divider, Snackbar, TextField, Typography } from '@mui/material';
import {
Box,
Divider,
InputAdornment,
Snackbar,
TextField,
Typography,
} from '@mui/material';
import { setBackendUri } from '../../store/settings/actions.ts';
import { useState } from 'react';
import { useVersion } from '../../hooks/useVersion.ts';
import { RotatingLoopIcon } from '../../components/base/LoadingIcon.tsx';
import { CloudDoneOutlined, CloudOff } from '@mui/icons-material';

type GeneralSettingsForm = {
backendUri: string;
Expand All @@ -19,6 +29,11 @@ function isValidUrl(url: string) {
export default function GeneralSettingsPage() {
const settings = useSettings();
const [snackStatus, setSnackStatus] = useState(false);
const versionInfo = useVersion({
retry: 0,
});

const { isLoading, isError } = versionInfo;

const { control, handleSubmit } = useForm<GeneralSettingsForm>({
reValidateMode: 'onBlur',
Expand Down Expand Up @@ -59,6 +74,19 @@ export default function GeneralSettingsPage() {
<TextField
fullWidth
label="Tunarr Backend URL"
InputProps={{
endAdornment: (
<InputAdornment position="end">
{isLoading ? (
<RotatingLoopIcon />
) : !isError ? (
<CloudDoneOutlined color="success" />
) : (
<CloudOff color="error" />
)}
</InputAdornment>
),
}}
{...field}
helperText={
error?.type === 'isValidUrl'
Expand Down

0 comments on commit d12da0a

Please sign in to comment.