From bd833fde576cd7b3d4b187ebdb70a8e3d8cc1054 Mon Sep 17 00:00:00 2001 From: Jeff Puzzo Date: Wed, 17 Apr 2024 15:26:44 -0400 Subject: [PATCH] [RHOAIENG-2986] Artifacts - Initial Infrastructure --- frontend/src/app/AppRoutes.tsx | 10 +++- frontend/src/components/table/TableBase.tsx | 6 +- frontend/src/pages/ApplicationsPage.tsx | 2 +- .../pages/pipelines/GlobalArtifactsRoutes.tsx | 21 +++++++ .../artifacts/ArtifactsListTable.tsx | 59 +++++++++++++++++++ .../artifacts/GlobalArtifactsPage.tsx | 29 +++++++++ .../global/experiments/artifacts/index.ts | 1 + .../artifacts/useGetArtifactsList.ts | 20 +++++++ frontend/src/routes/pipelines/artifacts.ts | 5 ++ frontend/src/routes/pipelines/index.ts | 1 + frontend/src/utilities/NavData.tsx | 46 +++++++++------ 11 files changed, 178 insertions(+), 22 deletions(-) create mode 100644 frontend/src/pages/pipelines/GlobalArtifactsRoutes.tsx create mode 100644 frontend/src/pages/pipelines/global/experiments/artifacts/ArtifactsListTable.tsx create mode 100644 frontend/src/pages/pipelines/global/experiments/artifacts/GlobalArtifactsPage.tsx create mode 100644 frontend/src/pages/pipelines/global/experiments/artifacts/index.ts create mode 100644 frontend/src/pages/pipelines/global/experiments/artifacts/useGetArtifactsList.ts create mode 100644 frontend/src/routes/pipelines/artifacts.ts diff --git a/frontend/src/app/AppRoutes.tsx b/frontend/src/app/AppRoutes.tsx index d337dac202..562be3bd4a 100644 --- a/frontend/src/app/AppRoutes.tsx +++ b/frontend/src/app/AppRoutes.tsx @@ -4,7 +4,12 @@ import { InvalidArgoDeploymentAlert } from '~/concepts/pipelines/content/Invalid import ApplicationsPage from '~/pages/ApplicationsPage'; import UnauthorizedError from '~/pages/UnauthorizedError'; import { useUser } from '~/redux/selectors'; -import { globExperimentsAll, globPipelineRunsAll, globPipelinesAll } from '~/routes'; +import { + globArtifactsAll, + globExperimentsAll, + globPipelineRunsAll, + globPipelinesAll, +} from '~/routes'; import { useCheckJupyterEnabled } from '~/utilities/notebookControllerUtils'; const InstalledApplications = React.lazy( @@ -30,6 +35,8 @@ const GlobalPipelineExperimentRoutes = React.lazy( () => import('../pages/pipelines/GlobalPipelineExperimentsRoutes'), ); +const GlobalArtifactsRoutes = React.lazy(() => import('../pages/pipelines/GlobalArtifactsRoutes')); + const GlobalDistributedWorkloadsRoutes = React.lazy( () => import('../pages/distributedWorkloads/GlobalDistributedWorkloadsRoutes'), ); @@ -91,6 +98,7 @@ const AppRoutes: React.FC = () => { } /> } /> } /> + } /> } /> diff --git a/frontend/src/components/table/TableBase.tsx b/frontend/src/components/table/TableBase.tsx index b7167db593..1b00e9d1d8 100644 --- a/frontend/src/components/table/TableBase.tsx +++ b/frontend/src/components/table/TableBase.tsx @@ -236,7 +236,11 @@ const TableBase = ({ {toolbarContent} {showPagination && ( - + {pagination('top')} )} diff --git a/frontend/src/pages/ApplicationsPage.tsx b/frontend/src/pages/ApplicationsPage.tsx index c581831840..4b2a3eed43 100644 --- a/frontend/src/pages/ApplicationsPage.tsx +++ b/frontend/src/pages/ApplicationsPage.tsx @@ -18,7 +18,7 @@ import { } from '@patternfly/react-core'; type ApplicationsPageProps = { - title: React.ReactNode; + title?: React.ReactNode; breadcrumb?: React.ReactNode; description?: React.ReactNode; loaded: boolean; diff --git a/frontend/src/pages/pipelines/GlobalArtifactsRoutes.tsx b/frontend/src/pages/pipelines/GlobalArtifactsRoutes.tsx new file mode 100644 index 0000000000..0280196852 --- /dev/null +++ b/frontend/src/pages/pipelines/GlobalArtifactsRoutes.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { Navigate, Route } from 'react-router-dom'; + +import ProjectsRoutes from '~/concepts/projects/ProjectsRoutes'; +import GlobalPipelineCoreLoader from '~/pages/pipelines/global/GlobalPipelineCoreLoader'; +import { artifactsBaseRoute } from '~/routes'; +import { GlobalArtifactsPage } from './global/experiments/artifacts'; + +const GlobalArtifactsRoutes: React.FC = () => ( + + } + > + } /> + } /> + + +); + +export default GlobalArtifactsRoutes; diff --git a/frontend/src/pages/pipelines/global/experiments/artifacts/ArtifactsListTable.tsx b/frontend/src/pages/pipelines/global/experiments/artifacts/ArtifactsListTable.tsx new file mode 100644 index 0000000000..a0e30127f3 --- /dev/null +++ b/frontend/src/pages/pipelines/global/experiments/artifacts/ArtifactsListTable.tsx @@ -0,0 +1,59 @@ +import React from 'react'; + +import { + Bullseye, + EmptyState, + EmptyStateBody, + EmptyStateHeader, + EmptyStateIcon, + EmptyStateVariant, + Spinner, +} from '@patternfly/react-core'; +import { ExclamationCircleIcon, PlusCircleIcon } from '@patternfly/react-icons'; + +import { useGetArtifactsList } from './useGetArtifactsList'; + +export const ArtifactsListTable: React.FC = () => { + const [artifacts, isArtifactsLoaded, artifactsError] = useGetArtifactsList(); + + if (artifactsError) { + return ( + + + } + headingLevel="h2" + /> + {artifactsError.message} + + + ); + } + + if (!isArtifactsLoaded) { + return ( + + + + ); + } + + if (!artifacts?.length) { + return ( + + } + headingLevel="h4" + /> + + No artifacts have been generated from experiments within this project. Select a different + project, or execute an experiment from the Experiments and runs page. + + + ); + } + + return <>; +}; diff --git a/frontend/src/pages/pipelines/global/experiments/artifacts/GlobalArtifactsPage.tsx b/frontend/src/pages/pipelines/global/experiments/artifacts/GlobalArtifactsPage.tsx new file mode 100644 index 0000000000..2443af6acc --- /dev/null +++ b/frontend/src/pages/pipelines/global/experiments/artifacts/GlobalArtifactsPage.tsx @@ -0,0 +1,29 @@ +import React from 'react'; + +import { usePipelinesAPI } from '~/concepts/pipelines/context'; +import PipelineServerActions from '~/concepts/pipelines/content/PipelineServerActions'; +import PipelineCoreApplicationPage from '~/pages/pipelines/global/PipelineCoreApplicationPage'; +import EnsureAPIAvailability from '~/concepts/pipelines/EnsureAPIAvailability'; +import EnsureCompatiblePipelineServer from '~/concepts/pipelines/EnsureCompatiblePipelineServer'; +import { artifactsBaseRoute } from '~/routes'; +import { ArtifactsListTable } from './ArtifactsListTable'; + +export const GlobalArtifactsPage: React.FC = () => { + const pipelinesAPI = usePipelinesAPI(); + + return ( + } + getRedirectPath={artifactsBaseRoute} + overrideChildPadding + > + + + + + + + ); +}; diff --git a/frontend/src/pages/pipelines/global/experiments/artifacts/index.ts b/frontend/src/pages/pipelines/global/experiments/artifacts/index.ts new file mode 100644 index 0000000000..0ea273a020 --- /dev/null +++ b/frontend/src/pages/pipelines/global/experiments/artifacts/index.ts @@ -0,0 +1 @@ +export { GlobalArtifactsPage } from './GlobalArtifactsPage'; diff --git a/frontend/src/pages/pipelines/global/experiments/artifacts/useGetArtifactsList.ts b/frontend/src/pages/pipelines/global/experiments/artifacts/useGetArtifactsList.ts new file mode 100644 index 0000000000..647a8d481d --- /dev/null +++ b/frontend/src/pages/pipelines/global/experiments/artifacts/useGetArtifactsList.ts @@ -0,0 +1,20 @@ +import React from 'react'; + +import { usePipelinesAPI } from '~/concepts/pipelines/context'; +import { Artifact, GetArtifactsRequest } from '~/third_party/mlmd'; +import useFetchState, { FetchState } from '~/utilities/useFetchState'; + +export const useGetArtifactsList = ( + refreshRate?: number, +): FetchState => { + const { metadataStoreServiceClient } = usePipelinesAPI(); + + const fetchArtifactsList = React.useCallback(async () => { + const response = await metadataStoreServiceClient.getArtifacts(new GetArtifactsRequest()); + return response.toObject().artifactsList; + }, [metadataStoreServiceClient]); + + return useFetchState(fetchArtifactsList, null, { + refreshRate, + }); +}; diff --git a/frontend/src/routes/pipelines/artifacts.ts b/frontend/src/routes/pipelines/artifacts.ts new file mode 100644 index 0000000000..7fc3437a73 --- /dev/null +++ b/frontend/src/routes/pipelines/artifacts.ts @@ -0,0 +1,5 @@ +export const artifactsRootPath = '/artifacts'; +export const globArtifactsAll = `${artifactsRootPath}/*`; + +export const artifactsBaseRoute = (namespace: string | undefined): string => + !namespace ? artifactsRootPath : `${artifactsRootPath}/${namespace}`; diff --git a/frontend/src/routes/pipelines/index.ts b/frontend/src/routes/pipelines/index.ts index ff446d2ad8..1dda88e0e3 100644 --- a/frontend/src/routes/pipelines/index.ts +++ b/frontend/src/routes/pipelines/index.ts @@ -1,4 +1,5 @@ export * from './global'; export * from './project'; export * from './experiments'; +export * from './artifacts'; export * from './runs'; diff --git a/frontend/src/utilities/NavData.tsx b/frontend/src/utilities/NavData.tsx index 32458e9942..c15cba9560 100644 --- a/frontend/src/utilities/NavData.tsx +++ b/frontend/src/utilities/NavData.tsx @@ -1,7 +1,12 @@ import * as React from 'react'; import { SupportedArea, useIsAreaAvailable } from '~/concepts/areas'; import { useUser } from '~/redux/selectors'; -import { experimentsRootPath, routePipelineRuns, routePipelines } from '~/routes'; +import { + artifactsRootPath, + experimentsRootPath, + routePipelineRuns, + routePipelines, +} from '~/routes'; type NavDataCommon = { id: string; @@ -54,7 +59,7 @@ const useDSPipelinesNav = (): NavDataItem[] => { return []; } - const pipelinesNav: NavDataItem[] = [ + return [ { id: 'pipelines', group: { id: 'pipelines', title: 'Data Science Pipelines' }, @@ -63,24 +68,27 @@ const useDSPipelinesNav = (): NavDataItem[] => { { id: 'global-pipeline-runs', label: 'Runs', href: routePipelineRuns() }, ], }, + ...(isExperimentsAvailable + ? [ + { + id: 'experiments', + group: { id: 'experiments', title: 'Experiments' }, + children: [ + { + id: 'experiments-and-runs', + label: 'Experiments and runs', + href: experimentsRootPath, + }, + { + id: 'artifacts', + label: 'Artifacts', + href: artifactsRootPath, + }, + ], + }, + ] + : []), ]; - - // TODO temporary solution to switch between layout options - remove with https://issues.redhat.com/browse/RHOAIENG-3826 - if (isExperimentsAvailable) { - pipelinesNav.push({ - id: 'experiments', - group: { id: 'experiments', title: 'Experiments' }, - children: [ - { - id: 'experiments-and-runs', - label: 'Experiments and runs', - href: experimentsRootPath, - }, - ], - }); - } - - return pipelinesNav; }; const useDistributedWorkloadsNav = (): NavDataItem[] =>