Repository: opendatahub-io/odh-dashboard
Pull request #4146: Serving Platform Change Button Bug Fix
Author: login: katieperry4
Labels: name: lgtm
name: approved
Created: 2025-05-02T18:28:08Z | Merged: 2025-05-09T11:45:07Z

Closes [RHOAIENG-15829](https://issues.redhat.com/browse/RHOAIENG-15829)

## Description
<!--- Describe your changes in detail; the what, the why, any findings, etc -->
<!--- Include any screenshots of changed UI; Include any gifs if it was a flow / UX change -->
The serving platform change button would only appear if there was more than one platform enabled. If you had a project using modelMesh and modelMesh got disabled, you'd be stuck with an error on the models tab saying "Multi-model platform is not installed" and unable to switch to kServe.

I added a util to see if the current platform is enabled, if it's not, show the change button and change the label to say the current platform is disabled.

**Previously:**
![Screenshot 2025-05-02 at 10 42 41 AM](https://github.com/user-attachments/assets/a4b6da1a-64a2-453d-b4cd-df21ad546a55)

**Now:**
![Screenshot 2025-05-02 at 1 24 43 PM](https://github.com/user-attachments/assets/d4958b75-d061-4d26-af0b-d0138127c32e)


## How Has This Been Tested?
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->
Tested locally and with `npm run test`

If you want to test it: (using kServe in this example but the same can be done for modelMesh)
- Create or open a project with no models.
- Select single-model serving -> don't serve a model.
- Open the openshift console.
- Find the ODH operator -> go to the DSC yaml.
- Under `spec: components:` find `kserve` and set `managementState` to `Removed`.
- It can take a few minutes for this to come through so wait a bit and refresh the page now and then.
- Eventually you'll get an error saying the platform is not installed. But you should see the 'change' button and when you click it, you'll be automatically switched over to modelMesh.
- When you're done go back to the DSC and set the `kserve` `managementState` to `Managed`.

You might be wondering what happens when both kServe and modelMesh are disabled. Don't worry, I tried it:
![Screenshot 2025-05-02 at 2 13 09 PM](https://github.com/user-attachments/assets/9c0825e7-20f8-475e-af2f-4a359aeefddb)

## Test Impact
<!--- What tests have you done to cover the implemented functionality -->
<!--- If tests are not applicable, explain why here -->
Added unit tests for the `isCurrentServingPlatformEnabled` util.

## Request review criteria:
<!--- This PR will be merged by any repository approver when it meets all the points in the checklist -->
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->

Self checklist (all need to be checked):
- [x] The developer has manually tested the changes and verified that the changes work
- [x] Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious).
- [x] The developer has added tests or explained why testing cannot be added (unit or cypress tests for related changes)

If you have UI changes: 
<!--- You can ignore these if you are doing manifest, backend, internal logic, etc changes; aka non-UI / visual changes -->
- [x] Included any necessary screenshots or gifs if it was a UI change.
- [ ] Included tags to the UX team if it was a UI/UX change.

After the PR is posted & before it merges:
- [ ] The developer has tested their solution on a cluster by using the image produced by the PR to `main`



Diff:
diff --git a/frontend/src/__tests__/cypress/cypress/tests/mocked/modelServing/runtime/servingRuntimeList.cy.ts b/frontend/src/__tests__/cypress/cypress/tests/mocked/modelServing/runtime/servingRuntimeList.cy.ts
index 323596794e..883b2693ca 100644
--- a/frontend/src/__tests__/cypress/cypress/tests/mocked/modelServing/runtime/servingRuntimeList.cy.ts
+++ b/frontend/src/__tests__/cypress/cypress/tests/mocked/modelServing/runtime/servingRuntimeList.cy.ts
@@ -422,6 +422,31 @@ const initIntercepts = ({
 };
 
 describe('Serving Runtime List', () => {
+  describe('Change button visiblity', () => {
+    it('Change button visible when current platform is disabled', () => {
+      // starts with modelMesh enabled and kServe disabled
+      initIntercepts({
+        disableModelMeshConfig: false,
+        disableKServeConfig: true,
+        servingRuntimes: [],
+        projectEnableModelMesh: true,
+      });
+      projectDetails.visitSection('test-project', 'model-server');
+      // shouldn't exist because kServe is disabled and theres nothing to change to
+      projectDetails.findResetPlatformButton().should('not.exist');
+      // simulate modelMesh being disabled
+      cy.interceptOdh(
+        'GET /api/dsc/status',
+        mockDscStatus({
+          components: undefined,
+          installedComponents: { kserve: false, 'model-mesh': false },
+        }),
+      );
+
+      cy.reload();
+      projectDetails.findResetPlatformButton().should('exist');
+    });
+  });
   describe('No server available', () => {
     it('No model serving platform available', () => {
       initIntercepts({
diff --git a/frontend/src/pages/modelServing/screens/const.ts b/frontend/src/pages/modelServing/screens/const.ts
index 55794d37d8..7f2ead5368 100644
--- a/frontend/src/pages/modelServing/screens/const.ts
+++ b/frontend/src/pages/modelServing/screens/const.ts
@@ -1,5 +1,10 @@
 import { ModelServingSize } from './types';
 
+export const platformKeyMap = {
+  single: 'kServe',
+  multi: 'modelMesh',
+} as const;
+
 export const SERVING_RUNTIME_SCOPE = {
   Global: 'global',
   Project: 'project',
diff --git a/frontend/src/pages/modelServing/screens/projects/ModelServingPlatform.tsx b/frontend/src/pages/modelServing/screens/projects/ModelServingPlatform.tsx
index 5937075afb..d4cddc7529 100644
--- a/frontend/src/pages/modelServing/screens/projects/ModelServingPlatform.tsx
+++ b/frontend/src/pages/modelServing/screens/projects/ModelServingPlatform.tsx
@@ -24,7 +24,10 @@ import {
   getTemplateEnabledForPlatform,
 } from '~/pages/modelServing/customServingRuntimes/utils';
 import { ServingRuntimePlatform } from '~/types';
-import { getProjectModelServingPlatform } from '~/pages/modelServing/screens/projects/utils';
+import {
+  getProjectModelServingPlatform,
+  isCurrentServingPlatformEnabled,
+} from '~/pages/modelServing/screens/projects/utils';
 import KServeInferenceServiceTable from '~/pages/modelServing/screens/projects/KServeSection/KServeInferenceServiceTable';
 import DashboardPopupIconButton from '~/concepts/dashboard/DashboardPopupIconButton';
 import DetailsSection from '~/pages/projects/screens/detail/DetailsSection';
@@ -100,6 +103,11 @@ const ModelServingPlatform: React.FC = () => {
 
   const isProjectModelMesh = currentProjectServingPlatform === ServingRuntimePlatform.MULTI;
 
+  const isCurrentPlatformEnabled = isCurrentServingPlatformEnabled(
+    currentProjectServingPlatform,
+    servingPlatformStatuses,
+  );
+
   const onSubmit = (submit: boolean) => {
     setPlatformSelected(undefined);
     if (submit) {
@@ -324,21 +332,26 @@ const ModelServingPlatform: React.FC = () => {
                   <Label data-testid="serving-platform-label">
                     {isKServeNIMEnabled
                       ? 'NVIDIA NIM serving enabled'
-                      : isProjectModelMesh
-                      ? 'Multi-model serving enabled'
-                      : 'Single-model serving enabled'}
+                      : isCurrentPlatformEnabled
+                      ? isProjectModelMesh
+                        ? 'Multi-model serving enabled'
+                        : 'Single-model serving enabled'
+                      : 'Current platform disabled'}
                   </Label>
 
-                  {emptyModelServer && servingPlatformStatuses.platformEnabledCount > 1 && (
-                    <ModelServingPlatformSelectButton
-                      namespace={currentProject.metadata.name}
-                      servingPlatform={NamespaceApplicationCase.RESET_MODEL_SERVING_PLATFORM}
-                      setError={setErrorSelectingPlatform}
-                      variant="link"
-                      isInline
-                      data-testid="change-serving-platform-button"
-                    />
-                  )}
+                  {emptyModelServer &&
+                    (servingPlatformStatuses.platformEnabledCount > 1 ||
+                      !isCurrentPlatformEnabled ||
+                      platformError) && (
+                      <ModelServingPlatformSelectButton
+                        namespace={currentProject.metadata.name}
+                        servingPlatform={NamespaceApplicationCase.RESET_MODEL_SERVING_PLATFORM}
+                        setError={setErrorSelectingPlatform}
+                        variant="link"
+                        isInline
+                        data-testid="change-serving-platform-button"
+                      />
+                    )}
                 </Flex>,
               ]
             : undefined
diff --git a/frontend/src/pages/modelServing/screens/projects/__tests__/utils.spec.ts b/frontend/src/pages/modelServing/screens/projects/__tests__/utils.spec.ts
index bfdcfd7221..f05b2b1eae 100644
--- a/frontend/src/pages/modelServing/screens/projects/__tests__/utils.spec.ts
+++ b/frontend/src/pages/modelServing/screens/projects/__tests__/utils.spec.ts
@@ -6,6 +6,7 @@ import {
   getCreateInferenceServiceLabels,
   getProjectModelServingPlatform,
   getUrlFromKserveInferenceService,
+  isCurrentServingPlatformEnabled,
 } from '~/pages/modelServing/screens/projects/utils';
 import { ServingPlatformStatuses } from '~/pages/modelServing/screens/types';
 import { ServingRuntimePlatform } from '~/types';
@@ -518,3 +519,35 @@ describe('updateServingRuntimeTemplate', () => {
     expect(result.spec.containers[0].volumeMounts).toBeUndefined();
   });
 });
+
+describe('isCurrentServingPlatformEnabled', () => {
+  const baseStatuses = {
+    kServe: { enabled: true, installed: true },
+    kServeNIM: { enabled: false, installed: false },
+    modelMesh: { enabled: true, installed: true },
+    platformEnabledCount: 2,
+    refreshNIMAvailability: async () => true,
+  };
+
+  it('returns true if currentPlatform is single and kServe is enabled', () => {
+    expect(isCurrentServingPlatformEnabled(ServingRuntimePlatform.SINGLE, baseStatuses)).toBe(true);
+  });
+
+  it('returns false if currentPlatform is single and kServe is disabled', () => {
+    const statuses = { ...baseStatuses, kServe: { enabled: false, installed: true } };
+    expect(isCurrentServingPlatformEnabled(ServingRuntimePlatform.SINGLE, statuses)).toBe(false);
+  });
+
+  it('returns true if currentPlatform is multi and modelMesh is enabled', () => {
+    expect(isCurrentServingPlatformEnabled(ServingRuntimePlatform.MULTI, baseStatuses)).toBe(true);
+  });
+
+  it('returns false if currentPlatform is multi and modelMesh is disabled', () => {
+    const statuses = { ...baseStatuses, modelMesh: { enabled: false, installed: true } };
+    expect(isCurrentServingPlatformEnabled(ServingRuntimePlatform.MULTI, statuses)).toBe(false);
+  });
+
+  it('returns false if currentPlatform is undefined', () => {
+    expect(isCurrentServingPlatformEnabled(undefined, baseStatuses)).toBe(false);
+  });
+});
diff --git a/frontend/src/pages/modelServing/screens/projects/utils.ts b/frontend/src/pages/modelServing/screens/projects/utils.ts
index 72109eb284..5b81f6233a 100644
--- a/frontend/src/pages/modelServing/screens/projects/utils.ts
+++ b/frontend/src/pages/modelServing/screens/projects/utils.ts
@@ -21,7 +21,7 @@ import {
   ServingRuntimeEditInfo,
 } from '~/pages/modelServing/screens/types';
 import { ServingRuntimePlatform } from '~/types';
-import { DEFAULT_MODEL_SERVER_SIZES } from '~/pages/modelServing/screens/const';
+import { DEFAULT_MODEL_SERVER_SIZES, platformKeyMap } from '~/pages/modelServing/screens/const';
 import { useDeepCompareMemoize } from '~/utilities/useDeepCompareMemoize';
 import { EMPTY_AWS_SECRET_DATA } from '~/pages/projects/dataConnections/const';
 import { getDisplayNameFromK8sResource } from '~/concepts/k8s/utils';
@@ -731,3 +731,14 @@ export const fetchInferenceServiceCount = async (namespace: string): Promise<num
     );
   }
 };
+
+export function isCurrentServingPlatformEnabled(
+  currentPlatform: ServingRuntimePlatform | undefined,
+  statuses: ServingPlatformStatuses,
+): boolean {
+  if (!currentPlatform) {
+    return false;
+  }
+  const mappedKey = platformKeyMap[currentPlatform];
+  return statuses[mappedKey].enabled;
+}


Reviews:
user: login: Griffin-Sullivan
body: Nice work leaving a comment for future cases
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T15:38:10Z
user: login: manaswinidas
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-06T16:30:30Z
user: login: katieperry4
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-06T19:14:04Z
user: login: manaswinidas
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-07T10:39:37Z
user: login: katieperry4
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T12:25:50Z
user: login: manaswinidas
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-05-08T11:30:12Z
user: login: manaswinidas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T11:41:41Z
user: login: katieperry4
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T14:14:34Z
user: login: manaswinidas
body: /lgtm
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-09T10:50:03Z

Review comments:
diff_hunk: @@ -731,3 +731,18 @@ export const fetchInferenceServiceCount = async (namespace: string): Promise<num
     );
   }
 };
+
+export function isCurrentServingPlatformEnabled(
+  currentPlatform: ServingRuntimePlatform | undefined,
+  statuses: ServingPlatformStatuses,
+): boolean {
+  if (!currentPlatform) {
+    return false;
+  }
+  const platformKeyMap = {
+    single: 'kServe',
+    multi: 'modelMesh',
+  } as const;
path: frontend/src/pages/modelServing/screens/projects/utils.ts
user: login: Griffin-Sullivan
body: If we find that we need to map these in other places we should make this in a shared file
created_at: 2025-05-06T15:37:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -324,21 +332,26 @@ const ModelServingPlatform: React.FC = () => {
                   <Label data-testid="serving-platform-label">
                     {isKServeNIMEnabled
                       ? 'NVIDIA NIM serving enabled'
-                      : isProjectModelMesh
-                      ? 'Multi-model serving enabled'
-                      : 'Single-model serving enabled'}
+                      : isCurrentPlatformEnabled
+                      ? isProjectModelMesh
+                        ? 'Multi-model serving enabled'
+                        : 'Single-model serving enabled'
+                      : 'Current platform disabled'}
                   </Label>
 
-                  {emptyModelServer && servingPlatformStatuses.platformEnabledCount > 1 && (
-                    <ModelServingPlatformSelectButton
-                      namespace={currentProject.metadata.name}
-                      servingPlatform={NamespaceApplicationCase.RESET_MODEL_SERVING_PLATFORM}
-                      setError={setErrorSelectingPlatform}
-                      variant="link"
-                      isInline
-                      data-testid="change-serving-platform-button"
-                    />
-                  )}
+                  {emptyModelServer &&
+                    (servingPlatformStatuses.platformEnabledCount > 1 ||
+                      !isCurrentPlatformEnabled ||
+                      platformError) && (
+                      <ModelServingPlatformSelectButton
+                        namespace={currentProject.metadata.name}
+                        servingPlatform={NamespaceApplicationCase.RESET_MODEL_SERVING_PLATFORM}
+                        setError={setErrorSelectingPlatform}
+                        variant="link"
+                        isInline
+                        data-testid="change-serving-platform-button"
+                      />
+                    )}
path: frontend/src/pages/modelServing/screens/projects/ModelServingPlatform.tsx
user: login: manaswinidas
body: Nice work here, can we have mocked Cypress tests to cover these cases?
created_at: 2025-05-06T16:30:22Z
author_association: MEMBER
diff_hunk: @@ -731,3 +731,18 @@ export const fetchInferenceServiceCount = async (namespace: string): Promise<num
     );
   }
 };
+
+export function isCurrentServingPlatformEnabled(
+  currentPlatform: ServingRuntimePlatform | undefined,
+  statuses: ServingPlatformStatuses,
+): boolean {
+  if (!currentPlatform) {
+    return false;
+  }
+  const platformKeyMap = {
+    single: 'kServe',
+    multi: 'modelMesh',
+  } as const;
path: frontend/src/pages/modelServing/screens/projects/utils.ts
user: login: katieperry4
body: Do you want me to do that now or is that a future thing? @Griffin-Sullivan 
created_at: 2025-05-06T19:14:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -422,6 +422,32 @@ const initIntercepts = ({
 };
 
 describe('Serving Runtime List', () => {
+  describe('Change button visiblity', () => {
+    it('Change button visible when current platform is disabled', () => {
+      // starts with modelMesh enabled and kServe disabled
+      initIntercepts({
+        disableModelMeshConfig: false,
+        disableKServeConfig: true,
+        servingRuntimes: [],
+        projectEnableModelMesh: true,
+      });
+      projectDetails.visitSection('test-project', 'model-server');
+      // shouldn't exist because kServe is disabled and theres nothing to change to
+      cy.findByTestId('change-serving-platform-button').should('not.exist');
path: frontend/src/__tests__/cypress/cypress/tests/mocked/modelServing/runtime/servingRuntimeList.cy.ts
user: login: manaswinidas
body: There's a function you can reuse for this: `findResetPlatformButton()`
created_at: 2025-05-07T10:39:26Z
author_association: MEMBER
diff_hunk: @@ -422,6 +422,32 @@ const initIntercepts = ({
 };
 
 describe('Serving Runtime List', () => {
+  describe('Change button visiblity', () => {
+    it('Change button visible when current platform is disabled', () => {
+      // starts with modelMesh enabled and kServe disabled
+      initIntercepts({
+        disableModelMeshConfig: false,
+        disableKServeConfig: true,
+        servingRuntimes: [],
+        projectEnableModelMesh: true,
+      });
+      projectDetails.visitSection('test-project', 'model-server');
+      // shouldn't exist because kServe is disabled and theres nothing to change to
+      cy.findByTestId('change-serving-platform-button').should('not.exist');
+
+      // simulate modelMesh being disabled
+      cy.interceptOdh(
+        'GET /api/dsc/status',
+        mockDscStatus({
+          components: undefined,
+          installedComponents: { kserve: false, 'model-mesh': false },
+        }),
+      );
+
+      cy.reload();
+      cy.findByTestId('change-serving-platform-button').should('exist');
path: frontend/src/__tests__/cypress/cypress/tests/mocked/modelServing/runtime/servingRuntimeList.cy.ts
user: login: manaswinidas
body: same here
created_at: 2025-05-07T10:39:31Z
author_association: MEMBER
diff_hunk: @@ -422,6 +422,32 @@ const initIntercepts = ({
 };
 
 describe('Serving Runtime List', () => {
+  describe('Change button visiblity', () => {
+    it('Change button visible when current platform is disabled', () => {
+      // starts with modelMesh enabled and kServe disabled
+      initIntercepts({
+        disableModelMeshConfig: false,
+        disableKServeConfig: true,
+        servingRuntimes: [],
+        projectEnableModelMesh: true,
+      });
+      projectDetails.visitSection('test-project', 'model-server');
+      // shouldn't exist because kServe is disabled and theres nothing to change to
+      cy.findByTestId('change-serving-platform-button').should('not.exist');
path: frontend/src/__tests__/cypress/cypress/tests/mocked/modelServing/runtime/servingRuntimeList.cy.ts
user: login: katieperry4
body: oh thanks! I didn't know that existed 😅 
created_at: 2025-05-07T12:25:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -731,3 +731,14 @@ export const fetchInferenceServiceCount = async (namespace: string): Promise<num
     );
   }
 };
+
+export function isCurrentServingPlatformEnabled(
path: frontend/src/pages/modelServing/screens/projects/utils.ts
user: login: manaswinidas
body: is there a need for `platformKeyMap` when there is `ServingRuntimePlatform` already? can we reuse that?
created_at: 2025-05-08T11:29:58Z
author_association: MEMBER
diff_hunk: @@ -731,3 +731,14 @@ export const fetchInferenceServiceCount = async (namespace: string): Promise<num
     );
   }
 };
+
+export function isCurrentServingPlatformEnabled(
path: frontend/src/pages/modelServing/screens/projects/utils.ts
user: login: manaswinidas
body: something like 
```
export function isCurrentServingPlatformEnabled(
  currentPlatform: ServingRuntimePlatform | undefined,
  statuses: ServingPlatformStatuses,
): boolean {
  if (!currentPlatform) {
    return false;
  }
  return currentPlatform === ServingRuntimePlatform.SINGLE
    ? statuses.kServe.enabled
    : statuses.modelMesh.enabled;
}
```
created_at: 2025-05-08T11:41:41Z
author_association: MEMBER
diff_hunk: @@ -731,3 +731,14 @@ export const fetchInferenceServiceCount = async (namespace: string): Promise<num
     );
   }
 };
+
+export function isCurrentServingPlatformEnabled(
path: frontend/src/pages/modelServing/screens/projects/utils.ts
user: login: katieperry4
body: That does work but I think it might get a little messy manually saying if the platform is single, then kserve is enabled if not then, modelmesh is if there are future changes in the serving platforms. 
created_at: 2025-05-08T14:14:33Z
author_association: CONTRIBUTOR

PR comments:
user: login: codecov[bot]
created_at: 2025-05-06T15:38:25Z
body: ## [Codecov](https://app.codecov.io/gh/opendatahub-io/odh-dashboard/pull/4146?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opendatahub-io) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 85.47%. Comparing base [(`0820a75`)](https://app.codecov.io/gh/opendatahub-io/odh-dashboard/commit/0820a757a88ea0d7addde1df11ba8f019c129121?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opendatahub-io) to head [(`c81bd49`)](https://app.codecov.io/gh/opendatahub-io/odh-dashboard/commit/c81bd49970ed4787eaf619ca0b312750df5c9dff?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opendatahub-io).
> Report is 81 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


[![Impacted file tree graph](https://app.codecov.io/gh/opendatahub-io/odh-dashboard/pull/4146/graphs/tree.svg?width=650&height=150&src=pr&token=UQM7NLQUXJ&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opendatahub-io)](https://app.codecov.io/gh/opendatahub-io/odh-dashboard/pull/4146?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opendatahub-io)

```diff
@@            Coverage Diff            @@
##           main    #4146       +/-   ##
=========================================
+ Coverage      0   85.47%   +85.47%     
=========================================
  Files         0     1635     +1635     
  Lines         0    38921    +38921     
  Branches      0    11046    +11046     
=========================================
+ Hits          0    33267    +33267     
- Misses        0     5654     +5654     
```

| [Files with missing lines](https://app.codecov.io/gh/opendatahub-io/odh-dashboard/pull/4146?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opendatahub-io) | Coverage Δ | |
|---|---|---|
| [frontend/src/pages/modelServing/screens/const.ts](https://app.codecov.io/gh/opendatahub-io/odh-dashboard/pull/4146?src=pr&el=tree&filepath=frontend%2Fsrc%2Fpages%2FmodelServing%2Fscreens%2Fconst.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opendatahub-io#diff-ZnJvbnRlbmQvc3JjL3BhZ2VzL21vZGVsU2VydmluZy9zY3JlZW5zL2NvbnN0LnRz) | `100.00% <100.00%> (ø)` | |
| [...lServing/screens/projects/ModelServingPlatform.tsx](https://app.codecov.io/gh/opendatahub-io/odh-dashboard/pull/4146?src=pr&el=tree&filepath=frontend%2Fsrc%2Fpages%2FmodelServing%2Fscreens%2Fprojects%2FModelServingPlatform.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opendatahub-io#diff-ZnJvbnRlbmQvc3JjL3BhZ2VzL21vZGVsU2VydmluZy9zY3JlZW5zL3Byb2plY3RzL01vZGVsU2VydmluZ1BsYXRmb3JtLnRzeA==) | `99.17% <100.00%> (ø)` | |
| [...d/src/pages/modelServing/screens/projects/utils.ts](https://app.codecov.io/gh/opendatahub-io/odh-dashboard/pull/4146?src=pr&el=tree&filepath=frontend%2Fsrc%2Fpages%2FmodelServing%2Fscreens%2Fprojects%2Futils.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opendatahub-io#diff-ZnJvbnRlbmQvc3JjL3BhZ2VzL21vZGVsU2VydmluZy9zY3JlZW5zL3Byb2plY3RzL3V0aWxzLnRz) | `93.77% <100.00%> (ø)` | |

... and [1616 files with indirect coverage changes](https://app.codecov.io/gh/opendatahub-io/odh-dashboard/pull/4146/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opendatahub-io)

------

[Continue to review full report in Codecov by Sentry](https://app.codecov.io/gh/opendatahub-io/odh-dashboard/pull/4146?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opendatahub-io).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opendatahub-io)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://app.codecov.io/gh/opendatahub-io/odh-dashboard/pull/4146?dropdown=coverage&src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opendatahub-io). Last update [683820b...c81bd49](https://app.codecov.io/gh/opendatahub-io/odh-dashboard/pull/4146?dropdown=coverage&src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opendatahub-io). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opendatahub-io).

</details>
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: openshift-ci[bot]
created_at: 2025-05-09T10:50:10Z
body: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/opendatahub-io/odh-dashboard/pull/4146#pullrequestreview-2818750346" title="Approved">Griffin-Sullivan</a>*, *<a href="https://github.com/opendatahub-io/odh-dashboard/pull/4146#pullrequestreview-2827879380" title="LGTM">manaswinidas</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=opendatahub-io%2Fodh-dashboard).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/opendatahub-io/odh-dashboard/blob/main/OWNERS)~~ [Griffin-Sullivan]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
author_association: CONTRIBUTOR