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

periodic sync upstream KF to midstream ODH #112

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
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
279 changes: 147 additions & 132 deletions clients/python/poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion clients/python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ coverage = { extras = ["toml"], version = "^7.3.2" }
pytest-cov = ">=4.1,<6.0"
ruff = ">=0.5.2,<0.7.0"
mypy = "^1.7.0"
pytest-asyncio = "^0.23.7"
pytest-asyncio = ">=0.23.7,<0.25.0"
requests = "^2.32.2"
black = "^24.4.2"

Expand Down
828 changes: 313 additions & 515 deletions clients/ui/frontend/package-lock.json

Large diffs are not rendered by default.

13 changes: 7 additions & 6 deletions clients/ui/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"scripts": {
"build": "run-s build:prod",
"build:analyze": "run-s build build:bundle-profile build:bundle-analyze",
"build:bundle-profile": "_ODH_OUTPUT_ONLY=true webpack --config ./config/webpack.prod.js --profile --json > ./bundle.stats.json",
"build:bundle-profile": "webpack --config ./config/webpack.prod.js --profile --json > ./bundle.stats.json",
"build:bundle-analyze": "webpack-bundle-analyzer ./bundle.stats.json",
"build:clean": "rimraf ./public",
"build:prod": "webpack --config ./config/webpack.prod.js",
Expand All @@ -32,7 +32,7 @@
"@cypress/code-coverage": "^3.12.45",
"@testing-library/cypress": "^10.0.1",
"@testing-library/jest-dom": "^6.4.8",
"@testing-library/react": "^14.0.0",
"@testing-library/react": "^16.0.0",
"@testing-library/user-event": "14.4.3",
"@types/jest": "^29.5.12",
"@types/react-router-dom": "^5.3.3",
Expand All @@ -42,7 +42,7 @@
"chai-subset": "^1.6.0",
"copy-webpack-plugin": "^12.0.2",
"css-loader": "^6.11.0",
"css-minimizer-webpack-plugin": "^5.0.1",
"css-minimizer-webpack-plugin": "^7.0.0",
"cypress": "^13.10.0",
"cypress-axe": "^1.5.0",
"cypress-high-resolution": "^1.0.0",
Expand All @@ -63,17 +63,17 @@
"prettier": "^3.3.3",
"prop-types": "^15.8.1",
"raw-loader": "^4.0.2",
"react-router-dom": "^6.4.1",
"react-router-dom": "^6.26.1",
"regenerator-runtime": "^0.14.1",
"rimraf": "^6.0.1",
"serve": "^14.2.1",
"style-loader": "^4.0.0",
"svg-url-loader": "^8.0.0",
"terser-webpack-plugin": "^5.3.10",
"ts-jest": "^29.2.4",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.1",
"tsconfig-paths-webpack-plugin": "^4.1.0",
"tslib": "^2.6.3",
"tslib": "^2.7.0",
"typescript": "^5.5.4",
"url-loader": "^4.1.1",
"webpack": "^5.91.0",
Expand All @@ -86,6 +86,7 @@
"@patternfly/react-core": "6.0.0-alpha.102",
"@patternfly/react-icons": "6.0.0-alpha.35",
"@patternfly/react-styles": "6.0.0-alpha.35",
"lodash-es": "^4.17.21",
"npm-run-all": "^4.1.5",
"react": "^18",
"react-dom": "^18"
Expand Down
100 changes: 86 additions & 14 deletions clients/ui/frontend/src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,88 @@ import '@patternfly/react-core/dist/styles/base.css';
import AppRoutes from './AppRoutes';
import './app.css';
import {
Alert,
Bullseye,
Button,
Flex,
Masthead,
MastheadContent,
MastheadToggle,
Page,
PageSection,
PageToggleButton,
Title
} from '@patternfly/react-core';
import NavSidebar from './NavSidebar';
import { BarsIcon } from '@patternfly/react-icons';
Spinner,
Stack,
StackItem,
Title,
} from "@patternfly/react-core";
import NavSidebar from "./NavSidebar";
import { BarsIcon } from "@patternfly/react-icons";
import { AppContext } from "./AppContext";
import { useSettings } from "./useSettings";

