Skip to content

Commit

Permalink
Setup new nav bar and vertical nav in project details
Browse files Browse the repository at this point in the history
  • Loading branch information
jeff-phillips-18 committed Jan 14, 2025
1 parent bbb5eaf commit ace063a
Show file tree
Hide file tree
Showing 32 changed files with 1,112 additions and 298 deletions.
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
38 changes: 37 additions & 1 deletion frontend/src/app/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ 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';

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

Expand Down Expand Up @@ -116,9 +118,18 @@ const AppRoutes: React.FC = () => {
element={<NotebookLogoutRedirectPage />}
/>

<Route
path="/modelOverview"
element={<ComingSoonPage title="Model overview" objectType={ProjectObjectType.model} />}
/>
<Route path="/modelServing/*" element={<ModelServingRoutes />} />

<Route path="/modelRegistry/*" element={<ModelRegistryRoutes />} />
<Route
path="/deployedModels"
element={
<ComingSoonPage title="Deployed models" objectType={ProjectObjectType.modelServer} />
}
/>

<Route path={globPipelinesAll} element={<GlobalPipelinesRoutes />} />
<Route path={globPipelineRunsAll} element={<GlobalPipelineRunsRoutes />} />
Expand All @@ -145,6 +156,31 @@ const AppRoutes: React.FC = () => {
<Route path="/groupSettings" element={<GroupSettingsPage />} />
</>
)}
<Route
path="/workbenches"
element={<ComingSoonPage title="Workbenches" objectType={ProjectObjectType.notebook} />}
/>
<Route
path="/connections"
element={
<ComingSoonPage title="Connections" objectType={ProjectObjectType.dataConnection} />
}
/>
<Route
path="/clusterStorage"
element={
<ComingSoonPage title="Cluster storage" objectType={ProjectObjectType.clusterStorage} />
}
/>
<Route
path="/applications"
element={
<ComingSoonPage
title="Applications"
objectType={ProjectObjectType.enabledApplications}
/>
}
/>

<Route path="*" element={<NotFound />} />
</Routes>
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
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { Link, matchPath, useLocation } from 'react-router-dom';
import { ActionsColumn, IAction, Td, Tr } from '@patternfly/react-table';
import { Truncate } from '@patternfly/react-core';
import { ExperimentKF, StorageStateKF } from '~/concepts/pipelines/kfTypes';
import { CheckboxTd, TableRowTitleDescription } from '~/components/table';
import { experimentArchivedRunsRoute, experimentRunsRoute } from '~/routes';
import {
experimentArchivedRunsRoute,
experimentRunsRoute,
PROJECT_DETAIL_ROUTES,
projectExperimentArchivedRunsRoute,
projectExperimentRunsRoute,
} from '~/routes';
import { usePipelinesAPI } from '~/concepts/pipelines/context';
import { ExperimentCreated, LastExperimentRuns, LastExperimentRunsStarted } from './renderUtils';

