diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index 6fc41300c..3485d47bf 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -16,6 +16,7 @@ import { import { AuthContext } from "./contexts/authContext"; import { autoLogin } from "./controllers/API"; import { useGetHealthQuery } from "./controllers/API/queries/health"; +import { useGetGlobalVariables } from "./controllers/API/queries/variables"; import { useGetVersionQuery } from "./controllers/API/queries/version"; import { setupAxiosDefaults } from "./controllers/API/utils"; import useTrackLastVisitedPath from "./hooks/use-track-last-visited-path"; @@ -42,6 +43,8 @@ export default function App() { const isLoadingFolders = useFolderStore((state) => state.isLoadingFolders); + const { mutate: mutateGetGlobalVariables } = useGetGlobalVariables(); + const { data: healthData, isFetching: fetchingHealth, @@ -66,6 +69,7 @@ export default function App() { if (user && user["access_token"]) { user["refresh_token"] = "auto"; login(user["access_token"], "auto"); + mutateGetGlobalVariables(); setUserData(user); setAutoLogin(true); fetchAllData(); @@ -99,6 +103,7 @@ export default function App() { */ return () => abortController.abort(); }, []); + const fetchAllData = async () => { setTimeout(async () => { await Promise.all([refreshStars(), fetchData()]); diff --git a/src/frontend/src/components/addNewVariableButtonComponent/addNewVariableButton.tsx b/src/frontend/src/components/addNewVariableButtonComponent/addNewVariableButton.tsx index 4dcda2deb..a2aff2713 100644 --- a/src/frontend/src/components/addNewVariableButtonComponent/addNewVariableButton.tsx +++ b/src/frontend/src/components/addNewVariableButtonComponent/addNewVariableButton.tsx @@ -1,5 +1,5 @@ +import { usePostGlobalVariables } from "@/controllers/API/queries/variables"; import { useState } from "react"; -import { registerGlobalVariable } from "../../controllers/API"; import BaseModal from "../../modals/baseModal"; import useAlertStore from "../../stores/alertStore"; import { useGlobalVariablesStore } from "../../stores/globalVariablesStore/globalVariables"; @@ -28,6 +28,7 @@ export default function AddNewVariableButton({ const [open, setOpen] = useState(false); const setErrorData = useAlertStore((state) => state.setErrorData); const componentFields = useTypesStore((state) => state.ComponentFields); + const unavaliableFields = new Set( Object.keys( useGlobalVariablesStore((state) => state.unavaliableFields) ?? {}, @@ -46,6 +47,9 @@ export default function AddNewVariableButton({ (state) => state.addGlobalVariable, ); + const { mutate: mutateAddGlobalVariable } = usePostGlobalVariables(); + const setSuccessData = useAlertStore((state) => state.setSuccessData); + function handleSaveVariable() { let data: { name: string; @@ -58,8 +62,9 @@ export default function AddNewVariableButton({ value, default_fields: fields, }; - registerGlobalVariable(data) - .then((res) => { + + mutateAddGlobalVariable(data, { + onSuccess: (res) => { const { name, id, type } = res.data; addGlobalVariable(name, id, type, fields); setKey(""); @@ -67,8 +72,12 @@ export default function AddNewVariableButton({ setType(""); setFields([]); setOpen(false); - }) - .catch((error) => { + + setSuccessData({ + title: `Variable ${name} created successfully`, + }); + }, + onError: (error) => { let responseError = error as ResponseErrorDetailAPI; setErrorData({ title: "Error creating variable", @@ -77,8 +86,10 @@ export default function AddNewVariableButton({ "An unexpected error occurred while adding a new variable. Please try again.", ], }); - }); + }, + }); } + return ( state.setErrorData); + const { mutate: mutateDeleteGlobalVariable } = useDeleteGlobalVariables(); + useEffect(() => { if (data && globalVariablesEntries) if (data.load_from_db && !globalVariablesEntries.includes(data.value)) { @@ -38,19 +39,23 @@ export default function InputGlobalComponent({ async function handleDelete(key: string) { const id = getVariableId(key); if (id !== undefined) { - await deleteGlobalVariable(id) - .then(() => { - removeGlobalVariable(key); - if (data?.value === key && data?.load_from_db) { - onChange("", false); - } - }) - .catch(() => { - setErrorData({ - title: "Error deleting variable", - list: [cn("ID not found for variable: ", key)], - }); - }); + mutateDeleteGlobalVariable( + { id }, + { + onSuccess: () => { + removeGlobalVariable(key); + if (data?.value === key && data?.load_from_db) { + onChange("", false); + } + }, + onError: () => { + setErrorData({ + title: "Error deleting variable", + list: [cn("ID not found for variable: ", key)], + }); + }, + }, + ); } else { setErrorData({ title: "Error deleting variable", diff --git a/src/frontend/src/contexts/authContext.tsx b/src/frontend/src/contexts/authContext.tsx index 3521fe8b6..907ca0fd6 100644 --- a/src/frontend/src/contexts/authContext.tsx +++ b/src/frontend/src/contexts/authContext.tsx @@ -8,14 +8,9 @@ import useFlowsManagerStore from "@/stores/flowsManagerStore"; import { createContext, useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import Cookies from "universal-cookie"; -import { - getGlobalVariables, - getLoggedUser, - requestLogout, -} from "../controllers/API"; +import { getLoggedUser, requestLogout } from "../controllers/API"; import useAlertStore from "../stores/alertStore"; import { useFolderStore } from "../stores/foldersStore"; -import { useGlobalVariablesStore } from "../stores/globalVariablesStore/globalVariables"; import { useStoreStore } from "../stores/storeStore"; import { Users } from "../types/api"; import { AuthContextType } from "../types/contexts/auth"; @@ -48,9 +43,7 @@ export function AuthProvider({ children }): React.ReactElement { ); const getFoldersApi = useFolderStore((state) => state.getFoldersApi); - const setGlobalVariables = useGlobalVariablesStore( - (state) => state.setGlobalVariables, - ); + const checkHasStore = useStoreStore((state) => state.checkHasStore); const fetchApiData = useStoreStore((state) => state.fetchApiData); const setAllFlows = useFlowsManagerStore((state) => state.setAllFlows); @@ -77,8 +70,7 @@ export function AuthProvider({ children }): React.ReactElement { const isSuperUser = user!.is_superuser; useAuthStore.getState().setIsAdmin(isSuperUser); getFoldersApi(true, true); - const res = await getGlobalVariables(); - setGlobalVariables(res); + checkHasStore(); fetchApiData(); }) diff --git a/src/frontend/src/controllers/API/helpers/constants.ts b/src/frontend/src/controllers/API/helpers/constants.ts index 2212023f0..4f54d7a78 100644 --- a/src/frontend/src/controllers/API/helpers/constants.ts +++ b/src/frontend/src/controllers/API/helpers/constants.ts @@ -17,6 +17,7 @@ export const URLs = { CUSTOM_COMPONENT: `custom_component`, FLOWS: `flows`, FOLDERS: `folders`, + VARIABLES: `variables`, } as const; export function getURL(key: keyof typeof URLs, params: any = {}) { diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts index 2b6667099..52ca5ba55 100644 --- a/src/frontend/src/controllers/API/index.ts +++ b/src/frontend/src/controllers/API/index.ts @@ -818,53 +818,6 @@ export async function requestLogout() { } } -export async function getGlobalVariables(): Promise<{ - [key: string]: { id: string; type: string; default_fields: string[] }; -}> { - const globalVariables = {}; - (await api.get(`${BASE_URL_API}variables/`))?.data?.forEach((element) => { - globalVariables[element.name] = { - id: element.id, - type: element.type, - default_fields: element.default_fields, - }; - }); - return globalVariables; -} - -export async function registerGlobalVariable({ - name, - value, - type, - default_fields = [], -}: { - name: string; - value: string; - type?: string; - default_fields?: string[]; -}): Promise> { - try { - const response = await api.post(`${BASE_URL_API}variables/`, { - name, - value, - type, - default_fields: default_fields, - }); - return response; - } catch (error) { - throw error; - } -} - -export async function deleteGlobalVariable(id: string) { - try { - const response = await api.delete(`${BASE_URL_API}variables/${id}`); - return response; - } catch (error) { - throw error; - } -} - export async function updateGlobalVariable( name: string, value: string, diff --git a/src/frontend/src/controllers/API/queries/variables/index.ts b/src/frontend/src/controllers/API/queries/variables/index.ts new file mode 100644 index 000000000..47bccb23c --- /dev/null +++ b/src/frontend/src/controllers/API/queries/variables/index.ts @@ -0,0 +1,4 @@ +export * from "./use-delete-global-variables"; +export * from "./use-get-global-variables"; +export * from "./use-patch-global-variables"; +export * from "./use-post-global-variables"; diff --git a/src/frontend/src/controllers/API/queries/variables/use-delete-global-variables.ts b/src/frontend/src/controllers/API/queries/variables/use-delete-global-variables.ts new file mode 100644 index 000000000..510956bee --- /dev/null +++ b/src/frontend/src/controllers/API/queries/variables/use-delete-global-variables.ts @@ -0,0 +1,31 @@ +import { useMutationFunctionType } from "@/types/api"; +import { UseMutationResult } from "@tanstack/react-query"; +import { api } from "../../api"; +import { getURL } from "../../helpers/constants"; +import { UseRequestProcessor } from "../../services/request-processor"; + +interface DeleteGlobalVariablesParams { + id: string | undefined; +} + +export const useDeleteGlobalVariables: useMutationFunctionType< + undefined, + DeleteGlobalVariablesParams +> = (options?) => { + const { mutate } = UseRequestProcessor(); + + const deleteGlobalVariables = async ({ + id, + }: DeleteGlobalVariablesParams): Promise => { + const res = await api.delete(`${getURL("VARIABLES")}/${id}`); + return res.data; + }; + + const mutation: UseMutationResult< + DeleteGlobalVariablesParams, + any, + DeleteGlobalVariablesParams + > = mutate(["useDeleteGlobalVariables"], deleteGlobalVariables, options); + + return mutation; +}; diff --git a/src/frontend/src/controllers/API/queries/variables/use-get-global-variables.ts b/src/frontend/src/controllers/API/queries/variables/use-get-global-variables.ts new file mode 100644 index 000000000..5b5df35cf --- /dev/null +++ b/src/frontend/src/controllers/API/queries/variables/use-get-global-variables.ts @@ -0,0 +1,54 @@ +import { useGlobalVariablesStore } from "@/stores/globalVariablesStore/globalVariables"; +import { useMutationFunctionType } from "@/types/api"; +import { UseMutationResult } from "@tanstack/react-query"; +import { api } from "../../api"; +import { getURL } from "../../helpers/constants"; +import { UseRequestProcessor } from "../../services/request-processor"; + +type GlobalVariable = { + id: string; + type: string; + default_fields: string[]; + name: string; +}; + +export const useGetGlobalVariables: useMutationFunctionType = ( + options?, +) => { + const { mutate } = UseRequestProcessor(); + + const setGlobalVariables = useGlobalVariablesStore( + (state) => state.setGlobalVariables, + ); + + const getGlobalVariables = async (): Promise<[GlobalVariable]> => { + const res = await api.get(`${getURL("VARIABLES")}/`); + return res.data; + }; + + const getGlobalVariablesFn = async (): Promise<{ + [key: string]: GlobalVariable; + }> => { + const data = await getGlobalVariables(); + const globalVariables = {}; + + data?.forEach((element) => { + globalVariables[element.name] = { + id: element.id, + type: element.type, + default_fields: element.default_fields, + }; + }); + + setGlobalVariables(globalVariables); + return globalVariables; + }; + + const mutation: UseMutationResult = mutate( + ["useGetGlobalVariables"], + getGlobalVariablesFn, + options, + ); + + return mutation; +}; diff --git a/src/frontend/src/controllers/API/queries/variables/use-patch-global-variables.ts b/src/frontend/src/controllers/API/queries/variables/use-patch-global-variables.ts new file mode 100644 index 000000000..bc2eac5e1 --- /dev/null +++ b/src/frontend/src/controllers/API/queries/variables/use-patch-global-variables.ts @@ -0,0 +1,38 @@ +import { changeUser, useMutationFunctionType } from "@/types/api"; +import { UseMutationResult } from "@tanstack/react-query"; +import { api } from "../../api"; +import { getURL } from "../../helpers/constants"; +import { UseRequestProcessor } from "../../services/request-processor"; + +interface PatchGlobalVariablesParams { + name: string; + value: string; + id: string; +} + +export const usePatchGlobalVariables: useMutationFunctionType< + undefined, + PatchGlobalVariablesParams +> = (options?) => { + const { mutate } = UseRequestProcessor(); + + async function patchGlobalVariables({ + name, + value, + id, + }: PatchGlobalVariablesParams): Promise { + const res = await api.patch(`${getURL("VARIABLES")}/${id}`, { + name, + value, + }); + return res.data; + } + + const mutation: UseMutationResult< + PatchGlobalVariablesParams, + any, + PatchGlobalVariablesParams + > = mutate(["usePatchGlobalVariables"], patchGlobalVariables, options); + + return mutation; +}; diff --git a/src/frontend/src/controllers/API/queries/variables/use-post-global-variables.ts b/src/frontend/src/controllers/API/queries/variables/use-post-global-variables.ts new file mode 100644 index 000000000..937954acc --- /dev/null +++ b/src/frontend/src/controllers/API/queries/variables/use-post-global-variables.ts @@ -0,0 +1,40 @@ +import { useMutationFunctionType } from "@/types/api"; +import { UseMutationResult } from "@tanstack/react-query"; +import { AxiosResponse } from "axios"; +import { api } from "../../api"; +import { getURL } from "../../helpers/constants"; +import { UseRequestProcessor } from "../../services/request-processor"; + +interface PostGlobalVariablesParams { + name: string; + value: string; + type?: string; + default_fields?: string[]; +} + +export const usePostGlobalVariables: useMutationFunctionType< + undefined, + PostGlobalVariablesParams +> = (options?) => { + const { mutate } = UseRequestProcessor(); + + const postGlobalVariablesFunction = async ({ + name, + value, + type, + default_fields = [], + }): Promise> => { + const res = await api.post(`${getURL("VARIABLES")}`, { + name, + value, + type, + default_fields: default_fields, + }); + return res; + }; + + const mutation: UseMutationResult = + mutate(["usePostGlobalVariables"], postGlobalVariablesFunction, options); + + return mutation; +}; diff --git a/src/frontend/src/pages/AdminPage/LoginPage/index.tsx b/src/frontend/src/pages/AdminPage/LoginPage/index.tsx index c6ce32c30..e4cccd574 100644 --- a/src/frontend/src/pages/AdminPage/LoginPage/index.tsx +++ b/src/frontend/src/pages/AdminPage/LoginPage/index.tsx @@ -1,3 +1,4 @@ +import { useGetGlobalVariables } from "@/controllers/API/queries/variables"; import { useContext, useState } from "react"; import { useNavigate } from "react-router-dom"; import { Button } from "../../../components/ui/button"; @@ -21,6 +22,8 @@ export default function LoginAdminPage() { const { login } = useContext(AuthContext); const setLoading = useAlertStore((state) => state.setLoading); + const { mutate: mutateGetGlobalVariables } = useGetGlobalVariables(); + const { password, username } = inputState; const setErrorData = useAlertStore((state) => state.setErrorData); function handleInput({ @@ -41,6 +44,8 @@ export default function LoginAdminPage() { setLoading(true); login(user.access_token, "login"); + mutateGetGlobalVariables(); + navigate("/admin/"); }) .catch((error) => { diff --git a/src/frontend/src/pages/SettingsPage/pages/GlobalVariablesPage/index.tsx b/src/frontend/src/pages/SettingsPage/pages/GlobalVariablesPage/index.tsx index 6f0dac836..51d3fa3f9 100644 --- a/src/frontend/src/pages/SettingsPage/pages/GlobalVariablesPage/index.tsx +++ b/src/frontend/src/pages/SettingsPage/pages/GlobalVariablesPage/index.tsx @@ -1,6 +1,7 @@ import IconComponent from "../../../../components/genericIconComponent"; import { Button } from "../../../../components/ui/button"; +import { useDeleteGlobalVariables } from "@/controllers/API/queries/variables"; import { ColDef, ColGroupDef, SelectionChangedEvent } from "ag-grid-community"; import { useEffect, useState } from "react"; import AddNewVariableButton from "../../../../components/addNewVariableButtonComponent/addNewVariableButton"; @@ -8,7 +9,6 @@ import Dropdown from "../../../../components/dropdownComponent"; import ForwardedIconComponent from "../../../../components/genericIconComponent"; import TableComponent from "../../../../components/tableComponent"; import { Badge } from "../../../../components/ui/badge"; -import { deleteGlobalVariable } from "../../../../controllers/API"; import useAlertStore from "../../../../stores/alertStore"; import { useGlobalVariablesStore } from "../../../../stores/globalVariablesStore/globalVariables"; @@ -108,23 +108,26 @@ export default function GlobalVariablesPage() { const [selectedRows, setSelectedRows] = useState([]); + const { mutate: mutateDeleteGlobalVariable } = useDeleteGlobalVariables(); + async function removeVariables() { - const deleteGlobalVariablesPromise = selectedRows.map(async (row) => { + selectedRows.map(async (row) => { const id = getVariableId(row); - const deleteGlobalVariables = deleteGlobalVariable(id!); - await deleteGlobalVariables; + mutateDeleteGlobalVariable( + { id }, + { + onSuccess: () => { + removeGlobalVariable(row); + }, + onError: () => { + setErrorData({ + title: `Error deleting variable`, + list: [`ID not found for variable: ${row}`], + }); + }, + }, + ); }); - Promise.all(deleteGlobalVariablesPromise) - .then(() => { - selectedRows.forEach((row) => { - removeGlobalVariable(row); - }); - }) - .catch(() => { - setErrorData({ - title: `Error deleting global variables.`, - }); - }); } return (