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

[WiP] Implement POC for testing IA redesign #3628

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions frontend/src/api/k8s/notebooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,11 @@ export const getStopPatch = (): Patch => ({
value: getStopPatchDataString(),
});

export const getAllNotebooks = (): Promise<NotebookKind[]> =>
k8sListResource<NotebookKind>({
model: NotebookModel,
}).then((listResource) => listResource.items);

export const getNotebooks = (namespace: string): Promise<NotebookKind[]> =>
k8sListResource<NotebookKind>({
model: NotebookModel,
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/api/k8s/pvcs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ export const assemblePvc = (
};
};

export const getAllDashboardPvcs = (): Promise<PersistentVolumeClaimKind[]> =>
k8sListResourceItems<PersistentVolumeClaimKind>({
model: PVCModel,
queryOptions: {
queryParams: { labelSelector: LABEL_SELECTOR_DASHBOARD_RESOURCE },
},
});

export const getDashboardPvcs = (projectName: string): Promise<PersistentVolumeClaimKind[]> =>
k8sListResourceItems<PersistentVolumeClaimKind>({
model: PVCModel,
Expand Down
11 changes: 11 additions & 0 deletions frontend/src/api/k8s/secrets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,17 @@ export const getSecret = (
),
);

export const getAllSecretsByLabel = (label: string, opts?: K8sAPIOptions): Promise<SecretKind[]> =>
k8sListResource<SecretKind>(
applyK8sAPIOptions(
{
model: SecretModel,
queryOptions: { queryParams: { labelSelector: label } },
},
opts,
),
).then((result) => result.items);

export const getSecretsByLabel = (
label: string,
namespace: string,
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/app/App.scss
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,16 @@ body,
overflow: hidden;
}
}

.pf-v6-c-menu__list-item:has(.pf-v6-c-menu__item-action) {
padding-inline-end: 0;
}

.pf-v6-c-menu__item-action.pf-m-favorited,
.pf-v6-c-menu__item-action.pf-m-favorited .pf-v6-c-button,
.pf-v6-c-menu__item-action.pf-m-favorited:hover,
.pf-v6-c-menu__item-toggle-icon.pf-m-favorited,
.pf-v6-c-menu__item-toggle-icon.pf-m-favorited .pf-v6-c-button,
.pf-v6-c-menu__item-toggle-icon.pf-m-favorited:hover {
--pf-v6-c-button__icon--Color: var(--pf-v6-c-menu__item-action--m-favorited--Color);
}
70 changes: 69 additions & 1 deletion frontend/src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
Stack,
StackItem,
} from '@patternfly/react-core';
import { useSearchParams } from 'react-router-dom';
import ErrorBoundary from '~/components/error/ErrorBoundary';
import ToastNotifications from '~/components/ToastNotifications';
import { useWatchBuildStatus } from '~/utilities/useWatchBuildStatus';
Expand All @@ -23,6 +24,7 @@ import { ModelRegistrySelectorContextProvider } from '~/concepts/modelRegistry/c
import useStorageClasses from '~/concepts/k8s/useStorageClasses';
import AreaContextProvider from '~/concepts/areas/AreaContext';
import { NimContextProvider } from '~/concepts/nimServing/NIMAvailabilityContext';
import { useBrowserStorage } from '~/components/browserStorage';
import useDevFeatureFlags from './useDevFeatureFlags';
import Header from './Header';
import AppRoutes from './AppRoutes';
Expand All @@ -38,9 +40,33 @@ import SessionExpiredModal from './SessionExpiredModal';

import './App.scss';

type PocConfigType = {
altNav?: boolean;
altPreferredProject?: boolean;
};

const FAVORITE_PROJECTS_KEY = 'odh-favorite-projects';
const POC_SESSION_KEY = 'odh-poc-flags';
const ALT_NAV_PARAM = 'altNav';
const ALT_PROJECTS_PARAM = 'altProjects';

