Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added ConfigProvider to ensure config data is always available #5465

Merged
merged 6 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 15 additions & 11 deletions frontend/app/cypress/support/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import { ReactRouter6Adapter } from "use-query-params/adapters/react-router-6";
import "./commands";

import "../../src/app/styles/index.css";
import { QueryClientProvider } from "@tanstack/react-query";
import { queryClient } from "../../src/shared/api/rest/client";

// Alternatively you can use CommonJS syntax:
// require('./commands')
Expand Down Expand Up @@ -54,17 +56,19 @@ Cypress.Commands.add("mount", (component, options = {}) => {

const wrapped = (
<React.StrictMode>
<MemoryRouter {...routerProps} basename="/">
<QueryParamProvider
adapter={ReactRouter6Adapter}
options={{
searchStringToObject: queryString.parse,
objectToSearchString: queryString.stringify,
}}
>
{component}
</QueryParamProvider>
</MemoryRouter>
<QueryClientProvider client={queryClient}>
<MemoryRouter {...routerProps} basename="/">
<QueryParamProvider
adapter={ReactRouter6Adapter}
options={{
searchStringToObject: queryString.parse,
objectToSearchString: queryString.stringify,
}}
>
{component}
</QueryParamProvider>
</MemoryRouter>
</QueryClientProvider>
</React.StrictMode>
);

Expand Down
33 changes: 18 additions & 15 deletions frontend/app/src/app/app.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import { ApolloProvider } from "@apollo/client";
import { addCollection } from "@iconify-icon/react";
import mdiIcons from "@iconify-json/mdi/icons.json";
import { QueryClientProvider } from "@tanstack/react-query";
import { Provider } from "jotai";
import { ErrorBoundary } from "react-error-boundary";
import { RouterProvider } from "react-router-dom";
import { Slide, ToastContainer } from "react-toastify";

import { TanStackQueryDevtools } from "@/app/devtools";
import { router } from "@/app/router";
import { AuthProvider } from "@/entities/authentication/ui/useAuth";
import { ConfigProvider } from "@/entities/config/config-provider";
import graphqlClient from "@/shared/api/graphql/graphqlClientApollo";
import { queryClient } from "@/shared/api/rest/client";
import ErrorFallback from "@/shared/components/errors/error-fallback";
import { store } from "@/shared/stores";
import { ApolloProvider } from "@apollo/client";
import { addCollection } from "@iconify-icon/react";
import mdiIcons from "@iconify-json/mdi/icons.json";
import { QueryClientProvider } from "@tanstack/react-query";

import "@/app/styles/index.css";
import "react-toastify/dist/ReactToastify.css";
import { TanStackQueryDevtools } from "@/app/devtools";
import { queryClient } from "@/shared/api/rest/client";

addCollection(mdiIcons);

Expand All @@ -27,15 +28,17 @@ export function App() {
<AuthProvider>
<QueryClientProvider client={queryClient}>
<ApolloProvider client={graphqlClient}>
<ToastContainer
hideProgressBar={true}
transition={Slide}
autoClose={5000}
closeOnClick={false}
newestOnTop
position="bottom-right"
/>
<RouterProvider router={router} />
<ConfigProvider>
<ToastContainer
hideProgressBar={true}
transition={Slide}
autoClose={5000}
closeOnClick={false}
newestOnTop
position="bottom-right"
/>
<RouterProvider router={router} />
</ConfigProvider>
</ApolloProvider>
<TanStackQueryDevtools buttonPosition="bottom-left" />
</QueryClientProvider>
Expand Down
59 changes: 0 additions & 59 deletions frontend/app/src/app/root.tsx

This file was deleted.

5 changes: 1 addition & 4 deletions frontend/app/src/app/router.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Root } from "@/app/root";
import { NODE_OBJECT, PROPOSED_CHANGES_OBJECT } from "@/config/constants";
import { RequireAuth } from "@/entities/authentication/ui/useAuth";
import { constructPathForIpam } from "@/entities/ipam/common/utils";
Expand All @@ -21,9 +20,7 @@ export const router = createBrowserRouter([
objectToSearchString: queryString.stringify,
}}
>
<Root>
<Outlet />
</Root>
<Outlet />
</QueryParamProvider>
),
children: [
Expand Down
44 changes: 0 additions & 44 deletions frontend/app/src/config/config.atom.ts

This file was deleted.

3 changes: 0 additions & 3 deletions frontend/app/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ export const CONFIG = {
branch
? `${INFRAHUB_API_SERVER_URL}/api/schema?branch=${branch}`
: `${INFRAHUB_API_SERVER_URL}/api/schema`,
CONFIG_URL: `${INFRAHUB_API_SERVER_URL}/api/config`,
SEARCH_URL: (query: string, limit: number = 3) =>
`${INFRAHUB_API_SERVER_URL}/api/search/docs?query=${query}&limit=${limit}`,
INFO_URL: `${INFRAHUB_API_SERVER_URL}/api/info`,
Expand All @@ -50,6 +49,4 @@ export const CONFIG = {
FILES_CONTENT_URL: (repositoryId: string, location: string) =>
`${INFRAHUB_API_SERVER_URL}/api/file/${repositoryId}/${encodeURIComponent(location)}`,
STORAGE_DETAILS_URL: (id: string) => `${INFRAHUB_API_SERVER_URL}/api/storage/object/${id}`,
MENU_URL: (branch?: string) =>
`${INFRAHUB_API_SERVER_URL}/api/menu${branch ? `?branch=${branch}` : ""}`,
};
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { INFRAHUB_API_SERVER_URL } from "@/config/config";
import { Provider } from "@/config/config.atom";
import { SSOProvider } from "@/entities/config/types";
import { classNames } from "@/shared/utils/common";
import { Icon } from "@iconify-icon/react";
import { useLocation } from "react-router-dom";

export interface LoginWithSSOButtonsProps {
className?: string;
providers: Array<Provider>;
providers: Array<SSOProvider>;
}

export const LoginWithSSOButtons = ({ className, providers }: LoginWithSSOButtonsProps) => {
Expand All @@ -30,7 +30,7 @@ export const LoginWithSSOButtons = ({ className, providers }: LoginWithSSOButton
export const ProviderButton = ({
provider,
redirectTo = "/",
}: { provider: Provider; redirectTo?: string }) => {
}: { provider: SSOProvider; redirectTo?: string }) => {
return (
<a
className="h-9 px-4 py-2 inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium disabled:opacity-60 disabled:cursor-not-allowed border bg-custom-white shadow-sm hover:bg-gray-100"
Expand Down
7 changes: 3 additions & 4 deletions frontend/app/src/entities/authentication/ui/login.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import { configState } from "@/config/config.atom";
import { LoginWithSSOButtons } from "@/entities/authentication/ui/login-sso-buttons";
import { useAuth } from "@/entities/authentication/ui/useAuth";
import { useConfig } from "@/entities/config/get-config.query";
import { Button } from "@/shared/components/buttons/button-primitive";
import InputField from "@/shared/components/form/fields/input.field";
import PasswordInputField from "@/shared/components/form/fields/password-input.field";
import { isRequired } from "@/shared/components/form/utils/validation";
import { Form, FormSubmit } from "@/shared/components/ui/form";
import { classNames } from "@/shared/utils/common";
import { useAtomValue } from "jotai";
import { useState } from "react";

export const Login = () => {
const config = useAtomValue(configState);
const { data: config } = useConfig();
const [displaySSO, setDisplaySSO] = useState(true);

if (config && config.sso.enabled && config.sso.providers.length > 0) {
if (config && config.sso.enabled && config.sso.providers && config.sso.providers.length > 0) {
return displaySSO ? (
<>
<LoginWithSSOButtons providers={config.sso.providers} className="animate-in fade-in" />
Expand Down
7 changes: 3 additions & 4 deletions frontend/app/src/entities/authentication/ui/useAuth.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { CONFIG } from "@/config/config";
import { configState } from "@/config/config.atom";
import { REFRESH_TOKEN_KEY } from "@/config/constants";
import { ACCESS_TOKEN_KEY } from "@/config/localStorage";
import { useConfig } from "@/entities/config/get-config.query";
import graphqlClient from "@/shared/api/graphql/graphqlClientApollo";
import { fetchUrl } from "@/shared/api/rest/fetch";
import { components } from "@/shared/api/rest/types.generated";
import { ALERT_TYPES, Alert } from "@/shared/components/ui/alert";
import { parseJwt } from "@/shared/utils/common";
import { ObservableQuery } from "@apollo/client";
import { useAtom } from "jotai/index";
import { ReactElement, ReactNode, createContext, useContext, useState } from "react";
import { Navigate, useLocation } from "react-router-dom";
import { toast } from "react-toastify";
Expand Down Expand Up @@ -173,11 +172,11 @@ export function useAuth() {
}

export function RequireAuth({ children }: { children: ReactElement }) {
const [config] = useAtom(configState);
const { data: config } = useConfig();
const { isAuthenticated } = useAuth();
const location = useLocation();

if (isAuthenticated || config?.main?.allow_anonymous_access) return children;
if (isAuthenticated || config.main.allow_anonymous_access) return children;

// Redirect them to the /login page, but save the current location they were
// trying to go to when they were redirected. This allows us to send them
Expand Down
19 changes: 19 additions & 0 deletions frontend/app/src/entities/config/config-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { getConfigQueryOptions } from "@/entities/config/get-config.query";
import ErrorScreen from "@/shared/components/errors/error-screen";
import { InfrahubLoading } from "@/shared/components/loading/infrahub-loading";
import { useQuery } from "@tanstack/react-query";
import React from "react";

export const ConfigProvider = ({ children }: { children: React.ReactNode }) => {
const { isPending, error } = useQuery(getConfigQueryOptions());

if (isPending) {
return <InfrahubLoading>Loading config...</InfrahubLoading>;
}

if (error) {
return <ErrorScreen message={error.message} />;
}

return children;
};
13 changes: 13 additions & 0 deletions frontend/app/src/entities/config/get-config.query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { getConfig } from "@/entities/config/get-config";
import { queryOptions, useSuspenseQuery } from "@tanstack/react-query";

export const getConfigQueryOptions = () => {
return queryOptions({
queryKey: ["config"],
queryFn: getConfig,
});
};

export const useConfig = () => {
return useSuspenseQuery(getConfigQueryOptions());
};
12 changes: 12 additions & 0 deletions frontend/app/src/entities/config/get-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ConfigAPI } from "@/entities/config/types";
import { apiClient } from "@/shared/api/rest/client";

export type GetConfig = () => Promise<ConfigAPI>;

export const getConfig: GetConfig = async () => {
const { data, error } = await apiClient.GET("/api/config");

if (error) throw error;

return data;
};
5 changes: 5 additions & 0 deletions frontend/app/src/entities/config/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { components } from "@/shared/api/rest/types.generated";

export type ConfigAPI = components["schemas"]["ConfigAPI"];

export type SSOProvider = components["schemas"]["SSOProviderInfo"];
6 changes: 3 additions & 3 deletions frontend/app/src/entities/permission/utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { configState } from "@/config/config.atom";
import { getConfigQueryOptions } from "@/entities/config/get-config.query";
import {
Permission,
PermissionAction,
PermissionData,
PermissionDecision,
PermissionDecisionData,
} from "@/entities/permission/types";
import { store } from "@/shared/stores";
import { queryClient } from "@/shared/api/rest/client";
import { warnUnexpectedType } from "@/shared/utils/common";

const getMessage = (action: string, decision?: PermissionDecisionData): string => {
Expand All @@ -31,7 +31,7 @@ const getMessage = (action: string, decision?: PermissionDecisionData): string =
export function getPermission(permission?: Array<{ node: PermissionData }>): Permission {
if (!Array.isArray(permission)) return PERMISSION_ALLOW_ALL;

const config = store.get(configState);
const config = queryClient.getQueryData(getConfigQueryOptions().queryKey);

const createPermissionAction = (action: PermissionAction): PermissionDecision => {
if (action === "view" && config?.main.allow_anonymous_access) return { isAllowed: true };
Expand Down
Loading
Loading