Expand All @@ -22,7 +28,10 @@ const ExperimentTableRow: React.FC<ExperimentTableRowProps> = ({
actionColumnItems,
}) => {
const { namespace } = usePipelinesAPI();

const location = useLocation();
const projectRoute = !!PROJECT_DETAIL_ROUTES.find((pattern) =>
matchPath(pattern, location.pathname),
);
const isArchived = experiment.storage_state === StorageStateKF.ARCHIVED;

return (
Expand All @@ -34,7 +43,11 @@ const ExperimentTableRow: React.FC<ExperimentTableRowProps> = ({
<Link
to={
isArchived
? experimentArchivedRunsRoute(namespace, experiment.experiment_id)
? projectRoute
? projectExperimentArchivedRunsRoute(namespace, experiment.experiment_id)
: experimentArchivedRunsRoute(namespace, experiment.experiment_id)
: projectRoute
? projectExperimentRunsRoute(namespace, experiment.experiment_id)
: experimentRunsRoute(namespace, experiment.experiment_id)
}
state={{ experiment }}
Expand Down
43 changes: 43 additions & 0 deletions frontend/src/concepts/projects/ProjectsContext.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import * as React from 'react';
import { useSearchParams } from 'react-router-dom';
import { useProjects } from '~/api';
import { FetchState } from '~/utilities/useFetchState';
import { KnownLabels, ProjectKind } from '~/k8sTypes';
import { useDashboardNamespace } from '~/redux/selectors';
import { getDisplayNameFromK8sResource } from '~/concepts/k8s/utils';
import { useBrowserStorage } from '~/components/browserStorage';
import { isAvailableProject } from './utils';

const projectSorter = (projectA: ProjectKind, projectB: ProjectKind) =>
Expand All @@ -28,6 +30,7 @@ type ProjectsContextType = {
// ...the rest of the state variables
loaded: ProjectFetchState[1];
loadError: ProjectFetchState[2];
altProjectNav: boolean;
};

export const ProjectsContext = React.createContext<ProjectsContextType>({
Expand All @@ -39,12 +42,20 @@ export const ProjectsContext = React.createContext<ProjectsContextType>({
loaded: false,
loadError: new Error('Not in project provider'),
waitForProject: () => Promise.resolve(),
altProjectNav: false,
});

/** Allow for name to be not passed; won't match, but ease of use. */
type GetByName = (name?: string) => Parameters<Array<ProjectKind>['find']>[0];
export const byName: GetByName = (name) => (project) => project.metadata.name === name;

const ALT_NAV_PARAM = 'altNav';
const POC_SESSION_KEY = 'odh-poc-flags';

type PocConfigType = {
altNav?: boolean;
};

type ProjectsProviderProps = {
children: React.ReactNode;
};
Expand All @@ -54,6 +65,36 @@ const ProjectsContextProvider: React.FC<ProjectsProviderProps> = ({ children })
React.useState<ProjectsContextType['preferredProject']>(null);
const [projectData, loaded, loadError] = useProjects();
const { dashboardNamespace } = useDashboardNamespace();
const [searchParams, setSearchParams] = useSearchParams();
const [altProjectNav, setAltProjectNav] = React.useState<boolean>(false);
const firstLoad = React.useRef(true);
const [pocConfig, setPocConfig] = useBrowserStorage<PocConfigType | null>(
POC_SESSION_KEY,
null,
true,
true,
);

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

React.useEffect(() => {
if (searchParams.has(ALT_NAV_PARAM)) {
const updated = searchParams.get(ALT_NAV_PARAM) === 'true';
setAltProjectNav(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 { projects, modelServingProjects, nonActiveProjects } = React.useMemo(
() =>
Expand Down Expand Up @@ -126,6 +167,7 @@ const ProjectsContextProvider: React.FC<ProjectsProviderProps> = ({ children })
loaded,
loadError,
waitForProject,
altProjectNav,
}),
[
projects,
Expand All @@ -136,6 +178,7 @@ const ProjectsContextProvider: React.FC<ProjectsProviderProps> = ({ children })
loaded,
loadError,
waitForProject,
altProjectNav,
],
);

Expand Down
27 changes: 27 additions & 0 deletions frontend/src/pages/ComingSoonPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as React from 'react';
import EmptyDetailsView from '~/components/EmptyDetailsView';
import ApplicationsPage from '~/pages/ApplicationsPage';
import TitleWithIcon from '~/concepts/design/TitleWithIcon';
import { ProjectObjectType } from '~/concepts/design/utils';

type ComingSoonPageProps = {
title: string;
objectType?: ProjectObjectType;
};

const ComingSoonPage: React.FC<ComingSoonPageProps> = ({ title, objectType }) => (
<ApplicationsPage
loaded
empty
title={objectType ? <TitleWithIcon title={title} objectType={objectType} /> : title}
emptyStatePage={
<EmptyDetailsView
title="This page is coming soon."
description="Not yet implemented"
imageAlt="coming soon"
/>
}
/>
);

export default ComingSoonPage;
48 changes: 42 additions & 6 deletions frontend/src/pages/projects/ProjectViewRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,25 @@ import ProjectModelMetricsConfigurationPage from '~/pages/modelServing/screens/p
import ProjectModelMetricsPage from '~/pages/modelServing/screens/projects/ProjectModelMetricsPage';
import ProjectInferenceExplainabilityWrapper from '~/pages/modelServing/screens/projects/ProjectInferenceExplainabilityWrapper';
import { SupportedArea, useIsAreaAvailable } from '~/concepts/areas';
import ProjectDetails from './screens/detail/ProjectDetails';
import ProjectView from './screens/projects/ProjectView';
import ProjectDetailsContextProvider from './ProjectDetailsContext';
import SpawnerPage from './screens/spawner/SpawnerPage';
import ProjectExperimentRuns from '~/pages/projects/screens/detail/experiments/ProjectExperimentRuns';
import ProjectExperiments from '~/pages/projects/screens/detail/experiments/ProjectExperiments';
import ProjectOverview from '~/pages/projects/screens/detail/overview/ProjectOverview';
import NotebookList from '~/pages/projects/screens/detail/notebooks/NotebookList';
import ExperimentContextProvider from '~/pages/pipelines/global/experiments/ExperimentContext';
import PipelineAvailabilityLoader from '~/pages/pipelines/global/pipelines/PipelineAvailabilityLoader';
import PipelinesSection from '~/pages/projects/screens/detail/pipelines/PipelinesSection';
import ModelServingPlatform from '~/pages/modelServing/screens/projects/ModelServingPlatform';
import ProjectSharing from '~/pages/projects/projectSharing/ProjectSharing';
import StorageList from '~/pages/projects/screens/detail/storage/StorageList';
import ConnectionsList from '~/pages/projects/screens/detail/connections/ConnectionsList';
import ExecutionsSection from '~/pages/projects/screens/detail/executions/ExecutionsSection';
import ArtifactsSection from '~/pages/projects/screens/detail/artifacts/ArtifactsSection';
import DistributedWorkloadsSection from '~/pages/projects/screens/detail/distributedWorkloads/DistributedWorkloadsSection';
import EditSpawnerPage from './screens/spawner/EditSpawnerPage';
import SpawnerPage from './screens/spawner/SpawnerPage';
import ProjectDetailsContextProvider from './ProjectDetailsContext';
import ProjectView from './screens/projects/ProjectView';
import ProjectDetails from './screens/detail/ProjectDetails';

const ProjectViewRoutes: React.FC = () => {
const [modelMetricsEnabled] = useModelMetricsEnabled();
Expand All @@ -24,8 +38,30 @@ const ProjectViewRoutes: React.FC = () => {
return (
<ProjectsRoutes>
<Route path="/" element={<ProjectView />} />
<Route path="/:namespace/*" element={<ProjectDetailsContextProvider />}>
<Route index element={<ProjectDetails />} />
<Route path="/:namespace" element={<ProjectDetailsContextProvider />}>
<Route element={<ProjectDetails />}>
<Route index element={<ProjectOverview />} />
<Route path="overview" element={<ProjectOverview />} />
<Route path="workbenches" element={<NotebookList />} />
<Route path="experiments-and-runs" element={<PipelineAvailabilityLoader />}>
<Route index element={<ProjectExperiments />} />
<Route path=":tab?">
<Route index element={<ProjectExperiments />} />
<Route path=":experimentId" element={<ExperimentContextProvider />}>
<Route index element={<ProjectExperimentRuns />} />
<Route path=":runTab" element={<ProjectExperimentRuns />} />
</Route>
</Route>
</Route>
<Route path="executions" element={<ExecutionsSection />} />
<Route path="artifacts" element={<ArtifactsSection />} />
<Route path="distributed-workloads" element={<DistributedWorkloadsSection />} />
<Route path="model-deployments" element={<ModelServingPlatform />} />
<Route path="pipelines" element={<PipelinesSection />} />
<Route path="connections" element={<ConnectionsList />} />
<Route path="cluster-storages" element={<StorageList />} />
<Route path="permissions" element={<ProjectSharing />} />
</Route>
<Route path="spawner" element={<SpawnerPage />} />
<Route path="spawner/:notebookName" element={<EditSpawnerPage />} />
{modelMetricsEnabled && (
Expand Down
Loading

0 comments on commit ace063a

Please sign in to comment.