const App: React.FC = () => {
const {
configSettings,
userSettings,
loaded: configLoaded,
loadError: configError,
} = useSettings();

const contextValue = React.useMemo(
() =>
configSettings && userSettings
? {
config: configSettings!,
user: userSettings!,
}
: null,
[configSettings, userSettings]
);

// We lack the critical data to startup the app
if (configError) {
// There was an error fetching critical data
return (
<Page>
<PageSection>
<Stack hasGutter>
<StackItem>
<Alert variant="danger" isInline title="General loading error">
<p>
{configError.message ||
"Unknown error occurred during startup."}
</p>
<p>Logging out and logging back in may solve the issue.</p>
</Alert>
</StackItem>
<StackItem>
<Button
variant="secondary"
onClick={() => {
// TODO: logout
}}
>
Logout
</Button>
</StackItem>
</Stack>
</PageSection>
</Page>
);
}

// Waiting on the API to finish
const loading = !configLoaded || !userSettings || !configSettings || !contextValue;

const masthead = (
<Masthead>
<MastheadToggle>
<PageToggleButton id="page-nav-toggle" variant="plain" aria-label="Dashboard navigation">
<PageToggleButton
id="page-nav-toggle"
variant="plain"
aria-label="Dashboard navigation"
>
<BarsIcon />
</PageToggleButton>
</MastheadToggle>
Expand All @@ -33,15 +99,21 @@ const App: React.FC = () => {
</Masthead>
);

return (
<Page
mainContainerId='primary-app-container'
masthead={masthead}
isManagedSidebar
sidebar={<NavSidebar />}
>
<AppRoutes />
</Page>
return loading ? (
<Bullseye>
<Spinner />
</Bullseye>
) : (
<AppContext.Provider value={contextValue}>
<Page
mainContainerId="primary-app-container"
masthead={masthead}
isManagedSidebar
sidebar={<NavSidebar />}
>
<AppRoutes />
</Page>
</AppContext.Provider>
);
};

Expand Down
13 changes: 13 additions & 0 deletions clients/ui/frontend/src/app/AppContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as React from 'react';
import { UserSettings, ConfigSettings } from '~/types';


type AppContextProps = {
config: ConfigSettings;
user: UserSettings;
};

// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
export const AppContext = React.createContext({} as AppContextProps);

export const useAppContext = (): AppContextProps => React.useContext(AppContext);
84 changes: 84 additions & 0 deletions clients/ui/frontend/src/app/useSettings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import * as React from "react";
import useTimeBasedRefresh from "./useTimeBasedRefresh";
import { ConfigSettings, UserSettings } from "../types";
import { POLL_INTERVAL } from "~/utilities/const";
import { useDeepCompareMemoize } from "../utilities/useDeepCompareMemoize";

export const useSettings = (): {
configSettings: ConfigSettings | null;
userSettings: UserSettings | null;
loaded: boolean;
loadError: Error | undefined;
} => {
const [loaded, setLoaded] = React.useState(false);
const [loadError, setLoadError] = React.useState<Error>();
const [config, setConfig] = React.useState<ConfigSettings | null>(null);
const [user, setUser] = React.useState<UserSettings | null>(null);
const setRefreshMarker = useTimeBasedRefresh();

React.useEffect(() => {
let watchHandle: ReturnType<typeof setTimeout>;
let cancelled = false;
const watchConfig = () => {
Promise.all([fetchConfig(), fetchUser()])
.then(([config, user]) => {
if (cancelled) {
return;
}
setConfig(config);
setUser(user);
setLoaded(true);
setLoadError(undefined);
})
.catch((e) => {
if (e?.message?.includes("Error getting Oauth Info for user")) {
// NOTE: this endpoint only requests oauth because of the security layer, this is not an ironclad use-case
// Something went wrong on the server with the Oauth, let us just log them out and refresh for them
/* eslint-disable-next-line no-console */
console.error(
"Something went wrong with the oauth token, please log out...",
e.message,
e
);
setRefreshMarker(new Date());
return;
}
setLoadError(e);
});
watchHandle = setTimeout(watchConfig, POLL_INTERVAL);
};
watchConfig();

return () => {
cancelled = true;
clearTimeout(watchHandle);
};
}, [setRefreshMarker]);

const retConfig = useDeepCompareMemoize<ConfigSettings | null>(config);
const retUser = useDeepCompareMemoize<UserSettings | null>(user);

return { configSettings: retConfig, userSettings: retUser, loaded, loadError };
};

// Mock a settings config call
// TODO: replace with thea actual call once we have the endpoint
export const fetchConfig = async (): Promise<ConfigSettings> => {
return {
common: {
featureFlags: {
modelRegistry: true,
},
},
};
};

// Mock a settings user call
// TODO: replace with thea actual call once we have the endpoint
export const fetchUser = async (): Promise<UserSettings> => {
return {
username: "admin",
isAdmin: true,
isAllowed: true,
};
};
48 changes: 48 additions & 0 deletions clients/ui/frontend/src/app/useTimeBasedRefresh.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import * as React from 'react';
import { useBrowserStorage } from '~/components/browserStorage';


export type SetTime = (refreshDateMarker: Date) => void;

const useTimeBasedRefresh = (): SetTime => {
const KEY_NAME = 'kf.dashboard.last.auto.refresh';
const [lastRefreshTimestamp, setLastRefreshTimestamp] = useBrowserStorage(
KEY_NAME,
'0',
false,
true,
);
const ref = React.useRef<{
lastRefreshTimestamp: string;
setLastRefreshTimestamp: (newValue: string) => void;
}>({ lastRefreshTimestamp, setLastRefreshTimestamp });
ref.current = { lastRefreshTimestamp, setLastRefreshTimestamp };

return React.useCallback<SetTime>((refreshDateMarker) => {
// Intentionally avoid referential changes. We want the value at call time.
// Recomputing the ref is not needed and will impact usage in hooks if it does.
const lastDate = new Date(ref.current.lastRefreshTimestamp);
const setNewDateString = ref.current.setLastRefreshTimestamp;

/* eslint-disable no-console */
// Print into the console in case we are not refreshing or the browser has preserve log enabled
console.warn('Attempting to re-trigger an auto refresh');
console.log('Last refresh was on:', lastDate);
console.log('Refreshing requested after:', refreshDateMarker);

lastDate.setHours(lastDate.getHours() + 1);
if (lastDate < refreshDateMarker) {
setNewDateString(refreshDateMarker.toString());
console.log('Logging out and refreshing');
// TODO: Replace with actual logout function
//logout().then(() => window.location.reload());
} else {
console.error(
`We should have refreshed but it appears the last time we auto-refreshed was less than an hour ago. '${KEY_NAME}' session storage setting can be cleared for this to refresh again within the hour from the last refresh.`,
);
}
/* eslint-enable no-console */
}, []);
};

export default useTimeBasedRefresh;
Empty file.
Loading
Loading