Repository: opendatahub-io/odh-dashboard
Pull request #4238: 'valueFrom' env vars bug fix
Author: login: katieperry4
Labels: name: lgtm
name: approved
Created: 2025-05-20T20:01:40Z | Merged: 2025-05-22T19:38:24Z

Closes [RHOAIENG-15595](https://issues.redhat.com/browse/RHOAIENG-15595)

## 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 -->
If you had an env variable that pointed to a `Secret` or `ConfigMap` using `valueFrom` then the env value shown in the model deployment would be empty. Upon trying to edit that empty value it would add `value` next to  `valueFrom` and break the whole thing.
Per the ticket, the input now shows the `valueFrom` but is disabled to prevent editing; with a tooltip that explains the value can be edited from the OCP.
<img width="339" alt="Screenshot 2025-05-20 at 3 44 14 PM" src="https://github.com/user-attachments/assets/35a5b017-4ac9-4e87-ae9d-49b1113e3d3a" />


**Previously:**
<img width="818" alt="Screenshot 2025-05-20 at 3 44 21 PM" src="https://github.com/user-attachments/assets/2aeee565-c7b9-407b-bb88-e2c4a8b41696" />

**Now:**
<img width="821" alt="Screenshot 2025-05-20 at 3 56 11 PM" src="https://github.com/user-attachments/assets/6ea4ea48-24ef-4e7c-bafb-3ed4c636d67a" />

**The tooltip:**
<img width="828" alt="Screenshot 2025-05-20 at 3 56 17 PM" src="https://github.com/user-attachments/assets/075236b4-45e0-44cc-a3b3-3d482c220bad" />


## 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`

**To test this:**
- Go to deploy a kserve model.
- Add a regular env variable. Ex key: `test-variable-key` value: `test-variable-value`.
- Deploy the model.
- Find the deployed inference service in OCP.
- Go to the yaml.
- in `spec.predictor.model.env` add:
```
        - name: console-variable-update
          valueFrom:
            secretKeyRef:
              key: fake-secret-key
              name: fake-secret
```
- Hit save.
- Go back to ODH (might need to refresh the page) and edit the model.
- Find the 'additional environment variables' section and notice that the new envVar is there.
- The key input should be enabled and can be edited but the value input should be disabled and populated with the `valueFrom` with a tooltip showing the content of the input and explaining the value can only be edited from OCP.



## Test Impact
<!--- What tests have you done to cover the implemented functionality -->
<!--- If tests are not applicable, explain why here -->
Added unit and cypress tests

## 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/__mocks__/mockInferenceServiceK8sResource.ts b/frontend/src/__mocks__/mockInferenceServiceK8sResource.ts
index 2ce57cc3b9..966f3d6fac 100644
--- a/frontend/src/__mocks__/mockInferenceServiceK8sResource.ts
+++ b/frontend/src/__mocks__/mockInferenceServiceK8sResource.ts
@@ -28,7 +28,11 @@ type MockResourceConfigType = {
   kserveInternalLabel?: boolean;
   additionalLabels?: Record<string, string>;
   args?: string[];
-  env?: Array<{ name: string; value: string }>;
+  env?: Array<{
+    name: string;
+    value?: string;
+    valueFrom?: { secretKeyRef: { name: string; key: string } };
+  }>;
   isKserveRaw?: boolean;
   tolerations?: Toleration[];
   nodeSelector?: NodeSelector;
diff --git a/frontend/src/__tests__/cypress/cypress/tests/mocked/modelServing/modelServingGlobal.cy.ts b/frontend/src/__tests__/cypress/cypress/tests/mocked/modelServing/modelServingGlobal.cy.ts
index 1656f4464f..ebd3e1aa5d 100644
--- a/frontend/src/__tests__/cypress/cypress/tests/mocked/modelServing/modelServingGlobal.cy.ts
+++ b/frontend/src/__tests__/cypress/cypress/tests/mocked/modelServing/modelServingGlobal.cy.ts
@@ -928,6 +928,34 @@ describe('Model Serving Global', () => {
     cy.findByTestId('app-page-title').should('have.text', 'Test Inference Service metrics');
   });
 
+  it('Should display env vars from a valueFrom secret', () => {
+    initIntercepts({
+      inferenceServices: [
+        mockInferenceServiceK8sResource({
+          env: [
+            {
+              name: 'value-from-secret-env-var',
+              valueFrom: { secretKeyRef: { name: 'test-secret', key: 'test-key' } },
+            },
+            {
+              name: 'key-value-env-var',
+              value: 'test-value',
+            },
+          ],
+        }),
+      ],
+    });
+    modelServingGlobal.visit('test-project');
+
+    modelServingGlobal.getModelRow('Test Inference Service').findKebabAction('Edit').click();
+    kserveModalEdit.findServingRuntimeEnvVarsValue('0').should('be.disabled');
+    kserveModalEdit
+      .findServingRuntimeEnvVarsValue('0')
+      .should('have.value', '{"secretKeyRef":{"name":"test-secret","key":"test-key"}}');
+    kserveModalEdit.findServingRuntimeEnvVarsValue('1').should('not.be.disabled');
+    kserveModalEdit.findServingRuntimeEnvVarsValue('1').should('have.value', 'test-value');
+  });
+
   describe('Table filter and pagination', () => {
     it('filter by name', () => {
       initIntercepts({});
diff --git a/frontend/src/concepts/connectionTypes/utils.ts b/frontend/src/concepts/connectionTypes/utils.ts
index b583103ebf..187feb43ec 100644
--- a/frontend/src/concepts/connectionTypes/utils.ts
+++ b/frontend/src/concepts/connectionTypes/utils.ts
@@ -430,22 +430,6 @@ export const findSectionFields = (
   return fields.slice(sectionIndex + 1, nextSectionIndex === -1 ? undefined : nextSectionIndex);
 };
 
-export const VALID_ENV_VARNAME_REGEX = /^[A-Za-z_][A-Za-z0-9_\-.]*$/;
-export const STARTS_WITH_DIGIT_REGEX = /^\d/;
-
-export const validateEnvVarName = (name: string): string | undefined => {
-  if (!name) {
-    return undefined;
-  }
-  if (STARTS_WITH_DIGIT_REGEX.test(name)) {
-    return 'Must not start with a digit.';
-  }
-  if (!VALID_ENV_VARNAME_REGEX.test(name)) {
-    return "Must consist of alphabetic characters, digits, '_', '-', or '.'";
-  }
-  return undefined;
-};
-
 export const convertObjectStorageSecretData = (dataConnection: Connection): AWSDataEntry => {
   let convertedData: { key: AwsKeys; value: string }[] = [];
   const secretData = dataConnection.data;
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 f05b2b1eae..453adb3834 100644
--- a/frontend/src/pages/modelServing/screens/projects/__tests__/utils.spec.ts
+++ b/frontend/src/pages/modelServing/screens/projects/__tests__/utils.spec.ts
@@ -7,6 +7,7 @@ import {
   getProjectModelServingPlatform,
   getUrlFromKserveInferenceService,
   isCurrentServingPlatformEnabled,
+  isValueFromEnvVar,
 } from '~/pages/modelServing/screens/projects/utils';
 import { ServingPlatformStatuses } from '~/pages/modelServing/screens/types';
 import { ServingRuntimePlatform } from '~/types';
@@ -551,3 +552,28 @@ describe('isCurrentServingPlatformEnabled', () => {
     expect(isCurrentServingPlatformEnabled(undefined, baseStatuses)).toBe(false);
   });
 });
+
+describe('isValueFrom', () => {
+  it('should return true if the value is from a valueFrom envVar', () => {
+    expect(
+      isValueFromEnvVar({
+        valueFrom: {
+          secretKeyRef: {
+            name: 'test',
+            key: '',
+          },
+        },
+        name: '',
+      }),
+    ).toBe(true);
+  });
+
+  it('should return false if the value is not from a valueFrom envVar', () => {
+    expect(
+      isValueFromEnvVar({
+        value: 'test',
+        name: '',
+      }),
+    ).toBe(false);
+  });
+});
diff --git a/frontend/src/pages/modelServing/screens/projects/kServeModal/EnvironmentVariablesSection.tsx b/frontend/src/pages/modelServing/screens/projects/kServeModal/EnvironmentVariablesSection.tsx
index 820ffcff49..fdd20548bb 100644
--- a/frontend/src/pages/modelServing/screens/projects/kServeModal/EnvironmentVariablesSection.tsx
+++ b/frontend/src/pages/modelServing/screens/projects/kServeModal/EnvironmentVariablesSection.tsx
@@ -23,7 +23,7 @@ import {
 } from '@patternfly/react-icons';
 import { UpdateObjectAtPropAndValue } from '~/pages/projects/types';
 import { CreatingInferenceServiceObject } from '~/pages/modelServing/screens/types';
-import { validateEnvVarName } from '~/concepts/connectionTypes/utils';
+import { isValueFromEnvVar, validateEnvVarName } from '~/pages/modelServing/screens/projects/utils';
 import { ServingContainer } from '~/k8sTypes';
 
 type EnvironmentVariablesSectionType = {
@@ -145,6 +145,7 @@ const EnvironmentVariablesSection: React.FC<EnvironmentVariablesSectionType> = (
       <Stack hasGutter>
         {data.servingRuntimeEnvVars?.map((envVar, index) => {
           const error = validateEnvVarName(envVar.name);
+          const isEnvVarValueFrom = isValueFromEnvVar(envVar);
           return (
             <Split hasGutter key={index}>
               <SplitItem isFilled>
@@ -171,14 +172,29 @@ const EnvironmentVariablesSection: React.FC<EnvironmentVariablesSectionType> = (
                 )}
               </SplitItem>
               <SplitItem isFilled>
-                <TextInput
-                  data-testid={`serving-runtime-environment-variables-input-value ${index}`}
-                  aria-label="env var value"
-                  value={envVar.value}
-                  onChange={(_event: React.FormEvent<HTMLInputElement>, value: string) =>
-                    updateEnvVar(index, { value })
+                <Tooltip
+                  trigger={isEnvVarValueFrom ? 'mouseenter' : ''}
+                  content={
+                    <>
+                      <div>{JSON.stringify(envVar.valueFrom)}</div>
+                      <br />
+                      <div>
+                        The value of this variable is defined by a key of a ConfigMap/Secret. It can
+                        only be edited from the OpenShift console.
+                      </div>
+                    </>
                   }
-                />
+                >
+                  <TextInput
+                    data-testid={`serving-runtime-environment-variables-input-value ${index}`}
+                    aria-label="env var value"
+                    value={envVar.value ? envVar.value : JSON.stringify(envVar.valueFrom)}
+                    isDisabled={isEnvVarValueFrom}
+                    onChange={(_event: React.FormEvent<HTMLInputElement>, value: string) =>
+                      updateEnvVar(index, { value })
+                    }
+                  />
+                </Tooltip>
               </SplitItem>
               <SplitItem>
                 <Button
diff --git a/frontend/src/pages/modelServing/screens/projects/kServeModal/ManageKServeModal.tsx b/frontend/src/pages/modelServing/screens/projects/kServeModal/ManageKServeModal.tsx
index 396003181e..06ee70af90 100644
--- a/frontend/src/pages/modelServing/screens/projects/kServeModal/ManageKServeModal.tsx
+++ b/frontend/src/pages/modelServing/screens/projects/kServeModal/ManageKServeModal.tsx
@@ -15,6 +15,7 @@ import {
   getSubmitServingRuntimeResourcesFn,
   useCreateInferenceServiceObject,
   useCreateServingRuntimeObject,
+  validateEnvVarName,
 } from '~/pages/modelServing/screens/projects/utils';
 import {
   TemplateKind,
@@ -58,7 +59,6 @@ import K8sNameDescriptionField, {
 import { isK8sNameDescriptionDataValid } from '~/concepts/k8s/K8sNameDescriptionField/utils';
 import { useProfileIdentifiers } from '~/concepts/hardwareProfiles/utils';
 import { useModelServingPodSpecOptionsState } from '~/concepts/hardwareProfiles/useModelServingPodSpecOptionsState';
-import { validateEnvVarName } from '~/concepts/connectionTypes/utils';
 import usePrefillModelDeployModal, {
   ModelDeployPrefillInfo,
 } from '~/pages/modelServing/screens/projects/usePrefillModelDeployModal';
diff --git a/frontend/src/pages/modelServing/screens/projects/utils.ts b/frontend/src/pages/modelServing/screens/projects/utils.ts
index 5b81f6233a..c7457a64a6 100644
--- a/frontend/src/pages/modelServing/screens/projects/utils.ts
+++ b/frontend/src/pages/modelServing/screens/projects/utils.ts
@@ -8,6 +8,7 @@ import {
   PersistentVolumeClaimKind,
   ProjectKind,
   SecretKind,
+  ServingContainer,
   ServingRuntimeKind,
 } from '~/k8sTypes';
 import { NamespaceApplicationCase, UpdateObjectAtPropAndValue } from '~/pages/projects/types';
@@ -742,3 +743,22 @@ export function isCurrentServingPlatformEnabled(
   const mappedKey = platformKeyMap[currentPlatform];
   return statuses[mappedKey].enabled;
 }
+
+export const VALID_ENV_VARNAME_REGEX = /^[A-Za-z_][A-Za-z0-9_\-.]*$/;
+export const STARTS_WITH_DIGIT_REGEX = /^\d/;
+
+export const validateEnvVarName = (name: string): string | undefined => {
+  if (!name) {
+    return undefined;
+  }
+  if (STARTS_WITH_DIGIT_REGEX.test(name)) {
+    return 'Must not start with a digit.';
+  }
+  if (!VALID_ENV_VARNAME_REGEX.test(name)) {
+    return "Must consist of alphabetic characters, digits, '_', '-', or '.'";
+  }
+  return undefined;
+};
+
+export const isValueFromEnvVar = (envVar: NonNullable<ServingContainer['env']>[number]): boolean =>
+  envVar.valueFrom !== undefined;


Reviews:
user: login: emilys314
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T20:42:10Z
user: login: katieperry4
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T12:17:30Z
user: login: emilys314
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T16:59:51Z
user: login: katieperry4
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T17:48:15Z
user: login: emilys314
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T18:30:19Z
user: login: emilys314
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T18:30:55Z

Review comments:
diff_hunk: @@ -446,6 +446,9 @@ export const validateEnvVarName = (name: string): string | undefined => {
   return undefined;
 };
 
+export const isValueFromEnvVar = (envVar: NonNullable<ServingContainer['env']>[number]): boolean =>
path: frontend/src/concepts/connectionTypes/utils.ts
user: login: emilys314
body: This doesn't belong in `connectionTypes/utils.ts`. 

(Also there does exist a `EnvironmentVariable` type that could be used to make things simpler. But what currently exists does work too)
created_at: 2025-05-21T20:42:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -446,6 +446,9 @@ export const validateEnvVarName = (name: string): string | undefined => {
   return undefined;
 };
 
+export const isValueFromEnvVar = (envVar: NonNullable<ServingContainer['env']>[number]): boolean =>
path: frontend/src/concepts/connectionTypes/utils.ts
user: login: katieperry4
body: I put it in there because
```
export const validateEnvVarName = (name: string): string | undefined => {
  if (!name) {
    return undefined;
  }
  if (STARTS_WITH_DIGIT_REGEX.test(name)) {
    return 'Must not start with a digit.';
  }
  if (!VALID_ENV_VARNAME_REGEX.test(name)) {
    return "Must consist of alphabetic characters, digits, '_', '-', or '.'";
  }
  return undefined;
};
```
is right above it in that `util` file 😅 but I'll look at moving it and look into that type too
created_at: 2025-05-22T12:17:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -446,6 +446,9 @@ export const validateEnvVarName = (name: string): string | undefined => {
   return undefined;
 };
 
+export const isValueFromEnvVar = (envVar: NonNullable<ServingContainer['env']>[number]): boolean =>
path: frontend/src/concepts/connectionTypes/utils.ts
user: login: emilys314
body: Ah interesting haha. The `validateEnvVarName` could probably be put in a better location too then
created_at: 2025-05-22T16:59:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -446,6 +446,9 @@ export const validateEnvVarName = (name: string): string | undefined => {
   return undefined;
 };
 
+export const isValueFromEnvVar = (envVar: NonNullable<ServingContainer['env']>[number]): boolean =>
path: frontend/src/concepts/connectionTypes/utils.ts
user: login: katieperry4
body: they're moved! I didn't end up using the `EnvironmentVariable` type since the `valueFrom` structure is different from what the env section is using:
```
export type EnvironmentVariable = EitherNotBoth<
  { value: string | number },
  { valueFrom: Record<string, unknown> }
> & {
  name: string;
};
```
vs
```
servingRuntimeEnvVars?: {
    name: string;
    value?: string;
    valueFrom?: {
        secretKeyRef?: {
            name: string;
            key: string;
        };
    };
}[] | undefined
```

I could try and convert them but not sure it's worth it for a one line check to see if `valueFrom` is there or not?
created_at: 2025-05-22T17:48:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -446,6 +446,9 @@ export const validateEnvVarName = (name: string): string | undefined => {
   return undefined;
 };
 
+export const isValueFromEnvVar = (envVar: NonNullable<ServingContainer['env']>[number]): boolean =>
path: frontend/src/concepts/connectionTypes/utils.ts
user: login: emilys314
body: sounds good!
created_at: 2025-05-22T18:30:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -446,6 +446,9 @@ export const validateEnvVarName = (name: string): string | undefined => {
   return undefined;
 };
 
+export const isValueFromEnvVar = (envVar: NonNullable<ServingContainer['env']>[number]): boolean =>
path: frontend/src/concepts/connectionTypes/utils.ts
user: login: emilys314
body: The type is fine to leave as is 
created_at: 2025-05-22T18:30:55Z
author_association: CONTRIBUTOR

PR comments:
user: login: codecov[bot]
created_at: 2025-05-20T20:22:30Z
body: ## [Codecov](https://app.codecov.io/gh/opendatahub-io/odh-dashboard/pull/4238?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 82.72%. Comparing base [(`7baf8b5`)](https://app.codecov.io/gh/opendatahub-io/odh-dashboard/commit/7baf8b598d461452582267595e8be3efdbd9161d?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opendatahub-io) to head [(`a30ece8`)](https://app.codecov.io/gh/opendatahub-io/odh-dashboard/commit/a30ece8978c817acb7a66d9583eac867d88ef47a?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opendatahub-io).
> Report is 14 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/4238/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/4238?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    #4238   +/-   ##
=======================================
  Coverage   82.72%   82.72%           
=======================================
  Files        1653     1653           
  Lines       34891    34886    -5     
  Branches    10329    10316   -13     
=======================================
- Hits        28862    28859    -3     
+ Misses       6029     6027    -2     
```

| [Files with missing lines](https://app.codecov.io/gh/opendatahub-io/odh-dashboard/pull/4238?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/concepts/connectionTypes/utils.ts](https://app.codecov.io/gh/opendatahub-io/odh-dashboard/pull/4238?src=pr&el=tree&filepath=frontend%2Fsrc%2Fconcepts%2FconnectionTypes%2Futils.ts&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opendatahub-io#diff-ZnJvbnRlbmQvc3JjL2NvbmNlcHRzL2Nvbm5lY3Rpb25UeXBlcy91dGlscy50cw==) | `94.44% <ø> (-0.23%)` | :arrow_down: |
| [...ojects/kServeModal/EnvironmentVariablesSection.tsx](https://app.codecov.io/gh/opendatahub-io/odh-dashboard/pull/4238?src=pr&el=tree&filepath=frontend%2Fsrc%2Fpages%2FmodelServing%2Fscreens%2Fprojects%2FkServeModal%2FEnvironmentVariablesSection.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opendatahub-io#diff-ZnJvbnRlbmQvc3JjL3BhZ2VzL21vZGVsU2VydmluZy9zY3JlZW5zL3Byb2plY3RzL2tTZXJ2ZU1vZGFsL0Vudmlyb25tZW50VmFyaWFibGVzU2VjdGlvbi50c3g=) | `88.88% <100.00%> (+3.17%)` | :arrow_up: |
| [...screens/projects/kServeModal/ManageKServeModal.tsx](https://app.codecov.io/gh/opendatahub-io/odh-dashboard/pull/4238?src=pr&el=tree&filepath=frontend%2Fsrc%2Fpages%2FmodelServing%2Fscreens%2Fprojects%2FkServeModal%2FManageKServeModal.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=opendatahub-io#diff-ZnJvbnRlbmQvc3JjL3BhZ2VzL21vZGVsU2VydmluZy9zY3JlZW5zL3Byb2plY3RzL2tTZXJ2ZU1vZGFsL01hbmFnZUtTZXJ2ZU1vZGFsLnRzeA==) | `93.52% <ø> (ø)` | |
| [...d/src/pages/modelServing/screens/projects/utils.ts](https://app.codecov.io/gh/opendatahub-io/odh-dashboard/pull/4238?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.20% <100.00%> (+0.32%)` | :arrow_up: |

... and [58 files with indirect coverage changes](https://app.codecov.io/gh/opendatahub-io/odh-dashboard/pull/4238/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/4238?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/4238?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 [15d4d2d...a30ece8](https://app.codecov.io/gh/opendatahub-io/odh-dashboard/pull/4238?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: andrewballantyne
created_at: 2025-05-21T20:45:04Z
body: PO 👍 
author_association: MEMBER
user: login: emilys314
created_at: 2025-05-22T19:08:16Z
body: looks good!
/lgtm
/approve

![image](https://github.com/user-attachments/assets/26ba0a8b-3c04-4dd8-bd23-1be0da1df3a6)

author_association: CONTRIBUTOR
user: login: openshift-ci[bot]
created_at: 2025-05-22T19:08:24Z
body: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/opendatahub-io/odh-dashboard/pull/4238#issuecomment-2902281947" title="Approved">emilys314</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)~~ [emilys314]

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