diff --git a/api/config/config.go b/api/config/config.go index 7cb7e568..3394c37b 100644 --- a/api/config/config.go +++ b/api/config/config.go @@ -119,9 +119,8 @@ type UIConfig struct { StaticPath string `validated:"required"` IndexPath string `validated:"required"` - ClockworkUIHomepage string `json:"REACT_APP_CLOCKWORK_UI_HOMEPAGE"` - KubeflowUIHomepage string `json:"REACT_APP_KUBEFLOW_UI_HOMEPAGE"` - CaramlAIStreamlitHomepage string `json:"REACT_APP_CARAML_AI_STREAMLIT_HOMEPAGE"` + ClockworkUIHomepage string `json:"REACT_APP_CLOCKWORK_UI_HOMEPAGE"` + KubeflowUIHomepage string `json:"REACT_APP_KUBEFLOW_UI_HOMEPAGE"` AllowCustomStream bool `json:"REACT_APP_ALLOW_CUSTOM_STREAM"` AllowCustomTeam bool `json:"REACT_APP_ALLOW_CUSTOM_TEAM"` diff --git a/api/models/v2/application.go b/api/models/v2/application.go index 95721f94..db6f183c 100644 --- a/api/models/v2/application.go +++ b/api/models/v2/application.go @@ -1,11 +1,12 @@ package models type Application struct { - Name string `json:"name" validate:"required"` - Description string `json:"description"` - Homepage string `json:"homepage"` - Configuration *ApplicationConfig `json:"config" validate:"dive"` - IsProjectAgnostic bool `json:"is_project_agnostic"` + Name string `json:"name" validate:"required"` + Description string `json:"description"` + Homepage string `json:"homepage"` + Configuration *ApplicationConfig `json:"config" validate:"dive"` + IsProjectAgnostic bool `json:"is_project_agnostic"` + StreamlitPlaceholderPageConfig *StreamlitPlaceholderPageConfig `json:"streamlit_placeholder_page_config"` } type ApplicationConfig struct { @@ -18,3 +19,7 @@ type NavigationMenuItem struct { Label string `json:"label"` Destination string `json:"destination"` } + +type StreamlitPlaceholderPageConfig struct { + StreamlitURL string `json:"streamlit_url"` +} diff --git a/ui/packages/app/src/App.js b/ui/packages/app/src/App.js index e1008274..1e2dd943 100644 --- a/ui/packages/app/src/App.js +++ b/ui/packages/app/src/App.js @@ -3,18 +3,22 @@ import { ErrorBoundary, Login, MlpApiContextProvider, + ApplicationsContextProvider, + ApplicationsContext, Page404, - Toast + Toast, + AuthContext } from "@caraml-dev/ui-lib"; import { EuiProvider } from "@elastic/eui"; -import React from "react"; +import React, { useContext } from "react"; import { Route, Routes } from "react-router-dom"; import AppRoutes from "./AppRoutes"; import { PrivateLayout } from "./PrivateLayout"; import config from "./config"; -import { CaraMLAIPage } from "./caraml_ai/CaraMLAIPage"; +import { StreamlitPlaceholderPage } from "./streamlit_placeholder_page/StreamlitPlaceholderPage"; -const App = () => ( +const App = () => { + return ( ( timeout={config.TIMEOUT} useMockData={config.USE_MOCK_DATA}> - - } /> - - }> - } /> - - - } /> - - { - config.CARAML_AI_STREAMLIT_HOMEPAGE && - }> - } /> - - } - - + + + + -); +)}; export default App; + +const MLPRoutes = () => { + const { apps, isLoaded } = useContext(ApplicationsContext); + const { state } = useContext(AuthContext); + + // If the user is not authenticated, show the login page, WITHOUT rendering the additional streamlit app routes (they + // need to be retrieved from the MLP API v2/applications endpoint). + // + // If the user is already authenticated, check that the apps have been loaded into an array (the ApplicationsContext + // may still return a memoized empty object even if state.isAuthenticated is true because the API call happens + // asynchronously). If the apps have not been loaded yet, we return null until all the apps are loaded before + // rendering all the routes, including the streamlit app routes, at once (we do this because React routes are rendered + // synchronously and cannot be updated without reloading the entire page). + return (!state.isAuthenticated || (isLoaded && apps.constructor === Array)) ? ( + + } /> + + }> + } /> + {/*If the user is authenticated, and all the apps have been loaded into an array, we construct a Route*/} + {/*corresponding to each Streamlit app*/} + {state.isAuthenticated && + isLoaded && apps.constructor === Array && + apps.map(app => + } /> + ) + } + + + } /> + + + ) : null; +}; \ No newline at end of file diff --git a/ui/packages/app/src/config.js b/ui/packages/app/src/config.js index d2a7e8a0..3a301dc0 100644 --- a/ui/packages/app/src/config.js +++ b/ui/packages/app/src/config.js @@ -33,7 +33,6 @@ const config = { ), CLOCKWORK_UI_HOMEPAGE: getEnv("REACT_APP_CLOCKWORK_UI_HOMEPAGE"), KUBEFLOW_UI_HOMEPAGE: getEnv("REACT_APP_KUBEFLOW_UI_HOMEPAGE"), - CARAML_AI_STREAMLIT_HOMEPAGE: getEnv("REACT_APP_CARAML_AI_STREAMLIT_HOMEPAGE"), ALLOW_CUSTOM_STREAM: getEnv("REACT_APP_ALLOW_CUSTOM_STREAM") != null ? getEnv("REACT_APP_ALLOW_CUSTOM_STREAM") diff --git a/ui/packages/app/src/caraml_ai/CaraMLAIPage.js b/ui/packages/app/src/streamlit_placeholder_page/StreamlitPlaceholderPage.js similarity index 66% rename from ui/packages/app/src/caraml_ai/CaraMLAIPage.js rename to ui/packages/app/src/streamlit_placeholder_page/StreamlitPlaceholderPage.js index dc3892ad..b99a500f 100644 --- a/ui/packages/app/src/caraml_ai/CaraMLAIPage.js +++ b/ui/packages/app/src/streamlit_placeholder_page/StreamlitPlaceholderPage.js @@ -1,9 +1,8 @@ import React from "react"; import { EuiPageTemplate } from "@elastic/eui"; -import config from "./../config"; -export const CaraMLAIPage = () => { - const iframe = `` +export const StreamlitPlaceholderPage = ({ app }) => { + const iframe = `` function Iframe(props) { return (
); @@ -12,8 +11,8 @@ export const CaraMLAIPage = () => { diff --git a/ui/packages/lib/src/providers/application/context.js b/ui/packages/lib/src/providers/application/context.js index d254fde7..7535b4f0 100644 --- a/ui/packages/lib/src/providers/application/context.js +++ b/ui/packages/lib/src/providers/application/context.js @@ -9,15 +9,15 @@ const ApplicationsContext = React.createContext({ export const ApplicationsContextProvider = ({ children }) => { const location = useLocation(); - const [{ data: apps }] = useMlpApi("/v2/applications", {}, []); + const [{ data: apps, isLoaded }] = useMlpApi("/v2/applications", {}, []); const currentApp = useMemo( - () => apps.find(a => location.pathname.startsWith(a.homepage)), - [apps, location.pathname] + () => isLoaded && apps.constructor === Array && apps.find(a => location.pathname.startsWith(a.homepage)), + [apps, isLoaded, location.pathname] ); return ( - + {children} );