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

Show non-DS projects in the project table #1643

Merged
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
1 change: 0 additions & 1 deletion backend/src/routes/api/namespaces/namespaceUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ export const applyNamespaceChange = async (
case NamespaceApplicationCase.DSG_CREATION:
labels = {
'opendatahub.io/dashboard': 'true',
'modelmesh-enabled': 'true',
};
break;
case NamespaceApplicationCase.MODEL_SERVING_PROMOTION:
Expand Down
3 changes: 0 additions & 3 deletions frontend/src/api/k8s/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ export const getProjects = (withLabel?: string): Promise<ProjectKind[]> =>
queryOptions: withLabel ? { queryParams: { labelSelector: withLabel } } : undefined,
}).then((listResource) => listResource.items);

export const getDSGProjects = (): Promise<ProjectKind[]> =>
getProjects(LABEL_SELECTOR_DASHBOARD_RESOURCE);

export const createProject = (
username: string,
displayName: string,
Expand Down
48 changes: 34 additions & 14 deletions frontend/src/concepts/projects/ProjectsContext.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import * as React from 'react';
import useFetchState, { FetchState } from '~/utilities/useFetchState';
import { getDSGProjects } from '~/api';
import { getProjects } from '~/api';
import { KnownLabels, ProjectKind } from '~/k8sTypes';
import { useDashboardNamespace } from '~/redux/selectors';

type ProjectFetchState = FetchState<ProjectKind[]>;
type ProjectsContext = {
projects: ProjectKind[];
dataScienceProjects: ProjectKind[];
modelServingProjects: ProjectKind[];
/** eg. Terminating state, etc */
nonActiveProjects: ProjectKind[];
Expand All @@ -26,6 +28,7 @@ type ProjectsContext = {

export const ProjectsContext = React.createContext<ProjectsContext>({
projects: [],
dataScienceProjects: [],
modelServingProjects: [],
nonActiveProjects: [],
preferredProject: null,
Expand All @@ -44,39 +47,55 @@ type ProjectsProviderProps = {
};

const ProjectsContextProvider: React.FC<ProjectsProviderProps> = ({ children }) => {
const fetchProjects = React.useCallback(() => getDSGProjects(), []);
const fetchProjects = React.useCallback(() => getProjects(), []);
DaoDaoNoCode marked this conversation as resolved.
Show resolved Hide resolved
const [preferredProject, setPreferredProject] =
React.useState<ProjectsContext['preferredProject']>(null);
const [projectData, loaded, loadError, refreshProjects] = useFetchState<ProjectKind[]>(
fetchProjects,
[],
);
const { dashboardNamespace } = useDashboardNamespace();

const { projects, modelServingProjects, nonActiveProjects } = React.useMemo(
const { projects, dataScienceProjects, modelServingProjects, nonActiveProjects } = React.useMemo(
() =>
projectData.reduce<{
projects: ProjectKind[];
dataScienceProjects: ProjectKind[];
modelServingProjects: ProjectKind[];
nonActiveProjects: ProjectKind[];
}>(
(states, project) => {
if (project.status?.phase === 'Active') {
// Project that is active
states.projects.push(project);
if (project.metadata.labels?.[KnownLabels.MODEL_SERVING_PROJECT]) {
// Model Serving active projects
states.modelServingProjects.push(project);
if (
!(
project.metadata.name.startsWith('openshift-') ||
project.metadata.name.startsWith('kube-') ||
project.metadata.name === 'default' ||
project.metadata.name === 'system' ||
project.metadata.name === 'openshift' ||
project.metadata.name === dashboardNamespace
)
) {
if (project.status?.phase === 'Active') {
// Project that is active
states.projects.push(project);
if (project.metadata.labels?.[KnownLabels.DASHBOARD_RESOURCE]) {
states.dataScienceProjects.push(project);
}
if (project.metadata.labels?.[KnownLabels.MODEL_SERVING_PROJECT]) {
// Model Serving active projects
states.modelServingProjects.push(project);
}
} else {
// Non 'Active' -- aka terminating
states.nonActiveProjects.push(project);
}
} else {
// Non 'Active' -- aka terminating
states.nonActiveProjects.push(project);
}

return states;
},
{ projects: [], modelServingProjects: [], nonActiveProjects: [] },
{ projects: [], dataScienceProjects: [], modelServingProjects: [], nonActiveProjects: [] },
),
[projectData],
[projectData, dashboardNamespace],
);

const refresh = React.useCallback<ProjectsContext['refresh']>(
Expand Down Expand Up @@ -114,6 +133,7 @@ const ProjectsContextProvider: React.FC<ProjectsProviderProps> = ({ children })
<ProjectsContext.Provider
value={{
projects,
dataScienceProjects,
modelServingProjects,
nonActiveProjects,
preferredProject,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ const ManageServingRuntimeModal: React.FC<ManageServingRuntimeModalProps> = ({
])
.then(() =>
Promise.all<ServingRuntimeKind | string | void>([
...(currentProject.metadata.labels?.['modelmesh-enabled'] && allowCreate
...(currentProject.metadata.labels?.['modelmesh-enabled'] === undefined && allowCreate
? [addSupportModelMeshProject(currentProject.metadata.name)]
: []),
...(editInfo?.servingRuntime
Expand Down
12 changes: 7 additions & 5 deletions frontend/src/pages/projects/screens/projects/ProjectListView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import * as React from 'react';
import { Button, ButtonVariant, ToolbarItem } from '@patternfly/react-core';
import { useNavigate } from 'react-router-dom';
import Table from '~/components/table/Table';
import useTableColumnSort from '~/components/table/useTableColumnSort';
import SearchField, { SearchType } from '~/pages/projects/components/SearchField';
import { ProjectKind } from '~/k8sTypes';
import { getProjectDisplayName, getProjectOwner } from '~/pages/projects/utils';
import { useAppContext } from '~/app/AppContext';
import LaunchJupyterButton from '~/pages/projects/screens/projects/LaunchJupyterButton';
import { ProjectsContext } from '~/concepts/projects/ProjectsContext';
import { ProjectScope } from '~/pages/projects/types';
import NewProjectButton from './NewProjectButton';
import { columns } from './tableData';
import ProjectTableRow from './ProjectTableRow';
Expand All @@ -17,16 +17,18 @@ import ManageProjectModal from './ManageProjectModal';

type ProjectListViewProps = {
allowCreate: boolean;
scope: ProjectScope;
};

const ProjectListView: React.FC<ProjectListViewProps> = ({ allowCreate }) => {
const ProjectListView: React.FC<ProjectListViewProps> = ({ allowCreate, scope }) => {
const { dashboardConfig } = useAppContext();
const { projects: unfilteredProjects, refresh } = React.useContext(ProjectsContext);
const { projects, dataScienceProjects, refresh } = React.useContext(ProjectsContext);
const navigate = useNavigate();
const [searchType, setSearchType] = React.useState<SearchType>(SearchType.NAME);
const [search, setSearch] = React.useState('');
const sort = useTableColumnSort<ProjectKind>(columns, 0);
const filteredProjects = sort.transformData(unfilteredProjects).filter((project) => {
const filteredProjects = (
scope === ProjectScope.ALL_PROJECTS ? projects : dataScienceProjects
).filter((project) => {
if (!search) {
return true;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import * as React from 'react';
import { Select, SelectOption } from '@patternfly/react-core';
import { isInEnum } from '~/typeHelpers';
import { ProjectScope } from '~/pages/projects/types';

type ProjectScopeSelectProps = {
selection: ProjectScope;
setSelection: (selection: ProjectScope) => void;
};

const isProjectScope = isInEnum(ProjectScope);

const ProjectScopeSelect: React.FC<ProjectScopeSelectProps> = ({ selection, setSelection }) => {
const [isOpen, setOpen] = React.useState(false);
return (
<Select
selections={selection}
width="200px"
onToggle={(open) => setOpen(open)}
onSelect={(_, selection) => {
if (isProjectScope(selection)) {
setSelection(selection);
}
setOpen(false);
}}
isOpen={isOpen}
>
{[
<SelectOption
key={ProjectScope.DS_PROJECTS}
value={ProjectScope.DS_PROJECTS}
description="Only projects created in the Data Science Dashboard are displayed."
/>,
<SelectOption
key={ProjectScope.ALL_PROJECTS}
value={ProjectScope.ALL_PROJECTS}
description="All available projects created in the Data Science Dashboard and OpenShift are displayed."
/>,
]}
</Select>
);
};

export default ProjectScopeSelect;
19 changes: 14 additions & 5 deletions frontend/src/pages/projects/screens/projects/ProjectTableRow.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from 'react';
import { Text, TextVariants, Timestamp } from '@patternfly/react-core';
import { Flex, Label, Text, TextVariants, Timestamp, Tooltip } from '@patternfly/react-core';
import { ActionsColumn, Td, Tr } from '@patternfly/react-table';
import { ProjectKind } from '~/k8sTypes';
import { KnownLabels, ProjectKind } from '~/k8sTypes';
import useProjectTableRowItems from '~/pages/projects/screens/projects/useProjectTableRowItems';
import useProjectNotebookStates from '~/pages/projects/notebook/useProjectNotebookStates';
import ListNotebookState from '~/pages/projects/notebook/ListNotebookState';
Expand All @@ -28,9 +28,18 @@ const ProjectTableRow: React.FC<ProjectTableRowProps> = ({
return (
<Tr>
<Td dataLabel="Name">
<ResourceNameTooltip resource={project}>
<ProjectLink project={project} />
</ResourceNameTooltip>
<Flex spaceItems={{ default: 'spaceItemsXs' }} alignItems={{ default: 'alignItemsCenter' }}>
{project.metadata.labels?.[KnownLabels.DASHBOARD_RESOURCE] && (
<Tooltip content="Data Science">
<Label isCompact color="green">
DS
</Label>
</Tooltip>
)}
<ResourceNameTooltip resource={project}>
<ProjectLink project={project} />
</ResourceNameTooltip>
</Flex>
{owner && <Text component={TextVariants.small}>{owner}</Text>}
</Td>
<Td dataLabel="Workbench">
Expand Down
18 changes: 15 additions & 3 deletions frontend/src/pages/projects/screens/projects/ProjectView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import { useAccessReview } from '~/api';
import { AccessReviewResourceAttributes } from '~/k8sTypes';
import { ProjectsContext } from '~/concepts/projects/ProjectsContext';
import useMountProjectRefresh from '~/concepts/projects/useMountProjectRefresh';
import { useBrowserStorage } from '~/components/browserStorage';
import { ProjectScope } from '~/pages/projects/types';
import EmptyProjects from './EmptyProjects';
import ProjectListView from './ProjectListView';
import ProjectScopeSelect from './ProjectScopeSelect';

const accessReviewResource: AccessReviewResourceAttributes = {
group: 'project.openshift.io',
Expand All @@ -14,7 +17,11 @@ const accessReviewResource: AccessReviewResourceAttributes = {
};

const ProjectView: React.FC = () => {
const { projects } = React.useContext(ProjectsContext);
const [scope, setScope] = useBrowserStorage<ProjectScope>(
'odh.dashboard.project.scope',
ProjectScope.DS_PROJECTS,
);
const { projects, dataScienceProjects } = React.useContext(ProjectsContext);
useMountProjectRefresh();
const [allowCreate, rbacLoaded] = useAccessReview(accessReviewResource);

Expand All @@ -26,12 +33,17 @@ const ProjectView: React.FC = () => {
? `View your existing projects${allowCreate ? ' or create new projects' : ''}.`
: undefined
}
headerContent={<ProjectScopeSelect selection={scope} setSelection={setScope} />}
loaded={rbacLoaded}
empty={projects.length === 0}
empty={
scope === ProjectScope.ALL_PROJECTS
? projects.length === 0
: dataScienceProjects.length === 0
}
emptyStatePage={<EmptyProjects allowCreate={allowCreate} />}
provideChildrenPadding
>
<ProjectListView allowCreate={allowCreate} />
<ProjectListView allowCreate={allowCreate} scope={scope} />
</ApplicationsPage>
);
};
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/pages/projects/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,7 @@ export enum ConfigMapCategory {
GENERIC = 'configmap key-value',
UPLOAD = 'configmap upload',
}
export enum ProjectScope {
DS_PROJECTS = 'Data science projects',
ALL_PROJECTS = 'All available projects',
}
5 changes: 5 additions & 0 deletions frontend/src/typeHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,8 @@ export type EitherNotBoth<TypeA, TypeB> = (TypeA & Never<TypeB>) | (TypeB & Neve
export type EitherOrNone<TypeA, TypeB> =
| EitherNotBoth<TypeA, TypeB>
| (Never<TypeA> & Never<TypeB>);

export const isInEnum =
<T extends { [s: string]: unknown }>(e: T) =>
(token: unknown): token is T[keyof T] =>
Object.values(e).includes(token as T[keyof T]);
Loading