diff --git a/src/App.js b/src/App.js index 765e9f765515185fbf85202a738f12a00a0f99de..d43b4f64acfbba8ed1341b95f9149824fc849d60 100644 --- a/src/App.js +++ b/src/App.js @@ -8,7 +8,6 @@ import { IOCListView } from "./views/IOC/IOCListView"; import NavigationMenu from "./components/navigation/NavigationMenu"; import { IOCDetailsAccessControl } from "./views/IOC/IOCDetailsAccessControl"; import { JobDetailsAccessControl } from "./views/jobs/JobDetailsAccessControl"; -import { LegacyApiProvider } from "./api/SwaggerApi"; import { UserProvider } from "./api/UserProvider"; import { HostListView } from "./views/host/HostListView"; import { HostDetailsAccessControl } from "./views/host/HostDetailsAccessControl"; @@ -46,95 +45,93 @@ function App() { <ThemeProvider theme={theme}> <CssBaseline /> <DeployAPIProvider> - <LegacyApiProvider> - <UserProvider> - <TokenRenew /> - <NotificationProvider> - <NavigationMenu> - <Routes> - <Route - path="/" - element={<Navigate to="/iocs" />} - exact - /> - <Route - path="/records" - element={<RecordListView />} - exact - /> - <Route - path="/records/:name" - element={<RecordDetailsView />} - exact - /> - <Route - path="/iocs/create" - element={<CreateIOCAccessControl />} - exact - /> - <Route - path="/iocs/:id" - element={<IOCDetailsAccessControl exact />} - /> - <Route - path="/iocs" - element={<IOCListView />} - exact - /> - <Route - path="/jobs" - element={<JobLogAccessControl />} - exact - /> - <Route - path="/jobs/:id" - element={<JobDetailsAccessControl exact />} - /> - <Route - path="/hosts/:id" - element={<HostDetailsAccessControl />} - exact - /> - <Route - path="/hosts" - element={<HostListView />} - exact - /> - <Route - path="/statistics" - element={<StatisticsView />} - exact - /> - <Route - path="/help" - element={<HelpView />} - exact - /> - <Route - path="/login" - element={<LoginView />} - exact - /> - <Route - path="/error-test" - element={<TestErrorView />} - exact - /> - <Route - path="/user/:userName" - element={<UserDetailsAccessControl />} - exact - /> - <Route - path="*" - element={<NotFoundView />} - exact - /> - </Routes> - </NavigationMenu> - </NotificationProvider> - </UserProvider> - </LegacyApiProvider> + <UserProvider> + <TokenRenew /> + <NotificationProvider> + <NavigationMenu> + <Routes> + <Route + path="/" + element={<Navigate to="/iocs" />} + exact + /> + <Route + path="/records" + element={<RecordListView />} + exact + /> + <Route + path="/records/:name" + element={<RecordDetailsView />} + exact + /> + <Route + path="/iocs/create" + element={<CreateIOCAccessControl />} + exact + /> + <Route + path="/iocs/:id" + element={<IOCDetailsAccessControl exact />} + /> + <Route + path="/iocs" + element={<IOCListView />} + exact + /> + <Route + path="/jobs" + element={<JobLogAccessControl />} + exact + /> + <Route + path="/jobs/:id" + element={<JobDetailsAccessControl exact />} + /> + <Route + path="/hosts/:id" + element={<HostDetailsAccessControl />} + exact + /> + <Route + path="/hosts" + element={<HostListView />} + exact + /> + <Route + path="/statistics" + element={<StatisticsView />} + exact + /> + <Route + path="/help" + element={<HelpView />} + exact + /> + <Route + path="/login" + element={<LoginView />} + exact + /> + <Route + path="/error-test" + element={<TestErrorView />} + exact + /> + <Route + path="/user/:userName" + element={<UserDetailsAccessControl />} + exact + /> + <Route + path="*" + element={<NotFoundView />} + exact + /> + </Routes> + </NavigationMenu> + </NotificationProvider> + </UserProvider> </DeployAPIProvider> </ThemeProvider> </StyledEngineProvider> diff --git a/src/api/APIProvider.spec.js b/src/api/APIProvider.spec.js index 0e9bd2de12151a2b19c7343d846a4c31ebb2408a..e89cd6b306b83debc65e103f101d83d650f74e8f 100644 --- a/src/api/APIProvider.spec.js +++ b/src/api/APIProvider.spec.js @@ -1,5 +1,5 @@ import React, { useContext } from "react"; -import { apiContext, LegacyApiProvider, useAPI } from "./SwaggerApi"; +import { DeployAPIProvider, apiContext } from "./DeployApi"; import { SnackbarProvider } from "notistack"; function AppHarness({ children }) { @@ -26,27 +26,16 @@ function DisplayAPISpecification({ api }) { } context("API", () => { - describe("useAPI", () => { - it("creates the SwaggerClient", () => { - const Glue = () => { - const [api] = useAPI(); - return <DisplayAPISpecification api={api} />; - }; - mountIntoHarness(<Glue />); - checkAPIDisplayed(); - }); - }); - - describe("LegacyApiProvider", () => { + describe("ApiProvider", () => { it("makes the API available via context", () => { const Glue = () => { const api = useContext(apiContext); return <DisplayAPISpecification api={api} />; }; mountIntoHarness( - <LegacyApiProvider> + <DeployAPIProvider> <Glue /> - </LegacyApiProvider> + </DeployAPIProvider> ); checkAPIDisplayed(); }); diff --git a/src/api/SwaggerApi.js b/src/api/SwaggerApi.js deleted file mode 100644 index ff25b530964b5f4bf1550c3edb39edeb72855a06..0000000000000000000000000000000000000000 --- a/src/api/SwaggerApi.js +++ /dev/null @@ -1,288 +0,0 @@ -/* eslint-disable react-hooks/exhaustive-deps */ -/** - * SwaggerApi - * Swagger API, fetching data - * and providing apiContext and userContext - */ -import React, { useCallback, useRef } from "react"; -import SwaggerClient from "swagger-client"; -import { createContext, useContext, useEffect, useState } from "react"; -import { CircularProgress } from "@mui/material"; -import { useCustomSnackbar } from "../components/common/snackbar/Snackbar"; -import { userContext } from "@ess-ics/ce-ui-common"; - -const createSwaggerClient = async () => { - const swaggerOptions = { - url: `${window.SERVER_ADDRESS}${window.API_BASE_ENDPOINT}`, - requestInterceptor: (req) => { - // if (user) req.headers["Authorization"] = `Bearer ${user.token}`; - req.headers["Content-Type"] = "application/json"; - req.headers.accept = "application/json"; - window.req = req; - return req; - }, - responseInterceptor: (res) => { - return res; - } - }; - - const swagger = await SwaggerClient(swaggerOptions); - // update the server url to be our intended server - swagger.spec.servers[0].url = `${window.SERVER_ADDRESS}`; - window.swagger = swagger; - return swagger; -}; - -export function useAPI() { - const create = useCallback(createSwaggerClient, []); - return useAsync({ fcn: create }); -} - -export const apiContext = createContext(null); - -export function LegacyApiProvider({ children }) { - const [api] = useAPI(); - - return api ? ( - <apiContext.Provider value={api}>{children}</apiContext.Provider> - ) : ( - <CircularProgress /> - ); -} - -export function useAPIErrorHandler(onError) { - const { logout } = useContext(userContext); - const showError = useCustomSnackbar(); - - function errorHandler(e) { - const { response, message } = e; - const { obj, status } = response ?? { obj: {}, status: "" }; - - // Always log out if 401 - if (status === 401) { - logout(); - } - - if (onError) { - const errorNotHandled = onError(obj?.description ?? message, status); - if (errorNotHandled) { - showError(obj?.description ?? message); - } - } else { - showError(obj?.description ?? message); - } - } - - return useCallback(errorHandler, [logout]); -} - -export function useQueuedResponse({ - init = null, - onError = null, - initLoading = false -}) { - const queueRef = useRef([]); - const busyRef = useRef(false); - const [response, setResponse] = useState(init); - const [loading, setLoading] = useState(initLoading); - const errorHandler = useAPIErrorHandler(onError); - const MAX_QUEUE_LENGTH = 10; - - const processQueue = useCallback(async () => { - if (busyRef.current) { - return; - } - - busyRef.current = true; - setLoading(true); - - let responses = []; - const queue = queueRef.current; - while (responses.length !== queue.length) { - responses = await Promise.allSettled(queue); - } - - queueRef.current = []; - busyRef.current = false; - - const { status, value, reason } = responses.pop(); - if (status === "rejected") { - setLoading(false); - errorHandler(reason); - return; - } - - setResponse(value); - setLoading(false); - }, [errorHandler]); - - const enqueue = useCallback( - (promise) => { - if (queueRef.current.length > MAX_QUEUE_LENGTH) { - throw new Error( - `Max queue length (${MAX_QUEUE_LENGTH}) exceeded in useQueuedResponse.` - ); - } - queueRef.current.push(promise); - processQueue(); - }, - [queueRef, processQueue] - ); - - return { response, setResponse, loading, enqueue }; -} - -export function useAsync({ fcn, call = true, init = null, onError = null }) { - const { response, setResponse, loading, enqueue } = useQueuedResponse({ - init, - onError, - call - }); - - const reset = useCallback(() => setResponse(init), [setResponse, init]); - - const wrapper = useCallback( - (...args) => { - enqueue(fcn(...args)); - }, - [fcn, enqueue] - ); - - useEffect(() => { - if (call) wrapper(); - }, [wrapper, call]); - - return [response, wrapper, reset, loading]; -} - -function useCallAndUnpack(fcn, unpacker = (x) => x) { - return useCallback(async (...args) => { - const response = await fcn(...args); - const unpacked = unpacker(response.obj, response.status); - return unpacked; - }, []); -} - -export function unpackIOC(ioc) { - ioc = { ...ioc }; - const { - id, - description, - createdBy, - active, - activeDeployment, - namingName, - gitProjectId, - namingUuid, - sourceUrl, - sourceVersion, - sourceVersionShort, - operationInProgress, - alerts - } = ioc; - - let unpackedIOC = { - id: id, - description: description, - createdBy: createdBy, - activeDeployment: activeDeployment, - active: active, - gitProjectId: gitProjectId, - sourceUrl: sourceUrl, - sourceVersion: sourceVersion, - sourceVersionShort: sourceVersionShort, - namingName: namingName, - operationInProgress: operationInProgress, - alerts: alerts, - namingUuid: namingUuid - }; - - return unpackedIOC; -} - -export function unpackIocInfo(ioc) { - ioc = { ...ioc }; - return ioc; -} - -export function unpackIOCList(iocs) { - let iocArray = iocs.iocList.map((ioc) => unpackIocInfo(ioc)); - - let unpackedIOCList = { - totalCount: iocs.totalCount, - pageNumber: iocs.pageNumber, - limit: iocs.limit, - iocList: iocArray - }; - - return unpackedIOCList; -} - -export function unpackDeployment(deployment) { - const d = { ...deployment }; - return d; -} - -export function unpackOngoingOperations(input) { - const { operations: operationsList, ...rest } = input; - const output = { ...rest, operationsList }; - return output.totalCount > 0 ? output.operationsList[0] : null; -} - -export function unpackJob(job) { - return { ...job }; -} - -export function useJobLogById() { - const api = useContext(apiContext); - const method = useCallAndUnpack((awxJobId) => - api.apis.Deployments.fetchDeploymentJobLog({ awx_job_id: awxJobId }) - ); - return useAsync({ fcn: method, call: false, init: null }); -} - -export function unpackRecord(record) { - return { ...record }; -} - -export function useRenewToken() { - const api = useContext(apiContext); - const method = useCallAndUnpack(api.apis.Authentication.tokenRenew); - - const [, /* response*/ renewToken /* reset*/, , loading] = useAsync({ - fcn: method, - call: false - }); - return [renewToken, loading]; -} - -export function unpackGitReference(reference) { - return { ...reference }; -} - -export function unpackTagAndCommitIdList(tagList) { - return tagList.map((t) => unpackGitReference(t)); -} - -export function useTagsAndCommitIds(onError) { - const api = useContext(apiContext); - const method = useCallAndUnpack( - (gitProjectId, reference, includeAllReference, searchMethod) => - api.apis.Git.listTagsAndCommitIds({ - project_id: gitProjectId, - reference: reference, - include_all_reference: includeAllReference, - search_method: searchMethod - }), - unpackTagAndCommitIdList - ); - return useAsync({ fcn: method, call: false, onError: onError, init: [] }); -} - -export function unpackGitProject(project) { - return { ...project }; -} - -export function unpackUpdateActiveDeploymentHost(ioc) { - return { ...ioc }; -} diff --git a/src/api/UserProvider.js b/src/api/UserProvider.js index f0ba41b79679b119c7778e5ffb99f0bd543a801e..d1265c36d7e3b7a2a9e33e0b0baf3c5699760dec 100644 --- a/src/api/UserProvider.js +++ b/src/api/UserProvider.js @@ -1,7 +1,7 @@ import React from "react"; import { useCallback, useEffect, useState, useContext } from "react"; import { userContext, useAPIMethod } from "@ess-ics/ce-ui-common"; -import { apiContext } from "./SwaggerApi"; +import { apiContext } from "./DeployApi"; function loginRequest(username, password) { return { diff --git a/src/api/UserProvider.spec.js b/src/api/UserProvider.spec.js index f0477f49f311efbfa5b7f56869fe1f10d53d998e..f23cc181ab864395306a96427e642ba4e1f87961 100644 --- a/src/api/UserProvider.spec.js +++ b/src/api/UserProvider.spec.js @@ -1,5 +1,5 @@ import React, { useContext } from "react"; -import { LegacyApiProvider } from "./SwaggerApi"; +import { DeployAPIProvider } from "./DeployApi"; import { UserProvider } from "./UserProvider"; import { userContext } from "@ess-ics/ce-ui-common"; import { SnackbarProvider } from "notistack"; @@ -10,7 +10,7 @@ function AppHarness({ children }) { preventDuplicate maxSnack="5" > - <LegacyApiProvider>{children}</LegacyApiProvider> + <DeployAPIProvider>{children}</DeployAPIProvider> </SnackbarProvider> ); } diff --git a/src/components/auth/TokenRenew/TokenRenew.js b/src/components/auth/TokenRenew/TokenRenew.js index 47b3e94e106761fe3c80c804e0c3397005e10d0e..956c29e3c651759ff7c28a6156f4c7642cdc0ec2 100644 --- a/src/components/auth/TokenRenew/TokenRenew.js +++ b/src/components/auth/TokenRenew/TokenRenew.js @@ -1,17 +1,30 @@ import { useContext, useCallback } from "react"; -import { useRenewToken } from "../../../api/SwaggerApi"; -import { userContext } from "@ess-ics/ce-ui-common"; +import { apiContext } from "../../../api/DeployApi"; +import { userContext, useAPIMethod } from "@ess-ics/ce-ui-common"; import { useSafePolling } from "../../../hooks/Polling"; export default function TokenRenew() { const { user } = useContext(userContext); - const [renewToken, loading] = useRenewToken(); + + const client = useContext(apiContext); + const { + wrapper: renewToken, + loading, + abort + } = useAPIMethod({ + fcn: client.apis.Authentication.tokenRenew, + call: false + }); const renewTokenWhenLoggedIn = useCallback(() => { if (user) { renewToken(); + + return () => { + abort(); + }; } - }, [user, renewToken]); + }, [user, renewToken, abort]); useSafePolling(renewTokenWhenLoggedIn, loading, window.TOKEN_RENEW_INTERVAL); diff --git a/src/components/auth/TokenRenew/TokenRenew.spec.js b/src/components/auth/TokenRenew/TokenRenew.spec.js index fc6e44cf4e586f44cc5d4a30c45a517e4a130c69..20dc589a0c6cd8ccebad54d22de733689709bdf3 100644 --- a/src/components/auth/TokenRenew/TokenRenew.spec.js +++ b/src/components/auth/TokenRenew/TokenRenew.spec.js @@ -1,8 +1,8 @@ import { SnackbarProvider } from "notistack"; import React from "react"; -import { LegacyApiProvider } from "../../../api/SwaggerApi"; import { UserProvider } from "../../../api/UserProvider"; import TokenRenew from "."; +import { DeployAPIProvider } from "../../../api/DeployApi"; function AppHarness({ children }) { return ( @@ -10,9 +10,9 @@ function AppHarness({ children }) { preventDuplicate maxSnack="5" > - <LegacyApiProvider> + <DeployAPIProvider> <UserProvider>{children}</UserProvider> - </LegacyApiProvider> + </DeployAPIProvider> </SnackbarProvider> ); } diff --git a/src/components/common/notification/Notifications.spec.js b/src/components/common/notification/Notifications.spec.js index 82bd0c192dbbd9a8b7f5e618506fef31d55267b3..7333548aea865c61ddefb5d2578503a4bd5f9562 100644 --- a/src/components/common/notification/Notifications.spec.js +++ b/src/components/common/notification/Notifications.spec.js @@ -1,6 +1,6 @@ import { SnackbarProvider } from "notistack"; import React, { useContext } from "react"; -import { LegacyApiProvider } from "../../../api/SwaggerApi"; +import { DeployAPIProvider } from "../../../api/DeployApi"; import { UserProvider } from "../../../api/UserProvider"; import { userContext } from "@ess-ics/ce-ui-common"; import { useEffectOnMount } from "../../../hooks/MountEffects"; @@ -12,9 +12,9 @@ function AppHarness({ children }) { preventDuplicate maxSnack="5" > - <LegacyApiProvider> + <DeployAPIProvider> <UserProvider>{children}</UserProvider> - </LegacyApiProvider> + </DeployAPIProvider> </SnackbarProvider> ); } diff --git a/src/mocks/AppHarness.js b/src/mocks/AppHarness.js index 89d1e6619dc40a9efab9f01a04464782e5459c07..45918d967cceef058f0fb7dce604d36f8fd5441e 100644 --- a/src/mocks/AppHarness.js +++ b/src/mocks/AppHarness.js @@ -3,7 +3,6 @@ import { SnackbarProvider } from "notistack"; import { Container, CssBaseline, StyledEngineProvider } from "@mui/material"; import { ThemeProvider } from "@mui/material/styles"; import { theme } from "../style/Theme"; -import { LegacyApiProvider } from "../api/SwaggerApi"; import { UserProvider } from "../api/UserProvider"; import { NotificationProvider } from "../components/common/notification/Notifications"; import NavigationMenu from "../components/navigation/NavigationMenu"; @@ -16,9 +15,7 @@ export function RouterHarness({ children, initialHistory = ["/"] }) { <ThemeProvider theme={theme}> <CssBaseline /> <MemoryRouter initialEntries={initialHistory}> - <DeployAPIProvider> - <LegacyApiProvider>{children}</LegacyApiProvider> - </DeployAPIProvider> + <DeployAPIProvider>{children}</DeployAPIProvider> </MemoryRouter> </ThemeProvider> </StyledEngineProvider>