const App: React.FC = () => {
const [notificationsOpen, setNotificationsOpen] = React.useState(false);
const { username, userError, isAllowed } = useUser();
const [searchParams, setSearchParams] = useSearchParams();
const [altNav, setAltNav] = React.useState<boolean>(false);
const [altPreferredProject, setAltPreferredProject] = React.useState<boolean>(false);
const firstLoad = React.useRef(true);
const [pocConfig, setPocConfig] = useBrowserStorage<PocConfigType | null>(
POC_SESSION_KEY,
null,
true,
true,
);
const [favoriteProjects, setFavoriteProjects] = useBrowserStorage<string[]>(
FAVORITE_PROJECTS_KEY,
[],
);

const buildStatuses = useWatchBuildStatus();
const {
Expand All @@ -56,6 +82,36 @@ const App: React.FC = () => {

useDetectUser();

React.useEffect(() => {
if (firstLoad.current && pocConfig?.altNav) {
setAltNav(true);
}
firstLoad.current = false;
}, [pocConfig]);

React.useEffect(() => {
if (searchParams.has(ALT_NAV_PARAM)) {
const updated = searchParams.get(ALT_NAV_PARAM) === 'true';
setAltNav(updated);
setPocConfig({ altNav: updated });

// clean up query string
searchParams.delete(ALT_NAV_PARAM);
setSearchParams(searchParams, { replace: true });
}
if (searchParams.has(ALT_PROJECTS_PARAM)) {
const updated = searchParams.get(ALT_PROJECTS_PARAM) === 'true';
setAltPreferredProject(updated);
setPocConfig({ altNav: updated });

// clean up query string
searchParams.delete(ALT_NAV_PARAM);
setSearchParams(searchParams, { replace: true });
}
// do not react to changes to setters
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchParams]);

const contextValue = React.useMemo(
() =>
dashboardConfig
Expand All @@ -64,9 +120,21 @@ const App: React.FC = () => {
dashboardConfig,
storageClasses,
isRHOAI: dashboardConfig.metadata?.namespace === 'redhat-ods-applications',
altNav,
altPreferredProject,
favoriteProjects,
setFavoriteProjects,
}
: null,
[buildStatuses, dashboardConfig, storageClasses],
[
dashboardConfig,
buildStatuses,
storageClasses,
altNav,
altPreferredProject,
favoriteProjects,
setFavoriteProjects,
],
);

const isUnauthorized = fetchConfigError?.request?.status === 403;
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/app/AppContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ type AppContextProps = {
dashboardConfig: DashboardConfigKind;
storageClasses: StorageClassKind[];
isRHOAI: boolean;
altNav?: boolean;
altPreferredProject?: boolean;
favoriteProjects: string[];
setFavoriteProjects: (projects: string[]) => void;
};

// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
Expand Down
81 changes: 78 additions & 3 deletions frontend/src/app/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ import { SupportedArea } from '~/concepts/areas';
import useIsAreaAvailable from '~/concepts/areas/useIsAreaAvailable';
import ModelRegistrySettingsRoutes from '~/pages/modelRegistrySettings/ModelRegistrySettingsRoutes';
import ConnectionTypeRoutes from '~/pages/connectionTypes/ConnectionTypeRoutes';
import ComingSoonPage from '~/pages/ComingSoonPage';
import { ProjectObjectType } from '~/concepts/design/utils';
import GlobalNotebooksPage from '~/pages/notebooks/GlobalNotebooksPage';
import ProjectDetailsContextProvider from '~/pages/projects/ProjectDetailsContext';
import GlobalClusterStoragePage from '~/pages/clusterStorage/GlobalClusterStoragePage';
import GlobalConnectionsPage from '~/pages/connections/GlobalConnectionsPage';
import Applications from '~/pages/applications/Applications';
import GeneralSettings from '~/pages/clusterSettings/GeneralSettings';
import ModelServingPlatforms from '~/pages/modelSetup/ModelServingPlatforms';

const HomePage = React.lazy(() => import('../pages/home/Home'));

Expand Down Expand Up @@ -55,9 +64,13 @@ const ClusterSettingsPage = React.lazy(() => import('../pages/clusterSettings/Cl
const CustomServingRuntimeRoutes = React.lazy(
() => import('../pages/modelServing/customServingRuntimes/CustomServingRuntimeRoutes'),
);
const EnvironmentSetupPage = React.lazy(() => import('../pages/environmentSetup/EnvironmentSetup'));
const ModelSetupPage = React.lazy(() => import('../pages/modelSetup/ModelSetup'));

const GroupSettingsPage = React.lazy(() => import('../pages/groupSettings/GroupSettings'));
const LearningCenterPage = React.lazy(() => import('../pages/learningCenter/LearningCenter'));
const BYONImageRoutes = React.lazy(() => import('../pages/BYONImages/BYONImageRoutes'));
const BYONImagesPage = React.lazy(() => import('../pages/BYONImages/BYONImages'));
const NotFound = React.lazy(() => import('../pages/NotFound'));

const DependencyMissingPage = React.lazy(
Expand Down Expand Up @@ -108,6 +121,12 @@ const AppRoutes: React.FC = () => {
)}
<Route path="/explore" element={<ExploreApplications />} />
<Route path="/resources" element={<LearningCenterPage />} />
<Route
path="/modelCustomization"
element={
<ComingSoonPage title="Model customization" objectType={ProjectObjectType.modelSetup} />
}
/>

<Route path="/projects/*" element={<ProjectViewRoutes />} />

Expand All @@ -124,6 +143,7 @@ const AppRoutes: React.FC = () => {

<Route path="/modelCatalog/*" element={<ModelCatalogRoutes />} />

<Route path="/modelRegistry" element={<ModelRegistryRoutes />} />
<Route path="/modelRegistry/*" element={<ModelRegistryRoutes />} />

<Route path={globPipelinesAll} element={<GlobalPipelinesRoutes />} />
Expand All @@ -138,8 +158,30 @@ const AppRoutes: React.FC = () => {

{isAdmin && (
<>
<Route path="/notebookImages/*" element={<BYONImageRoutes />} />
<Route path="/clusterSettings" element={<ClusterSettingsPage />} />
<Route path="/notebookImages" element={<BYONImageRoutes />} />
<Route path="/clusterSettings" element={<ClusterSettingsPage />}>
<Route path="general" element={<GeneralSettings />} />
<Route path="storage-classes" element={<StorageClassesPage />} />
</Route>
<Route path="/environmentSetup" element={<EnvironmentSetupPage />}>
<Route path="workbench-images" element={<BYONImagesPage />} />
<Route path="hardware-profiles" element={<AcceleratorProfileRoutes />} />
<Route path="connection-types" element={<ConnectionTypeRoutes />} />
</Route>
<Route path="/modelSetup" element={<ModelSetupPage />}>
<Route path="model-serving-platforms" element={<ModelServingPlatforms />} />
<Route path="serving-runtimes" element={<CustomServingRuntimeRoutes />} />
<Route
path="model-registry-settings"
element={
<ComingSoonPage
title="Model registry settings"
objectType={ProjectObjectType.modelRegistrySettings}
/>
}
/>
</Route>

<Route path="/acceleratorProfiles/*" element={<AcceleratorProfileRoutes />} />
<Route path="/hardwareProfiles/*" element={<HardwareProfileRoutes />} />
<Route path="/servingRuntimes/*" element={<CustomServingRuntimeRoutes />} />
Expand All @@ -149,7 +191,40 @@ const AppRoutes: React.FC = () => {
<Route path="/groupSettings" element={<GroupSettingsPage />} />
</>
)}

<Route
path="/workbenches/:namespace?"
element={
<ProjectDetailsContextProvider
getInvalidRedirectPath={(ns) => `/workbenches/${ns}`}
allowAllProjects
/>
}
>
<Route index element={<GlobalNotebooksPage />} />
</Route>
<Route
path="/clusterStorage/:namespace?"
element={
<ProjectDetailsContextProvider
getInvalidRedirectPath={(ns) => `/clusterStorage/${ns}`}
allowAllProjects
/>
}
>
<Route index element={<GlobalClusterStoragePage />} />
</Route>
<Route
path="/connections/:namespace?"
element={
<ProjectDetailsContextProvider
getInvalidRedirectPath={(ns) => `/connections/${ns}`}
allowAllProjects
/>
}
>
<Route index element={<GlobalConnectionsPage />} />
</Route>
<Route path="/applications/:tab?" element={<Applications />} />
<Route path="*" element={<NotFound />} />
</Routes>
</React.Suspense>
Expand Down
26 changes: 23 additions & 3 deletions frontend/src/app/NavSidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React from 'react';
import { Link, useLocation } from 'react-router-dom';
import {
Flex,
FlexItem,
Nav,
NavExpandable,
NavItem,
Expand All @@ -20,7 +22,16 @@ const NavHref: React.FC<{ item: NavDataHref; pathname: string }> = ({ item, path
itemId={item.id}
isActive={checkLinkActiveStatus(pathname, item.href)}
>
<Link to={item.href}>{item.label}</Link>
<Link to={item.href}>
{item.icon ? (
<Flex gap={{ default: 'gapXs' }}>
<FlexItem>{item.icon}</FlexItem>
<FlexItem>{item.label}</FlexItem>
</Flex>
) : (
item.label
)}
</Link>
</NavItem>
);

Expand All @@ -41,12 +52,21 @@ const NavGroup: React.FC<{ item: NavDataGroup; pathname: string }> = ({ item, pa
data-id={group.id}
key={group.id}
id={group.id}
title={group.title}
title={
group.icon ? (
<Flex gap={{ default: 'gapXs' }}>
<FlexItem>{group.icon}</FlexItem>
<FlexItem>{group.label}</FlexItem>
</Flex>
) : (
group.label
)
}
groupId={group.id}
isActive={isActive}
isExpanded={expanded}
onExpand={(e, val) => setExpanded(val)}
aria-label={group.title}
aria-label={group.label}
>
{children.map((childItem) => (
<NavHref key={childItem.id} data-id={childItem.id} item={childItem} pathname={pathname} />
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/app/__tests__/AboutDialog.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ describe('AboutDialog', () => {
dashboardConfig: DashboardConfigKind;
storageClasses: StorageClassKind[];
isRHOAI: boolean;
favoriteProjects: string[];
setFavoriteProjects: (projects: string[]) => void;
};
let userInfo: UserState;
const clusterInfo: ClusterState = { serverURL: 'https://test-server.com' };
Expand All @@ -67,6 +69,9 @@ describe('AboutDialog', () => {
dashboardConfig,
storageClasses: [],
isRHOAI: false,
favoriteProjects: [],
// eslint-disable-next-line @typescript-eslint/no-empty-function
setFavoriteProjects: () => {},
};
dsciStatus = {
conditions: [],
Expand Down
Loading
Loading