From 3658290820e4cc97b9f0918d8bcccea12bb9079d Mon Sep 17 00:00:00 2001 From: Dallas <5322142+gitdallas@users.noreply.github.com> Date: Fri, 8 Nov 2024 12:43:37 -0600 Subject: [PATCH 1/2] fix(12782): display actual error msg for failed mr creation (#3444) Signed-off-by: gitdallas <5322142+gitdallas@users.noreply.github.com> --- backend/src/routes/api/modelRegistries/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/src/routes/api/modelRegistries/index.ts b/backend/src/routes/api/modelRegistries/index.ts index 1e2bccc961..e4897a9fa6 100644 --- a/backend/src/routes/api/modelRegistries/index.ts +++ b/backend/src/routes/api/modelRegistries/index.ts @@ -1,6 +1,7 @@ import { FastifyReply, FastifyRequest } from 'fastify'; import { secureAdminRoute } from '../../../utils/route-security'; import { KubeFastifyInstance, ModelRegistryKind, RecursivePartial } from '../../../types'; +import createError from 'http-errors'; import { createModelRegistryAndSecret, deleteModelRegistryAndSecret, @@ -61,7 +62,9 @@ export default async (fastify: KubeFastifyInstance): Promise => { modelRegistryNamespace, databasePassword, !!dryRun, - ); + ).catch((e) => { + throw createError(e.statusCode, e?.body?.message); + }); } catch (e) { fastify.log.error( `ModelRegistry ${modelRegistry.metadata.name} could not be created, ${ From e85d7a1f7259689acec1152603ab8c44d601b741 Mon Sep 17 00:00:00 2001 From: jpuzz0 <96431149+jpuzz0@users.noreply.github.com> Date: Fri, 8 Nov 2024 17:39:35 -0500 Subject: [PATCH 2/2] [RHOAIENG-12866] entire project context refreshed when individual resources are deleted (#3437) --- .../mocked/projects/dataConnection.cy.ts | 13 -- .../ServingRuntimeTableExpandedSection.tsx | 5 +- .../pages/projects/ProjectDetailsContext.tsx | 41 +----- .../screens/detail/ProjectDetails.tsx | 130 +++++++++--------- .../data-connections/DataConnectionsList.tsx | 27 ++-- .../screens/detail/notebooks/NotebookList.tsx | 20 +-- .../screens/detail/storage/StorageList.tsx | 14 +- .../screens/spawner/SpawnerFooter.tsx | 16 ++- 8 files changed, 115 insertions(+), 151 deletions(-) diff --git a/frontend/src/__tests__/cypress/cypress/tests/mocked/projects/dataConnection.cy.ts b/frontend/src/__tests__/cypress/cypress/tests/mocked/projects/dataConnection.cy.ts index c9ee99d261..16e89b3649 100644 --- a/frontend/src/__tests__/cypress/cypress/tests/mocked/projects/dataConnection.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/tests/mocked/projects/dataConnection.cy.ts @@ -171,12 +171,6 @@ describe('Data connections', () => { .findSelectOption('Test Notebook') .click(); editDataConnectionModal.findNotebookRestartAlert().should('exist'); - cy.interceptK8sList( - NotebookModel, - mockK8sResourceList([ - mockNotebookK8sResource({ envFrom: [{ secretRef: { name: 'test-secret' } }] }), - ]), - ).as('addConnectedWorkbench'); cy.interceptK8s('PUT', SecretModel, mockSecretK8sResource({})).as('editDataConnection'); editDataConnectionModal.findSubmitButton().click(); @@ -212,13 +206,6 @@ describe('Data connections', () => { cy.get('@editDataConnection.all').then((interceptions) => { expect(interceptions).to.have.length(2); //1 dry run request and 1 actual request }); - - cy.wait('@addConnectedWorkbench').then(() => { - projectDetails - .getDataConnectionRow('Test Secret') - .findWorkbenchConnection() - .should('have.text', 'Test Notebook'); - }); }); it('Delete connection', () => { initIntercepts({}); diff --git a/frontend/src/pages/modelServing/screens/projects/ModelMeshSection/ServingRuntimeTableExpandedSection.tsx b/frontend/src/pages/modelServing/screens/projects/ModelMeshSection/ServingRuntimeTableExpandedSection.tsx index 46d0039493..269cffb87d 100644 --- a/frontend/src/pages/modelServing/screens/projects/ModelMeshSection/ServingRuntimeTableExpandedSection.tsx +++ b/frontend/src/pages/modelServing/screens/projects/ModelMeshSection/ServingRuntimeTableExpandedSection.tsx @@ -28,8 +28,7 @@ const ServingRuntimeTableExpandedSection: React.FC { const { - inferenceServices: { data: inferenceServices }, - refreshAllProjectData, + inferenceServices: { data: inferenceServices, refresh: refreshInferenceServices }, } = React.useContext(ProjectDetailsContext); const modelInferenceServices = getInferenceServiceFromServingRuntime(inferenceServices, obj); @@ -55,7 +54,7 @@ const ServingRuntimeTableExpandedSection: React.FC { - refreshAllProjectData(); + refreshInferenceServices(); onClose(); }} /> diff --git a/frontend/src/pages/projects/ProjectDetailsContext.tsx b/frontend/src/pages/projects/ProjectDetailsContext.tsx index d275017f67..a461c42166 100644 --- a/frontend/src/pages/projects/ProjectDetailsContext.tsx +++ b/frontend/src/pages/projects/ProjectDetailsContext.tsx @@ -37,7 +37,6 @@ import useConnections from './screens/detail/connections/useConnections'; type ProjectDetailsContextType = { currentProject: ProjectKind; - refreshAllProjectData: () => void; filterTokens: (servingRuntime?: string) => SecretKind[]; notebooks: ContextResourceData; pvcs: ContextResourceData; @@ -54,10 +53,7 @@ type ProjectDetailsContextType = { }; export const ProjectDetailsContext = React.createContext({ - // We never will get into a case without a project, so fudge the default value - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - currentProject: null as unknown as ProjectKind, - refreshAllProjectData: () => undefined, + currentProject: { apiVersion: '', kind: '', metadata: { name: '' } }, filterTokens: () => [], notebooks: DEFAULT_CONTEXT_DATA, pvcs: DEFAULT_CONTEXT_DATA, @@ -99,39 +95,6 @@ const ProjectDetailsContextProvider: React.FC = () => { const projectSharingRB = useContextResourceData(useProjectSharing(namespace)); const groups = useGroups(); - const notebookRefresh = notebooks.refresh; - const pvcRefresh = pvcs.refresh; - const dataConnectionRefresh = dataConnections.refresh; - const connectionRefresh = connections.refresh; - const servingRuntimeRefresh = servingRuntimes.refresh; - const servingRuntimeTemplateOrderRefresh = servingRuntimeTemplateOrder.refresh; - const servingRuntimeTemplateDisablementRefresh = servingRuntimeTemplateDisablement.refresh; - const inferenceServiceRefresh = inferenceServices.refresh; - const projectSharingRefresh = projectSharingRB.refresh; - const refreshAllProjectData = React.useCallback(() => { - notebookRefresh(); - setTimeout(notebookRefresh, 2000); - pvcRefresh(); - dataConnectionRefresh(); - connectionRefresh(); - servingRuntimeRefresh(); - inferenceServiceRefresh(); - projectSharingRefresh(); - - servingRuntimeTemplateOrderRefresh(); - servingRuntimeTemplateDisablementRefresh(); - }, [ - notebookRefresh, - pvcRefresh, - dataConnectionRefresh, - connectionRefresh, - servingRuntimeRefresh, - servingRuntimeTemplateOrderRefresh, - servingRuntimeTemplateDisablementRefresh, - inferenceServiceRefresh, - projectSharingRefresh, - ]); - const filterTokens = React.useCallback( (servingRuntimeName?: string): SecretKind[] => { if (!namespace || !servingRuntimeName) { @@ -167,7 +130,6 @@ const ProjectDetailsContextProvider: React.FC = () => { servingRuntimeTemplateOrder, servingRuntimeTemplateDisablement, inferenceServices, - refreshAllProjectData, filterTokens, serverSecrets, projectSharingRB, @@ -185,7 +147,6 @@ const ProjectDetailsContextProvider: React.FC = () => { servingRuntimeTemplateOrder, servingRuntimeTemplateDisablement, inferenceServices, - refreshAllProjectData, filterTokens, serverSecrets, projectSharingRB, diff --git a/frontend/src/pages/projects/screens/detail/ProjectDetails.tsx b/frontend/src/pages/projects/screens/detail/ProjectDetails.tsx index a604ea480f..2e024443d4 100644 --- a/frontend/src/pages/projects/screens/detail/ProjectDetails.tsx +++ b/frontend/src/pages/projects/screens/detail/ProjectDetails.tsx @@ -53,72 +53,6 @@ const ProjectDetails: React.FC = () => { useCheckLogoutParams(); - const content = () => ( - }, - { id: ProjectSectionID.WORKBENCHES, title: 'Workbenches', component: }, - ...(pipelinesEnabled - ? [ - { - id: ProjectSectionID.PIPELINES, - title: 'Pipelines', - component: , - }, - ] - : []), - ...(modelServingEnabled - ? [ - { - id: ProjectSectionID.MODEL_SERVER, - title: 'Models', - component: , - }, - ] - : []), - { - id: ProjectSectionID.CLUSTER_STORAGES, - title: 'Cluster storage', - component: , - }, - ...(connectionTypesEnabled - ? [ - { - id: ProjectSectionID.CONNECTIONS, - title: 'Connections', - component: , - }, - ] - : [ - { - id: ProjectSectionID.DATA_CONNECTIONS, - title: 'Data connections', - component: , - }, - ]), - ...(projectSharingEnabled && allowCreate - ? [ - { - id: ProjectSectionID.PERMISSIONS, - title: 'Permissions', - component: , - }, - ] - : []), - ...(biasMetricsAreaAvailable && allowCreate - ? [ - { - id: ProjectSectionID.SETTINGS, - title: 'Settings', - component: , - }, - ] - : []), - ]} - /> - ); - return ( { empty={false} headerAction={} > - {content()} + }, + { id: ProjectSectionID.WORKBENCHES, title: 'Workbenches', component: }, + ...(pipelinesEnabled + ? [ + { + id: ProjectSectionID.PIPELINES, + title: 'Pipelines', + component: , + }, + ] + : []), + ...(modelServingEnabled + ? [ + { + id: ProjectSectionID.MODEL_SERVER, + title: 'Models', + component: , + }, + ] + : []), + { + id: ProjectSectionID.CLUSTER_STORAGES, + title: 'Cluster storage', + component: , + }, + ...(connectionTypesEnabled + ? [ + { + id: ProjectSectionID.CONNECTIONS, + title: 'Connections', + component: , + }, + ] + : [ + { + id: ProjectSectionID.DATA_CONNECTIONS, + title: 'Data connections', + component: , + }, + ]), + ...(projectSharingEnabled && allowCreate + ? [ + { + id: ProjectSectionID.PERMISSIONS, + title: 'Permissions', + component: , + }, + ] + : []), + ...(biasMetricsAreaAvailable && allowCreate + ? [ + { + id: ProjectSectionID.SETTINGS, + title: 'Settings', + component: , + }, + ] + : []), + ]} + /> ); }; diff --git a/frontend/src/pages/projects/screens/detail/data-connections/DataConnectionsList.tsx b/frontend/src/pages/projects/screens/detail/data-connections/DataConnectionsList.tsx index 34a8759b1d..8929b8d9df 100644 --- a/frontend/src/pages/projects/screens/detail/data-connections/DataConnectionsList.tsx +++ b/frontend/src/pages/projects/screens/detail/data-connections/DataConnectionsList.tsx @@ -13,12 +13,21 @@ import DataConnectionsTable from './DataConnectionsTable'; const DataConnectionsList: React.FC = () => { const { - dataConnections: { data: connections, loaded, error }, - refreshAllProjectData, + notebooks: { refresh: refreshNotebooks }, + dataConnections: { + data: dataConnections, + loaded: dataConnectionsLoaded, + error: dataConnectionsError, + refresh: refreshDataConnections, + }, } = React.useContext(ProjectDetailsContext); const [open, setOpen] = React.useState(false); + const isDataConnectionsEmpty = dataConnections.length === 0; - const isDataConnectionsEmpty = connections.length === 0; + const refresh = () => { + refreshDataConnections(); + refreshNotebooks(); + }; return ( <> @@ -55,9 +64,9 @@ const DataConnectionsList: React.FC = () => { ] : undefined } - isLoading={!loaded} + isLoading={!dataConnectionsLoaded} isEmpty={isDataConnectionsEmpty} - loadError={error} + loadError={dataConnectionsError} emptyState={ { /> } > - {!isDataConnectionsEmpty ? ( - - ) : null} + {!isDataConnectionsEmpty && ( + + )} {open ? ( { if (submitted) { - refreshAllProjectData(); + refresh(); } setOpen(false); }} diff --git a/frontend/src/pages/projects/screens/detail/notebooks/NotebookList.tsx b/frontend/src/pages/projects/screens/detail/notebooks/NotebookList.tsx index d363a1c8aa..93c82de3a8 100644 --- a/frontend/src/pages/projects/screens/detail/notebooks/NotebookList.tsx +++ b/frontend/src/pages/projects/screens/detail/notebooks/NotebookList.tsx @@ -16,21 +16,25 @@ import NotebookTable from './NotebookTable'; const NotebookList: React.FC = () => { const { currentProject, - notebooks: { data: notebookStates, loaded, error: loadError }, - refreshAllProjectData: refresh, + notebooks: { + data: notebooks, + loaded: notebooksLoaded, + error: notebooksError, + refresh: refreshNotebooks, + }, } = React.useContext(ProjectDetailsContext); const navigate = useNavigate(); const projectName = currentProject.metadata.name; - const isNotebooksEmpty = notebookStates.length === 0; + const isNotebooksEmpty = notebooks.length === 0; useRefreshInterval(FAST_POLL_INTERVAL, () => - notebookStates + notebooks .filter((notebookState) => notebookState.isStarting || notebookState.isStopping) .forEach((notebookState) => notebookState.refresh()), ); useRefreshInterval(POLL_INTERVAL, () => - notebookStates + notebooks .filter((notebookState) => !notebookState.isStarting && !notebookState.isStopping) .forEach((notebookState) => notebookState.refresh()), ); @@ -63,8 +67,8 @@ const NotebookList: React.FC = () => { Create workbench , ]} - isLoading={!loaded} - loadError={loadError} + isLoading={!notebooksLoaded} + loadError={notebooksError} isEmpty={isNotebooksEmpty} emptyState={ { } > {!isNotebooksEmpty ? ( - + ) : null} ); diff --git a/frontend/src/pages/projects/screens/detail/storage/StorageList.tsx b/frontend/src/pages/projects/screens/detail/storage/StorageList.tsx index 9a9e36cade..03c4d41f73 100644 --- a/frontend/src/pages/projects/screens/detail/storage/StorageList.tsx +++ b/frontend/src/pages/projects/screens/detail/storage/StorageList.tsx @@ -14,12 +14,16 @@ import ClusterStorageModal from './ClusterStorageModal'; const StorageList: React.FC = () => { const [isOpen, setOpen] = React.useState(false); const { - pvcs: { data: pvcs, loaded, error: loadError }, - refreshAllProjectData: refresh, + notebooks: { refresh: refreshNotebooks }, + pvcs: { data: pvcs, loaded: pvcsLoaded, error: pvcsError, refresh: refreshPvcs }, } = React.useContext(ProjectDetailsContext); - const isPvcsEmpty = pvcs.length === 0; + const refresh = () => { + refreshPvcs(); + refreshNotebooks(); + }; + return ( <> { Add cluster storage , ]} - isLoading={!loaded} + isLoading={!pvcsLoaded} isEmpty={isPvcsEmpty} - loadError={loadError} + loadError={pvcsError} emptyState={ = ({ } = useAppContext(); const tolerationSettings = notebookController?.notebookTolerationSettings; const { - notebooks: { data }, - dataConnections: { data: existingDataConnections }, - connections: { data: projectConnections }, - refreshAllProjectData, + notebooks: { data: notebooks, refresh: refreshNotebooks }, + dataConnections: { data: existingDataConnections, refresh: refreshDataConnections }, + connections: { data: projectConnections, refresh: refreshConnections }, } = React.useContext(ProjectDetailsContext); const { notebookName } = useParams(); - const notebookState = data.find( + const notebookState = notebooks.find( (currentNotebookState) => currentNotebookState.notebook.metadata.name === notebookName, ); const editNotebook = notebookState?.notebook; @@ -118,8 +117,13 @@ const SpawnerFooter: React.FC = ({ outcome: TrackingOutcome.submit, success: true, }; + fireFormTrackingEvent(`Workbench ${type === 'created' ? 'Created' : 'Updated'}`, tep); - refreshAllProjectData(); + + refreshNotebooks(); + refreshDataConnections(); + refreshConnections(); + navigate(`/projects/${projectName}?section=${ProjectSectionID.WORKBENCHES}`); }; const handleError = (e: K8sStatusError) => {