From 6b9b88202e28f92d7f0993ef6a6a248a0695239e Mon Sep 17 00:00:00 2001 From: ppadti Date: Mon, 27 May 2024 18:12:39 +0530 Subject: [PATCH] Convert templates to use websocket --- .../CustomServingRuntimes.cy.ts | 145 ++++++------------ .../customServingRuntimesUtils.ts | 5 +- .../src/api/k8s/__tests__/templates.spec.ts | 112 ++++++++++---- frontend/src/api/k8s/templates.ts | 51 ++++-- .../modelServing/ModelServingContext.tsx | 21 +-- .../CustomServingRuntimeContext.tsx | 31 ++-- .../CustomServingRuntimeEditTemplate.tsx | 2 +- .../CustomServingRuntimeEnabledToggle.tsx | 2 +- .../CustomServingRuntimeListView.tsx | 2 +- .../CustomServingRuntimeView.tsx | 2 +- .../DeleteCustomServingRuntimeModal.tsx | 2 +- .../customServingRuntimes/useTemplates.ts | 61 -------- .../screens/global/ServeModelButton.tsx | 2 +- .../projects/EmptyMultiModelServingCard.tsx | 2 +- .../projects/EmptySingleModelServingCard.tsx | 2 +- .../screens/projects/ModelServingPlatform.tsx | 4 +- .../ModelServingPlatformButtonAction.tsx | 2 +- .../pages/projects/ProjectDetailsContext.tsx | 17 +- .../overview/serverModels/AddModelFooter.tsx | 2 +- frontend/src/utilities/const.ts | 3 + 20 files changed, 211 insertions(+), 259 deletions(-) delete mode 100644 frontend/src/pages/modelServing/customServingRuntimes/useTemplates.ts diff --git a/frontend/src/__tests__/cypress/cypress/e2e/customServingRuntimes/CustomServingRuntimes.cy.ts b/frontend/src/__tests__/cypress/cypress/e2e/customServingRuntimes/CustomServingRuntimes.cy.ts index 977f76be85..efc2d4909b 100644 --- a/frontend/src/__tests__/cypress/cypress/e2e/customServingRuntimes/CustomServingRuntimes.cy.ts +++ b/frontend/src/__tests__/cypress/cypress/e2e/customServingRuntimes/CustomServingRuntimes.cy.ts @@ -1,4 +1,3 @@ -import { mockK8sResourceList } from '~/__mocks__/mockK8sResourceList'; import { mockServingRuntimeTemplateK8sResource } from '~/__mocks__/mockServingRuntimeTemplateK8sResource'; import { servingRuntimes } from '~/__tests__/cypress/cypress/pages/servingRuntimes'; import { ServingRuntimeAPIProtocol, ServingRuntimePlatform } from '~/types'; @@ -6,10 +5,8 @@ import { deleteModal } from '~/__tests__/cypress/cypress/pages/components/Delete import { mockServingRuntimeK8sResource } from '~/__mocks__/mockServingRuntimeK8sResource'; import { asProductAdminUser, asProjectAdminUser } from '~/__tests__/cypress/cypress/utils/users'; import { pageNotfound } from '~/__tests__/cypress/cypress/pages/pageNotFound'; -import { - customServingRuntimesInitialMock, - customServingRuntimesIntercept, -} from '~/__tests__/cypress/cypress/e2e/customServingRuntimes/customServingRuntimesUtils'; +import { customServingRuntimesIntercept } from '~/__tests__/cypress/cypress/e2e/customServingRuntimes/customServingRuntimesUtils'; +import { TemplateModel } from '~/__tests__/cypress/cypress/utils/models'; const addfilePath = '../../__mocks__/mock-custom-serving-runtime-add.yaml'; const editfilePath = '../../__mocks__/mock-custom-serving-runtime-edit.yaml'; @@ -81,37 +78,20 @@ describe('Custom serving runtimes', () => { servingRuntimes.findSubmitButton().should('be.enabled'); servingRuntimes.findSubmitButton().click(); + cy.wait('@createTemplate'); - cy.wait('@createSingleModelServingRuntime').then((interception) => { - expect(interception.request.url).to.include('?dryRun=All'); - expect(interception.request.body).to.containSubset({ - metadata: { - name: 'template-new', - annotations: { 'openshift.io/display-name': 'New OVMS Server' }, - namespace: 'opendatahub', - }, - }); - }); + cy.wsK8s( + 'ADDED', + TemplateModel, + mockServingRuntimeTemplateK8sResource({ + name: 'template-new', + displayName: 'New OVMS Server', + platforms: [ServingRuntimePlatform.SINGLE], + apiProtocol: ServingRuntimeAPIProtocol.REST, + }), + ); - cy.wait('@createTemplate').then((interception) => { - expect(interception.request.body).to.containSubset({ - metadata: { - annotations: { - 'opendatahub.io/modelServingSupport': '["single"]', - 'opendatahub.io/apiProtocol': 'REST', - }, - }, - objects: [ - { - metadata: { - name: 'template-new', - annotations: { 'openshift.io/display-name': 'New OVMS Server' }, - labels: { 'opendatahub.io/dashboard': 'true' }, - }, - }, - ], - }); - }); + servingRuntimes.getRowById('template-new').shouldBeSingleModel(true); }); it('should add a new multi model serving runtime', () => { @@ -143,35 +123,17 @@ describe('Custom serving runtimes', () => { servingRuntimes.findSubmitButton().should('be.enabled'); servingRuntimes.findSubmitButton().click(); - cy.wait('@createMultiModelServingRuntime').then((interception) => { - expect(interception.request.url).to.include('?dryRun=All'); - expect(interception.request.body.metadata).to.eql({ + cy.wsK8s( + 'ADDED', + TemplateModel, + mockServingRuntimeTemplateK8sResource({ name: 'template-new', - annotations: { 'openshift.io/display-name': 'New OVMS Server' }, - labels: { 'opendatahub.io/dashboard': 'true' }, - namespace: 'opendatahub', - }); - }); + displayName: 'New OVMS Server', + platforms: [ServingRuntimePlatform.MULTI], + }), + ); - cy.wait('@createTemplate').then((interception) => { - expect(interception.request.body).to.containSubset({ - metadata: { - annotations: { - 'opendatahub.io/modelServingSupport': '["multi"]', - 'opendatahub.io/apiProtocol': 'REST', - }, - }, - objects: [ - { - metadata: { - name: 'template-new', - annotations: { 'openshift.io/display-name': 'New OVMS Server' }, - labels: { 'opendatahub.io/dashboard': 'true' }, - }, - }, - ], - }); - }); + servingRuntimes.getRowById('template-new').shouldBeMultiModel(true); }); it('should duplicate a serving runtime', () => { @@ -185,19 +147,6 @@ describe('Custom serving runtimes', () => { 'duplicateTemplate', ); - const ServingRuntimeTemplateMock = mockServingRuntimeTemplateK8sResource({ - name: 'serving-runtime-template-1', - displayName: 'Multi platform', - platforms: [ServingRuntimePlatform.SINGLE], - apiProtocol: ServingRuntimeAPIProtocol.GRPC, - }); - - cy.interceptOdh( - 'GET /api/templates/:namespace', - { path: { namespace: 'opendatahub' } }, - mockK8sResourceList([...customServingRuntimesInitialMock, ServingRuntimeTemplateMock]), - ).as('refreshServingRuntime'); - servingRuntimes.getRowById('template-1').find().findKebabAction('Duplicate').click(); servingRuntimes.findAppTitle().should('have.text', 'Duplicate serving runtime'); cy.url().should('include', '/addServingRuntime'); @@ -210,36 +159,19 @@ describe('Custom serving runtimes', () => { servingRuntimes.findSubmitButton().should('be.enabled'); servingRuntimes.findSubmitButton().click(); - cy.wait('@duplicateServingRuntime').then((interception) => { - expect(interception.request.body.metadata).to.containSubset({ + cy.wsK8s( + 'ADDED', + TemplateModel, + mockServingRuntimeTemplateK8sResource({ name: 'template-1-copy', - annotations: { 'openshift.io/display-name': 'Copy of Multi Platform' }, - namespace: 'opendatahub', - }); - }); - - cy.wait('@duplicateTemplate').then((interception) => { - expect(interception.request.body).to.containSubset({ - metadata: { - annotations: { - 'opendatahub.io/modelServingSupport': '["single"]', - 'opendatahub.io/apiProtocol': 'gRPC', - }, - }, - objects: [ - { - metadata: { - name: 'template-1-copy', - annotations: { 'openshift.io/display-name': 'Copy of Multi Platform' }, - }, - }, - ], - }); - }); - cy.wait('@refreshServingRuntime'); + displayName: 'Copy of Multi platform', + platforms: [ServingRuntimePlatform.SINGLE], + apiProtocol: ServingRuntimeAPIProtocol.GRPC, + }), + ); servingRuntimes - .getRowById('serving-runtime-template-1') + .getRowById('template-1-copy') .shouldHaveAPIProtocol(ServingRuntimeAPIProtocol.GRPC); }); @@ -310,5 +242,16 @@ describe('Custom serving runtimes', () => { deleteModal.findSubmitButton().should('be.enabled').click(); cy.wait('@deleteServingRuntime'); + cy.wsK8s( + 'DELETED', + TemplateModel, + mockServingRuntimeTemplateK8sResource({ + name: 'template-1', + displayName: 'Multi platform', + platforms: [ServingRuntimePlatform.SINGLE], + apiProtocol: ServingRuntimeAPIProtocol.REST, + }), + ); + servingRuntimes.getRowById('template-1').find().should('not.exist'); }); }); diff --git a/frontend/src/__tests__/cypress/cypress/e2e/customServingRuntimes/customServingRuntimesUtils.ts b/frontend/src/__tests__/cypress/cypress/e2e/customServingRuntimes/customServingRuntimesUtils.ts index 2036d8d18b..edddd79d0f 100644 --- a/frontend/src/__tests__/cypress/cypress/e2e/customServingRuntimes/customServingRuntimesUtils.ts +++ b/frontend/src/__tests__/cypress/cypress/e2e/customServingRuntimes/customServingRuntimesUtils.ts @@ -1,7 +1,7 @@ import { mockK8sResourceList } from '~/__mocks__/mockK8sResourceList'; import { mockServingRuntimeTemplateK8sResource } from '~/__mocks__/mockServingRuntimeTemplateK8sResource'; import { ServingRuntimeAPIProtocol, ServingRuntimePlatform } from '~/types'; -import { ProjectModel } from '~/__tests__/cypress/cypress/utils/models'; +import { ProjectModel, TemplateModel } from '~/__tests__/cypress/cypress/utils/models'; import { mockProjectK8sResource } from '~/__mocks__'; export const customServingRuntimesInitialMock = [ @@ -28,10 +28,11 @@ export const customServingRuntimesInitialMock = [ ]; export const customServingRuntimesIntercept = (): void => { + cy.interceptK8sList(TemplateModel, mockK8sResourceList(customServingRuntimesInitialMock)); + cy.interceptK8sList(ProjectModel, mockK8sResourceList([mockProjectK8sResource({})])); cy.interceptOdh( 'GET /api/templates/:namespace', { path: { namespace: 'opendatahub' } }, mockK8sResourceList(customServingRuntimesInitialMock), ); - cy.interceptK8sList(ProjectModel, mockK8sResourceList([mockProjectK8sResource({})])); }; diff --git a/frontend/src/api/k8s/__tests__/templates.spec.ts b/frontend/src/api/k8s/__tests__/templates.spec.ts index 23df1ddc5c..54e0545753 100644 --- a/frontend/src/api/k8s/__tests__/templates.spec.ts +++ b/frontend/src/api/k8s/__tests__/templates.spec.ts @@ -1,26 +1,50 @@ -import { K8sStatus, k8sDeleteResource, k8sListResource } from '@openshift/dynamic-plugin-sdk-utils'; -import { mockK8sResourceList } from '~/__mocks__/mockK8sResourceList'; +import { + K8sStatus, + k8sDeleteResource, + useK8sWatchResource, +} from '@openshift/dynamic-plugin-sdk-utils'; import { mock200Status, mock404Error } from '~/__mocks__/mockK8sStatus'; import { mockServingRuntimeTemplateK8sResource } from '~/__mocks__/mockServingRuntimeTemplateK8sResource'; -import { assembleServingRuntimeTemplate, deleteTemplate, listTemplates } from '~/api'; +import { + assembleServingRuntimeTemplate, + deleteTemplate, + groupVersionKind, + useTemplates, +} from '~/api'; import { TemplateModel } from '~/api/models'; import { K8sDSGResource, TemplateKind } from '~/k8sTypes'; +import useCustomServingRuntimesEnabled from '~/pages/modelServing/customServingRuntimes/useCustomServingRuntimesEnabled'; +import useModelServingEnabled from '~/pages/modelServing/useModelServingEnabled'; import { ServingRuntimeAPIProtocol, ServingRuntimePlatform } from '~/types'; import { genRandomChars } from '~/utilities/string'; +import { NotReadyError } from '~/utilities/useFetchState'; jest.mock('@openshift/dynamic-plugin-sdk-utils', () => ({ k8sListResource: jest.fn(), k8sDeleteResource: jest.fn(), + useK8sWatchResource: jest.fn(), })); jest.mock('~/utilities/string', () => ({ genRandomChars: jest.fn(), })); -const k8sListResourceMock = jest.mocked(k8sListResource); +jest.mock('~/pages/modelServing/useModelServingEnabled', () => ({ + __esModule: true, + default: jest.fn(), +})); + +jest.mock('~/pages/modelServing/customServingRuntimes/useCustomServingRuntimesEnabled', () => ({ + __esModule: true, + default: jest.fn(), +})); + +const useModelServingEnabledMock = jest.mocked(useModelServingEnabled); +const useCustomServingRuntimesEnabledMock = jest.mocked(useCustomServingRuntimesEnabled); const genRandomCharsMock = jest.mocked(genRandomChars); const k8sDeleteResourceMock = jest.mocked(k8sDeleteResource); +const useK8sWatchResourceMock = jest.mocked(useK8sWatchResource); const templateMock = mockServingRuntimeTemplateK8sResource({}); const { namespace } = templateMock.metadata; @@ -75,37 +99,63 @@ describe('assembleServingRuntimeTemplate', () => { }); }); -describe('listTemplates', () => { - it('should list templates without namespace and label selector', async () => { - k8sListResourceMock.mockResolvedValue(mockK8sResourceList([templateMock])); - const result = await listTemplates(); - expect(k8sListResourceMock).toHaveBeenCalledWith({ - model: TemplateModel, - queryOptions: {}, - }); - expect(k8sListResourceMock).toHaveBeenCalledTimes(1); - expect(result).toStrictEqual([templateMock]); +describe('useTemplates', () => { + it('should wrap useK8sWatchResource to watch templates', () => { + useModelServingEnabledMock.mockReturnValue(true); + useCustomServingRuntimesEnabledMock.mockReturnValue(true); + const mockReturnValue: ReturnType = [[], false, undefined]; + useK8sWatchResourceMock.mockReturnValue(mockReturnValue); + expect(useTemplates('opendatahub')).toStrictEqual(mockReturnValue); + expect(useK8sWatchResourceMock).toHaveBeenCalledWith( + { + isList: true, + groupVersionKind: groupVersionKind(TemplateModel), + namespace, + }, + TemplateModel, + ); }); - it('should list templates with namespace and label selector', async () => { - k8sListResourceMock.mockResolvedValue(mockK8sResourceList([templateMock])); - const result = await listTemplates(namespace, 'labelSelector'); - expect(k8sListResourceMock).toHaveBeenCalledWith({ - model: TemplateModel, - queryOptions: { ns: namespace, queryParams: { labelSelector: 'labelSelector' } }, - }); - expect(k8sListResourceMock).toHaveBeenCalledTimes(1); - expect(result).toStrictEqual([templateMock]); + it('should throw error when namespace is not provided', () => { + useModelServingEnabledMock.mockReturnValue(true); + useCustomServingRuntimesEnabledMock.mockReturnValue(true); + const mockReturnValue: ReturnType = [[], false, undefined]; + useK8sWatchResourceMock.mockReturnValue(mockReturnValue); + expect(() => useTemplates()).toThrow(new NotReadyError('No namespace provided')); }); - it('should handle errors and rethrow', async () => { - k8sListResourceMock.mockRejectedValue(new Error('error1')); - await expect(listTemplates()).rejects.toThrow('error1'); - expect(k8sListResourceMock).toHaveBeenCalledTimes(1); - expect(k8sListResourceMock).toHaveBeenCalledWith({ - model: TemplateModel, - queryOptions: {}, - }); + it('should throw error when model serving is not enabled', () => { + useModelServingEnabledMock.mockReturnValue(false); + useCustomServingRuntimesEnabledMock.mockReturnValue(true); + const mockReturnValue: ReturnType = [[], false, undefined]; + useK8sWatchResourceMock.mockReturnValue(mockReturnValue); + expect(() => useTemplates('opendatahub')).toThrow( + new NotReadyError('Model serving is not enabled'), + ); + }); + + it('should filter templates when custom serving runtime is not enabled', () => { + const templatesMock = { + ...templateMock, + metadata: { ...templateMock.metadata, labels: { 'opendatahub.io/ootb': 'true' } }, + }; + useModelServingEnabledMock.mockReturnValue(true); + useCustomServingRuntimesEnabledMock.mockReturnValue(false); + const mockReturnValue: ReturnType = [ + [templatesMock], + false, + undefined, + ]; + useK8sWatchResourceMock.mockReturnValue(mockReturnValue); + expect(useTemplates('opendatahub')).toStrictEqual(mockReturnValue); + expect(useK8sWatchResourceMock).toHaveBeenCalledWith( + { + isList: true, + groupVersionKind: groupVersionKind(TemplateModel), + namespace, + }, + TemplateModel, + ); }); }); diff --git a/frontend/src/api/k8s/templates.ts b/frontend/src/api/k8s/templates.ts index 5d90ea50cd..80bcb75846 100644 --- a/frontend/src/api/k8s/templates.ts +++ b/frontend/src/api/k8s/templates.ts @@ -1,9 +1,17 @@ import YAML from 'yaml'; -import { k8sDeleteResource, k8sListResource } from '@openshift/dynamic-plugin-sdk-utils'; +import { + WatchK8sResult, + k8sDeleteResource, + useK8sWatchResource, +} from '@openshift/dynamic-plugin-sdk-utils'; import { ServingRuntimeKind, TemplateKind } from '~/k8sTypes'; import { TemplateModel } from '~/api/models'; import { genRandomChars } from '~/utilities/string'; import { ServingRuntimeAPIProtocol, ServingRuntimePlatform } from '~/types'; +import { NotReadyError } from '~/utilities/useFetchState'; +import useModelServingEnabled from '~/pages/modelServing/useModelServingEnabled'; +import useCustomServingRuntimesEnabled from '~/pages/modelServing/customServingRuntimes/useCustomServingRuntimesEnabled'; +import { groupVersionKind } from '~/api/k8sUtils'; export const assembleServingRuntimeTemplate = ( body: string, @@ -39,18 +47,35 @@ export const assembleServingRuntimeTemplate = ( }; }; -export const listTemplates = async ( - namespace?: string, - labelSelector?: string, -): Promise => { - const queryOptions = { - ...(namespace && { ns: namespace }), - ...(labelSelector && { queryParams: { labelSelector } }), - }; - return k8sListResource({ - model: TemplateModel, - queryOptions, - }).then((listResource) => listResource.items); +export const useTemplates = (namespace?: string): WatchK8sResult => { + const modelServingEnabled = useModelServingEnabled(); + const customServingRuntimesEnabled = useCustomServingRuntimesEnabled(); + + if (!namespace) { + throw new NotReadyError('No namespace provided'); + } + + if (!modelServingEnabled) { + throw new NotReadyError('Model serving is not enabled'); + } + + const [templates, loaded, error] = useK8sWatchResource( + { + isList: true, + groupVersionKind: groupVersionKind(TemplateModel), + namespace, + }, + TemplateModel, + ); + + if (!customServingRuntimesEnabled) { + return [ + templates.filter((template) => template.metadata.labels?.['opendatahub.io/ootb'] === 'true'), + loaded, + error, + ]; + } + return [templates, loaded, error]; }; export const deleteTemplate = (name: string, namespace: string): Promise => diff --git a/frontend/src/pages/modelServing/ModelServingContext.tsx b/frontend/src/pages/modelServing/ModelServingContext.tsx index 2a3dfd3a24..36aaf13d4e 100644 --- a/frontend/src/pages/modelServing/ModelServingContext.tsx +++ b/frontend/src/pages/modelServing/ModelServingContext.tsx @@ -10,6 +10,7 @@ import { } from '@patternfly/react-core'; import { useNavigate } from 'react-router-dom'; import { ExclamationCircleIcon } from '@patternfly/react-icons'; +import { WatchK8sResult } from '@openshift/dynamic-plugin-sdk-utils'; import { ServingRuntimeKind, InferenceServiceKind, @@ -17,7 +18,7 @@ import { ProjectKind, SecretKind, } from '~/k8sTypes'; -import { DEFAULT_CONTEXT_DATA } from '~/utilities/const'; +import { DEFAULT_CONTEXT_DATA, DEFAULT_LIST_WATCH_RESULT } from '~/utilities/const'; import { ContextResourceData } from '~/types'; import { useContextResourceData } from '~/utilities/useContextResourceData'; import { useDashboardNamespace } from '~/redux/selectors'; @@ -27,9 +28,9 @@ import useSyncPreferredProject from '~/concepts/projects/useSyncPreferredProject import { ProjectsContext, byName } from '~/concepts/projects/ProjectsContext'; import { SupportedArea, conditionalArea } from '~/concepts/areas'; import useServingPlatformStatuses from '~/pages/modelServing/useServingPlatformStatuses'; +import { useTemplates } from '~/api'; import useInferenceServices from './useInferenceServices'; import useServingRuntimes from './useServingRuntimes'; -import useTemplates from './customServingRuntimes/useTemplates'; import useTemplateOrder from './customServingRuntimes/useTemplateOrder'; import useTemplateDisablement from './customServingRuntimes/useTemplateDisablement'; import { getTokenNames } from './utils'; @@ -39,7 +40,7 @@ type ModelServingContextType = { refreshAllData: () => void; filterTokens: (servingRuntime?: string) => SecretKind[]; dataConnections: ContextResourceData; - servingRuntimeTemplates: ContextResourceData; + servingRuntimeTemplates: WatchK8sResult; servingRuntimeTemplateOrder: ContextResourceData; servingRuntimeTemplateDisablement: ContextResourceData; servingRuntimes: ContextResourceData; @@ -60,7 +61,7 @@ export const ModelServingContext = React.createContext( refreshAllData: () => undefined, filterTokens: () => [], dataConnections: DEFAULT_CONTEXT_DATA, - servingRuntimeTemplates: DEFAULT_CONTEXT_DATA, + servingRuntimeTemplates: DEFAULT_LIST_WATCH_RESULT, servingRuntimeTemplateOrder: DEFAULT_CONTEXT_DATA, servingRuntimeTemplateDisablement: DEFAULT_CONTEXT_DATA, servingRuntimes: DEFAULT_CONTEXT_DATA, @@ -80,9 +81,9 @@ const ModelServingContextProvider = conditionalArea( - useTemplates(dashboardNamespace), - ); + const servingRuntimeTemplates = useTemplates(dashboardNamespace); + const servingRuntimeTemplatesError = servingRuntimeTemplates[2] as Error | undefined; + const servingRuntimeTemplateOrder = useContextResourceData( useTemplateOrder(dashboardNamespace), ); @@ -137,7 +138,7 @@ const ModelServingContextProvider = conditionalArea void; - servingRuntimeTemplates: ContextResourceData; + servingRuntimeTemplates: WatchK8sResult; servingRuntimeTemplateOrder: ContextResourceData; servingRuntimeTemplateDisablement: ContextResourceData; }; export const CustomServingRuntimeContext = React.createContext({ refreshData: () => undefined, - servingRuntimeTemplates: DEFAULT_CONTEXT_DATA, + servingRuntimeTemplates: DEFAULT_LIST_WATCH_RESULT, servingRuntimeTemplateOrder: DEFAULT_CONTEXT_DATA, servingRuntimeTemplateDisablement: DEFAULT_CONTEXT_DATA, }); @@ -35,12 +36,10 @@ export const CustomServingRuntimeContext = React.createContext { const { dashboardNamespace } = useDashboardNamespace(); - // TODO: Disable backend workaround when we migrate admin panel to Passthrough API - const servingRuntimeTemplates = useContextResourceData( - useTemplates(dashboardNamespace, true), - ); + const servingRuntimeTemplates = useTemplates(dashboardNamespace); + const servingRuntimeTemplatesError = servingRuntimeTemplates[2] as Error | undefined; - // TODO: Disable backend workaround when we migrate admin panel to Passthrough API + // TODO: Disable backend workaround when we migrate admin panelk to Passthrough API const servingRuntimeTemplateOrder = useContextResourceData( useTemplateOrder(dashboardNamespace, true), 2 * 60 * 1000, @@ -52,19 +51,13 @@ const CustomServingRuntimeContextProvider: React.FC = () => { 2 * 60 * 1000, ); - const servingRuntimeTemplateRefresh = servingRuntimeTemplates.refresh; const servingRuntimeTemplateOrderRefresh = servingRuntimeTemplateOrder.refresh; const servingRuntimeTemplateDisablementRefresh = servingRuntimeTemplateOrder.refresh; const refreshData = React.useCallback(() => { - servingRuntimeTemplateRefresh(); servingRuntimeTemplateOrderRefresh(); servingRuntimeTemplateDisablementRefresh(); - }, [ - servingRuntimeTemplateRefresh, - servingRuntimeTemplateOrderRefresh, - servingRuntimeTemplateDisablementRefresh, - ]); + }, [servingRuntimeTemplateOrderRefresh, servingRuntimeTemplateDisablementRefresh]); const contextValue = React.useMemo( () => ({ @@ -82,7 +75,7 @@ const CustomServingRuntimeContextProvider: React.FC = () => { ); if ( - servingRuntimeTemplates.error || + servingRuntimeTemplatesError || servingRuntimeTemplateOrder.error || servingRuntimeTemplateDisablement.error ) { @@ -95,7 +88,7 @@ const CustomServingRuntimeContextProvider: React.FC = () => { headingLevel="h2" /> - {servingRuntimeTemplates.error?.message || servingRuntimeTemplateOrder.error?.message} + {servingRuntimeTemplatesError?.message || servingRuntimeTemplateOrder.error?.message} @@ -103,7 +96,7 @@ const CustomServingRuntimeContextProvider: React.FC = () => { } if ( - !servingRuntimeTemplates.loaded || + !servingRuntimeTemplates[1] || !servingRuntimeTemplateOrder.loaded || !servingRuntimeTemplateDisablement.loaded ) { diff --git a/frontend/src/pages/modelServing/customServingRuntimes/CustomServingRuntimeEditTemplate.tsx b/frontend/src/pages/modelServing/customServingRuntimes/CustomServingRuntimeEditTemplate.tsx index 42f60586bc..1e6a29353d 100644 --- a/frontend/src/pages/modelServing/customServingRuntimes/CustomServingRuntimeEditTemplate.tsx +++ b/frontend/src/pages/modelServing/customServingRuntimes/CustomServingRuntimeEditTemplate.tsx @@ -18,7 +18,7 @@ import { CustomServingRuntimeContext } from './CustomServingRuntimeContext'; const CustomServingRuntimeEditTemplate: React.FC = () => { const { - servingRuntimeTemplates: { data }, + servingRuntimeTemplates: [data], } = React.useContext(CustomServingRuntimeContext); const navigate = useNavigate(); const { servingRuntimeName } = useParams(); diff --git a/frontend/src/pages/modelServing/customServingRuntimes/CustomServingRuntimeEnabledToggle.tsx b/frontend/src/pages/modelServing/customServingRuntimes/CustomServingRuntimeEnabledToggle.tsx index b220d0fca1..8d26b7bae9 100644 --- a/frontend/src/pages/modelServing/customServingRuntimes/CustomServingRuntimeEnabledToggle.tsx +++ b/frontend/src/pages/modelServing/customServingRuntimes/CustomServingRuntimeEnabledToggle.tsx @@ -20,7 +20,7 @@ const CustomServingRuntimeEnabledToggle: React.FC { const { servingRuntimeTemplateOrder: { data: templateOrder, refresh: refreshOrder }, - servingRuntimeTemplates: { data: unsortedTemplates }, + servingRuntimeTemplates: [unsortedTemplates], refreshData, } = React.useContext(CustomServingRuntimeContext); const { dashboardNamespace } = useDashboardNamespace(); diff --git a/frontend/src/pages/modelServing/customServingRuntimes/CustomServingRuntimeView.tsx b/frontend/src/pages/modelServing/customServingRuntimes/CustomServingRuntimeView.tsx index ce3330b091..f296119987 100644 --- a/frontend/src/pages/modelServing/customServingRuntimes/CustomServingRuntimeView.tsx +++ b/frontend/src/pages/modelServing/customServingRuntimes/CustomServingRuntimeView.tsx @@ -7,7 +7,7 @@ import { CustomServingRuntimeContext } from './CustomServingRuntimeContext'; const CustomServingRuntimeView: React.FC = () => { const { - servingRuntimeTemplates: { data: servingRuntimeTemplates }, + servingRuntimeTemplates: [servingRuntimeTemplates], } = React.useContext(CustomServingRuntimeContext); return ( diff --git a/frontend/src/pages/modelServing/customServingRuntimes/DeleteCustomServingRuntimeModal.tsx b/frontend/src/pages/modelServing/customServingRuntimes/DeleteCustomServingRuntimeModal.tsx index 9fc485630f..c6489820d3 100644 --- a/frontend/src/pages/modelServing/customServingRuntimes/DeleteCustomServingRuntimeModal.tsx +++ b/frontend/src/pages/modelServing/customServingRuntimes/DeleteCustomServingRuntimeModal.tsx @@ -25,7 +25,7 @@ const DeleteCustomServingRuntimeModal: React.FC => { - const customServingRuntimesEnabled = useCustomServingRuntimesEnabled(); - const modelServingEnabled = useModelServingEnabled(); - - const getTemplates = React.useCallback(() => { - if (!namespace) { - return Promise.reject(new NotReadyError('No namespace provided')); - } - - if (!modelServingEnabled) { - return Promise.reject(new NotReadyError('Model serving is not enabled')); - } - - // TODO: Remove this when we migrate admin panel to Passthrough API - if (adminPanel) { - return listTemplatesBackend(namespace, 'opendatahub.io/dashboard=true') - .catch((e) => { - if (e.statusObject?.code === 404) { - throw new Error('Serving Runtime templates is not properly configured.'); - } - throw e; - }) - .then((templates) => { - if (!customServingRuntimesEnabled) { - return templates.filter( - (template) => template.metadata.labels?.['opendatahub.io/ootb'] === 'true', - ); - } - return templates; - }); - } - - return listTemplates(namespace, 'opendatahub.io/dashboard=true') - .catch((e) => { - if (e.statusObject?.code === 404) { - throw new Error('Serving Runtime templates is not properly configured.'); - } - throw e; - }) - .then((templates) => { - if (!customServingRuntimesEnabled) { - return templates.filter( - (template) => template.metadata.labels?.['opendatahub.io/ootb'] === 'true', - ); - } - return templates; - }); - }, [namespace, customServingRuntimesEnabled, modelServingEnabled, adminPanel]); - - return useFetchState(getTemplates, []); -}; - -export default useTemplates; diff --git a/frontend/src/pages/modelServing/screens/global/ServeModelButton.tsx b/frontend/src/pages/modelServing/screens/global/ServeModelButton.tsx index 5b0fd26468..c42491dfeb 100644 --- a/frontend/src/pages/modelServing/screens/global/ServeModelButton.tsx +++ b/frontend/src/pages/modelServing/screens/global/ServeModelButton.tsx @@ -21,7 +21,7 @@ const ServeModelButton: React.FC = () => { const { inferenceServices: { refresh: refreshInferenceServices }, servingRuntimes: { refresh: refreshServingRuntimes }, - servingRuntimeTemplates: { data: templates }, + servingRuntimeTemplates: [templates], servingRuntimeTemplateOrder: { data: templateOrder }, servingRuntimeTemplateDisablement: { data: templateDisablement }, dataConnections: { data: dataConnections }, diff --git a/frontend/src/pages/modelServing/screens/projects/EmptyMultiModelServingCard.tsx b/frontend/src/pages/modelServing/screens/projects/EmptyMultiModelServingCard.tsx index 828dcb360f..d9b670de84 100644 --- a/frontend/src/pages/modelServing/screens/projects/EmptyMultiModelServingCard.tsx +++ b/frontend/src/pages/modelServing/screens/projects/EmptyMultiModelServingCard.tsx @@ -24,7 +24,7 @@ const EmptyMultiModelServingCard: React.FC = () => { const { servingRuntimes: { refresh: refreshServingRuntime }, - servingRuntimeTemplates: { data: templates }, + servingRuntimeTemplates: [templates], servingRuntimeTemplateOrder: { data: templateOrder }, servingRuntimeTemplateDisablement: { data: templateDisablement }, serverSecrets: { refresh: refreshTokens }, diff --git a/frontend/src/pages/modelServing/screens/projects/EmptySingleModelServingCard.tsx b/frontend/src/pages/modelServing/screens/projects/EmptySingleModelServingCard.tsx index 761e17b6e9..e02b81d93e 100644 --- a/frontend/src/pages/modelServing/screens/projects/EmptySingleModelServingCard.tsx +++ b/frontend/src/pages/modelServing/screens/projects/EmptySingleModelServingCard.tsx @@ -27,7 +27,7 @@ const EmptySingleModelServingCard: React.FC = () => { const { servingRuntimes: { refresh: refreshServingRuntime }, - servingRuntimeTemplates: { data: templates }, + servingRuntimeTemplates: [templates], servingRuntimeTemplateOrder: { data: templateOrder }, servingRuntimeTemplateDisablement: { data: templateDisablement }, serverSecrets: { refresh: refreshTokens }, diff --git a/frontend/src/pages/modelServing/screens/projects/ModelServingPlatform.tsx b/frontend/src/pages/modelServing/screens/projects/ModelServingPlatform.tsx index ee1fb728a7..9963e95f36 100644 --- a/frontend/src/pages/modelServing/screens/projects/ModelServingPlatform.tsx +++ b/frontend/src/pages/modelServing/screens/projects/ModelServingPlatform.tsx @@ -54,7 +54,7 @@ const ModelServingPlatform: React.FC = () => { error: servingRuntimeError, refresh: refreshServingRuntime, }, - servingRuntimeTemplates: { data: templates, loaded: templatesLoaded, error: templateError }, + servingRuntimeTemplates: [templates, templatesLoaded, templateError], servingRuntimeTemplateOrder: { data: templateOrder }, servingRuntimeTemplateDisablement: { data: templateDisablement }, dataConnections: { data: dataConnections }, @@ -167,7 +167,7 @@ const ModelServingPlatform: React.FC = () => { } isLoading={!servingRuntimesLoaded && !templatesLoaded} isEmpty={shouldShowPlatformSelection} - loadError={platformError || servingRuntimeError || templateError} + loadError={platformError || servingRuntimeError || (templateError as Error | undefined)} emptyState={ kServeEnabled && modelMeshEnabled ? ( diff --git a/frontend/src/pages/modelServing/screens/projects/ModelServingPlatformButtonAction.tsx b/frontend/src/pages/modelServing/screens/projects/ModelServingPlatformButtonAction.tsx index 1e3c999648..73ee8b5a10 100644 --- a/frontend/src/pages/modelServing/screens/projects/ModelServingPlatformButtonAction.tsx +++ b/frontend/src/pages/modelServing/screens/projects/ModelServingPlatformButtonAction.tsx @@ -16,7 +16,7 @@ const ModelServingPlatformButtonAction: React.FC { const { - servingRuntimeTemplates: { loaded: templatesLoaded }, + servingRuntimeTemplates: [, templatesLoaded], } = React.useContext(ProjectDetailsContext); const actionButton = () => ( diff --git a/frontend/src/pages/projects/ProjectDetailsContext.tsx b/frontend/src/pages/projects/ProjectDetailsContext.tsx index 4a187cc210..b878f712b5 100644 --- a/frontend/src/pages/projects/ProjectDetailsContext.tsx +++ b/frontend/src/pages/projects/ProjectDetailsContext.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import { Navigate, Outlet, useParams } from 'react-router-dom'; +import { WatchK8sResult } from '@openshift/dynamic-plugin-sdk-utils'; import { GroupKind, InferenceServiceKind, @@ -10,7 +11,7 @@ import { ServingRuntimeKind, TemplateKind, } from '~/k8sTypes'; -import { DEFAULT_CONTEXT_DATA } from '~/utilities/const'; +import { DEFAULT_CONTEXT_DATA, DEFAULT_LIST_WATCH_RESULT } from '~/utilities/const'; import useServingRuntimes from '~/pages/modelServing/useServingRuntimes'; import useInferenceServices from '~/pages/modelServing/useInferenceServices'; import { ContextResourceData } from '~/types'; @@ -20,12 +21,12 @@ import { PipelineContextProvider } from '~/concepts/pipelines/context'; import { byName, ProjectsContext } from '~/concepts/projects/ProjectsContext'; import InvalidProject from '~/concepts/projects/InvalidProject'; import useSyncPreferredProject from '~/concepts/projects/useSyncPreferredProject'; -import useTemplates from '~/pages/modelServing/customServingRuntimes/useTemplates'; import useTemplateOrder from '~/pages/modelServing/customServingRuntimes/useTemplateOrder'; import useTemplateDisablement from '~/pages/modelServing/customServingRuntimes/useTemplateDisablement'; import { useDashboardNamespace } from '~/redux/selectors'; import { getTokenNames } from '~/pages/modelServing/utils'; import { SupportedArea, useIsAreaAvailable } from '~/concepts/areas'; +import { useTemplates } from '~/api'; import { NotebookState } from './notebook/types'; import { DataConnection } from './types'; import useDataConnections from './screens/detail/data-connections/useDataConnections'; @@ -42,7 +43,7 @@ type ProjectDetailsContextType = { pvcs: ContextResourceData; dataConnections: ContextResourceData; servingRuntimes: ContextResourceData; - servingRuntimeTemplates: ContextResourceData; + servingRuntimeTemplates: WatchK8sResult; servingRuntimeTemplateOrder: ContextResourceData; servingRuntimeTemplateDisablement: ContextResourceData; inferenceServices: ContextResourceData; @@ -60,7 +61,7 @@ export const ProjectDetailsContext = React.createContext { const pvcs = useContextResourceData(useProjectPvcs(namespace)); const dataConnections = useContextResourceData(useDataConnections(namespace)); const servingRuntimes = useContextResourceData(useServingRuntimes(namespace)); - const servingRuntimeTemplates = useContextResourceData( - useTemplates(dashboardNamespace), - ); + const servingRuntimeTemplates = useTemplates(dashboardNamespace); + const servingRuntimeTemplateOrder = useContextResourceData( useTemplateOrder(dashboardNamespace), ); @@ -99,7 +99,6 @@ const ProjectDetailsContextProvider: React.FC = () => { const pvcRefresh = pvcs.refresh; const dataConnectionRefresh = dataConnections.refresh; const servingRuntimeRefresh = servingRuntimes.refresh; - const servingRuntimeTemplateRefresh = servingRuntimeTemplates.refresh; const servingRuntimeTemplateOrderRefresh = servingRuntimeTemplateOrder.refresh; const servingRuntimeTemplateDisablementRefresh = servingRuntimeTemplateDisablement.refresh; const inferenceServiceRefresh = inferenceServices.refresh; @@ -114,7 +113,6 @@ const ProjectDetailsContextProvider: React.FC = () => { inferenceServiceRefresh(); projectSharingRefresh(); groupsRefresh(); - servingRuntimeTemplateRefresh(); servingRuntimeTemplateOrderRefresh(); servingRuntimeTemplateDisablementRefresh(); }, [ @@ -122,7 +120,6 @@ const ProjectDetailsContextProvider: React.FC = () => { pvcRefresh, dataConnectionRefresh, servingRuntimeRefresh, - servingRuntimeTemplateRefresh, servingRuntimeTemplateOrderRefresh, servingRuntimeTemplateDisablementRefresh, inferenceServiceRefresh, diff --git a/frontend/src/pages/projects/screens/detail/overview/serverModels/AddModelFooter.tsx b/frontend/src/pages/projects/screens/detail/overview/serverModels/AddModelFooter.tsx index bbb05ab1b4..fe5498c9b8 100644 --- a/frontend/src/pages/projects/screens/detail/overview/serverModels/AddModelFooter.tsx +++ b/frontend/src/pages/projects/screens/detail/overview/serverModels/AddModelFooter.tsx @@ -24,7 +24,7 @@ const AddModelFooter: React.FC = ({ selectedPlatform }) => const { servingRuntimes: { refresh: refreshServingRuntime }, - servingRuntimeTemplates: { data: templates }, + servingRuntimeTemplates: [templates], servingRuntimeTemplateOrder: { data: templateOrder }, servingRuntimeTemplateDisablement: { data: templateDisablement }, dataConnections: { data: dataConnections }, diff --git a/frontend/src/utilities/const.ts b/frontend/src/utilities/const.ts index 770010e8ce..760a74586c 100644 --- a/frontend/src/utilities/const.ts +++ b/frontend/src/utilities/const.ts @@ -1,3 +1,4 @@ +import { WatchK8sResult } from '@openshift/dynamic-plugin-sdk-utils'; import { ContextResourceData, FetchStateObject, OdhDocumentType } from '~/types'; const WS_HOSTNAME = process.env.WS_HOSTNAME || location.host; @@ -57,6 +58,8 @@ export const DEFAULT_LIST_FETCH_STATE: FetchStateObject = { refresh: () => undefined, }; +export const DEFAULT_LIST_WATCH_RESULT: WatchK8sResult = [[], false, undefined]; + export const DEFAULT_VALUE_FETCH_STATE: FetchStateObject = { data: undefined, loaded: false,