Skip to content

Commit

Permalink
Refactor caraml ai app into generic streamlit placeholders
Browse files Browse the repository at this point in the history
  • Loading branch information
deadlycoconuts committed Jul 25, 2024
1 parent 09c363a commit ff2fb73
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 40 deletions.
5 changes: 2 additions & 3 deletions api/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
15 changes: 10 additions & 5 deletions api/models/v2/application.go
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -18,3 +19,7 @@ type NavigationMenuItem struct {
Label string `json:"label"`
Destination string `json:"destination"`
}

type StreamlitPlaceholderPageConfig struct {
StreamlitURL string `json:"streamlit_url"`
}
69 changes: 47 additions & 22 deletions ui/packages/app/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,71 @@ 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 (
<EuiProvider>
<ErrorBoundary>
<MlpApiContextProvider
mlpApiUrl={config.API}
timeout={config.TIMEOUT}
useMockData={config.USE_MOCK_DATA}>
<AuthProvider clientId={config.OAUTH_CLIENT_ID}>
<Routes>
<Route path="/login" element={<Login />} />

<Route element={<PrivateLayout />}>
<Route path="/*" element={<AppRoutes />} />
</Route>

<Route path="/pages/404" element={<Page404 />} />

{
config.CARAML_AI_STREAMLIT_HOMEPAGE &&
<Route element={<PrivateLayout />}>
<Route path="/caraml-ai" element={<CaraMLAIPage />} />
</Route>
}
</Routes>
<Toast />
<ApplicationsContextProvider>
<MLPRoutes />
<Toast />
</ApplicationsContextProvider>
</AuthProvider>
</MlpApiContextProvider>
</ErrorBoundary>
</EuiProvider>
);
)};

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)) ? (
<Routes>
<Route path="/login" element={<Login />} />

<Route element={<PrivateLayout />}>
<Route path="/*" element={<AppRoutes />} />
{/*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 =>
<Route key={app.name} path={app.homepage} element={<StreamlitPlaceholderPage app={app} />} />
)
}
</Route>

<Route path="/pages/404" element={<Page404 />} />

</Routes>
) : null;
};
1 change: 0 additions & 1 deletion ui/packages/app/src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import React from "react";
import { EuiPageTemplate } from "@elastic/eui";
import config from "./../config";

export const CaraMLAIPage = () => {
const iframe = `<iframe src=${config.CARAML_AI_STREAMLIT_HOMEPAGE} style="height: 80vh; width: 100%;"></iframe>`
export const StreamlitPlaceholderPage = ({ app }) => {
const iframe = `<iframe src=${app.streamlit_placeholder_page_config.streamlit_url} style="height: 80vh; width: 100%;"></iframe>`

function Iframe(props) {
return (<div dangerouslySetInnerHTML={ {__html: props.iframe?props.iframe:""}} />);
Expand All @@ -12,8 +11,8 @@ export const CaraMLAIPage = () => {
<EuiPageTemplate restrictWidth="90%" panelled={false}>
<EuiPageTemplate.Header
bottomBorder={false}
iconType="timelionApp"
pageTitle="CaraML AI"
iconType={app.config.icon}
pageTitle={app.name}
/>

<EuiPageTemplate.Section paddingSize="none">
Expand Down
8 changes: 4 additions & 4 deletions ui/packages/lib/src/providers/application/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<ApplicationsContext.Provider value={{ currentApp, apps }}>
<ApplicationsContext.Provider value={{ currentApp, apps, isLoaded }}>
{children}
</ApplicationsContext.Provider>
);
Expand Down

0 comments on commit ff2fb73

Please sign in to comment.