Skip to content

Jonburdo 20260422 sync#1714

Merged
openshift-merge-bot[bot] merged 13 commits into
opendatahub-io:mainfrom
jonburdo:jonburdo-20260422-sync
Apr 22, 2026
Merged

Jonburdo 20260422 sync#1714
openshift-merge-bot[bot] merged 13 commits into
opendatahub-io:mainfrom
jonburdo:jonburdo-20260422-sync

Conversation

@jonburdo
Copy link
Copy Markdown
Member

@jonburdo jonburdo commented Apr 22, 2026

supersedes #1713
to fix merge conflicts

Description

How Has This Been Tested?

Merge criteria:

  • All the commits have been signed-off (To pass the DCO check)
  • The commits have meaningful messages
  • Automated tests are provided as part of the PR for major new functionalities; testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious).
  • The developer has manually tested the changes and verified that the changes work.
  • Code changes follow the kubeflow contribution guidelines.
  • For first time contributors: Please reach out to the Reviewers to ensure all tests are being run, ensuring the label ok-to-test has been added to the PR.

If you have UI changes

  • The developer has added tests or explained why testing cannot be added.
  • Included any necessary screenshots or gifs if it was a UI change.
  • Verify that UI/UX changes conform the UX guidelines for Kubeflow.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added single-category view mode with automatic selection when only one source category is available.
    • Added error message display and retry functionality for failed model transfer jobs.
    • Enhanced model details view with markdown rendering support.
  • Bug Fixes

    • Updated catalog source validation status label from "Connected" to "Ready".
    • Improved empty state messaging for catalogs with no active categories.
  • UI/UX Improvements

    • Centralized user-facing message strings for consistency.
    • Enhanced form layouts with improved helper text organization.
    • Refined organization field labeling and model visibility filtering copy.

dependabot Bot and others added 13 commits April 20, 2026 22:45
Bumps [actions/cache](https://github.com/actions/cache) from 5.0.4 to 5.0.5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](actions/cache@6682284...27d5ce7)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: 5.0.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [mikefarah/yq](https://github.com/mikefarah/yq) from 4.52.5 to 4.53.2.
- [Release notes](https://github.com/mikefarah/yq/releases)
- [Changelog](https://github.com/mikefarah/yq/blob/master/release_notes.txt)
- [Commits](mikefarah/yq@0f4fb8d...751d8ad)

---
updated-dependencies:
- dependency-name: mikefarah/yq
  dependency-version: 4.53.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: manaswinidas <dasmanaswini10@gmail.com>
* fix pf5 to pf6

Signed-off-by: Philip Colares Carneiro <philip.colares@gmail.com>

* start to change the texts

Signed-off-by: Philip Colares Carneiro <philip.colares@gmail.com>

* change text and add them to constants

Signed-off-by: Philip Colares Carneiro <philip.colares@gmail.com>

* fix constants text

Signed-off-by: Philip Colares Carneiro <philip.colares@gmail.com>

* fix helper text prosition

Signed-off-by: Philip Colares Carneiro <philip.colares@gmail.com>

* add fixes requested

Signed-off-by: Philip Colares Carneiro <philip.colares@gmail.com>

* fix layout issue

Signed-off-by: Philip Colares Carneiro <philip.colares@gmail.com>

* fix changes requested

Signed-off-by: Philip Colares Carneiro <philip.colares@gmail.com>

---------

Signed-off-by: Philip Colares Carneiro <philip.colares@gmail.com>
…kubeflow#2614)

Bumps [k8s.io/api](https://github.com/kubernetes/api) from 0.35.3 to 0.35.4.
- [Commits](kubernetes/api@v0.35.3...v0.35.4)

---
updated-dependencies:
- dependency-name: k8s.io/api
  dependency-version: 0.35.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…kubeflow#2593)

The operator renamed the ConfigMap from model-catalog-default-sources
to default-catalog-sources in model-registry-operator#489, but the BFF
and kustomize manifests were not updated, causing a 500 error on the
Model Catalog Settings page.

Ref: RHOAIENG-57824

Signed-off-by: Mike Turley <mike.turley@alum.cs.umass.edu>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…ubeflow#2600)

* add helper text for hf

Signed-off-by: Taj010 <rsheen003@gmail.com>

* adjust the message

Signed-off-by: Taj010 <rsheen003@gmail.com>

* rebase and adjust

Signed-off-by: Taj010 <rsheen003@gmail.com>

---------

Signed-off-by: Taj010 <rsheen003@gmail.com>
…#2624)

AI-assisted: Claude Code (Opus 4.6)

Signed-off-by: Debarati Basu-Nag <dbasunag@redhat.com>
* fix a11y from model details

Signed-off-by: Philip Colares Carneiro <philip.colares@gmail.com>

* add changes requested

Signed-off-by: Philip Colares Carneiro <philip.colares@gmail.com>

---------

Signed-off-by: Philip Colares Carneiro <philip.colares@gmail.com>
* hide cat toogle in case only one cat is available

Signed-off-by: Philip Colares Carneiro <philip.colares@gmail.com>

* fix changes requested in tests

Signed-off-by: Philip Colares Carneiro <philip.colares@gmail.com>

* fix changes requested

Signed-off-by: Philip Colares Carneiro <philip.colares@gmail.com>

* add changes requested

Signed-off-by: Philip Colares Carneiro <philip.colares@gmail.com>

* add changes requested

Signed-off-by: Philip Colares Carneiro <philip.colares@gmail.com>

---------

Signed-off-by: Philip Colares Carneiro <philip.colares@gmail.com>
* truncate error msg from transfer jobs

Signed-off-by: Philip Colares Carneiro <philip.colares@gmail.com>

* add changes requested and updated tests

Signed-off-by: Philip Colares Carneiro <philip.colares@gmail.com>

---------

Signed-off-by: Philip Colares Carneiro <philip.colares@gmail.com>
…acts the run (kubeflow#2628)

Signed-off-by: Debarati Basu-Nag <dbasunag@redhat.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

📝 Walkthrough

Walkthrough

This PR refactors catalog source configuration and model catalog browsing behavior. Changes include: renaming the default catalog sources ConfigMap from model-catalog-default-sources to default-catalog-sources across Go code, Kubernetes test fixtures, and Kustomize manifests; adding automatic single-category UI selection when only one catalog source label is available; updating catalog source status label from "Connected" to "Ready"; reorganizing UI text constants (renaming HELP_TEXT to DESCRIPTION_TEXT, extracting new ERROR_MESSAGES, SUCCESS_MESSAGES, and EMPTY_STATE_TEXT exports); expanding transfer job UI to display error messages and retry actions; and bumping Kubernetes API dependencies (k8s.io/api, k8s.io/apimachinery v0.35.3 → v0.35.4).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes


Security & Actionable Issues

No immediate CWE/CVE-level vulnerabilities detected in this diff. However, flag these for verification:

  • Kubernetes dependency bump (v0.35.3 → v0.35.4): Confirm this minor version bump does not contain security patches bundled with breaking changes or unexpected behavioral shifts in client initialization (k8s.io/client-go interaction patterns).
  • Constants extraction (ERROR_MESSAGES, SUCCESS_MESSAGES): Verify all UI text strings are properly escaped and do not allow user-controlled content to pass through without sanitization when rendered in EmptyState, Alert, or modal components.
  • Form error handling (CredentialsSection, ManageSourceForm): The catch block now uses ERROR_MESSAGES.SAVE_FAILED for non-Error instances. Confirm that actual error details (potentially containing sensitive API keys or credentials) are not logged or exposed to the frontend; error swallowing without audit logging could obscure failures.
  • YAML file upload error handling (YamlSection): The new fileUploadError state is displayed inline. Ensure FileReader.onerror messages do not leak file system paths or privileged information in browser environments.
🚥 Pre-merge checks | ✅ 2 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is incomplete: it references a superseded PR and mentions merge conflicts but provides no implementation details, testing instructions, or meaningful explanation of the changes. Fill in the Description section with a detailed summary of changes, specify how changes were tested, and mark completed merge checklist items.
Title check ❓ Inconclusive The title 'Jonburdo 20260422 sync' is vague and does not clearly describe the main changes in the changeset, using a branch-naming convention rather than a descriptive summary. Provide a descriptive title that summarizes the primary change (e.g., 'Hide catalog toggle for single-category view and add transfer job error handling').
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
clients/ui/frontend/src/__tests__/cypress/cypress/tests/mocked/modelCatalogSettings/modelCatalogSettings.cy.ts (1)

436-448: ⚠️ Potential issue | 🟡 Minor

Stale test title "Connected".

The it(...) description still says should show "Connected" status for available sources, but the assertion on line 446 now checks for 'Ready'. Rename the test so greppability doesn't suffer.

Proposed tweak
-    it('should show "Connected" status for available sources', () => {
+    it('should show "Ready" status for available sources', () => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@clients/ui/frontend/src/__tests__/cypress/cypress/tests/mocked/modelCatalogSettings/modelCatalogSettings.cy.ts`
around lines 436 - 448, The test title in the it(...) block for the case using
mockCatalogSource('hf-google') is stale — it says "should show \"Connected\"
status for available sources" but the assertion uses
row.shouldHaveValidationStatus('Ready') and checks for
'source-status-connected-hf-google'; update the test description to match the
assertion (e.g., change the it(...) string to "should show 'Ready' status for
available sources" or equivalent) so the test name reflects
modelCatalogSettings.getRow('HuggingFace Google') expectations and keeps
greppability accurate.
clients/ui/frontend/src/app/pages/modelCatalogSettings/components/ModelVisibilitySection.tsx (1)

66-132: ⚠️ Potential issue | 🟠 Major

Remove permanently-active error styling from both textareas.

ThemeAwareFormGroupWrapper sets error state (pf-m-error) whenever helperTextNode is defined: const hasError = !!helperTextNode;. Lines 70–75 and 99–104 unconditionally define allowedModelsHelperTxtNode and excludedModelsHelperTxtNode as React nodes wrapping purely informational copy (getIncludedModelsFieldHelperText / getExcludedModelsFieldHelperText). Passing these to helperTextNode forces both form groups into the error variant on every render—red borders and error-styled labels on fields with no validation failure.

Route the informational text through descriptionTextNode instead and omit helperTextNode. Merge the two text items into a single description node:

🔧 Suggested fix: consolidate informational text into description node
-  const allowedModelsDescriptionTxtNode = (
-    <FormHelperText>
-      <HelperText>
-        <HelperTextItem>{getAllowedModelsHelp(organization)}</HelperTextItem>
-      </HelperText>
-    </FormHelperText>
-  );
-
-  const allowedModelsHelperTxtNode = (
-    <FormHelperText>
-      <HelperText>
-        <HelperTextItem>{getIncludedModelsFieldHelperText}</HelperTextItem>
-      </HelperText>
-    </FormHelperText>
-  );
+  const allowedModelsDescriptionTxtNode = (
+    <FormHelperText>
+      <HelperText>
+        <HelperTextItem>{getAllowedModelsHelp(organization)}</HelperTextItem>
+        <HelperTextItem>{getIncludedModelsFieldHelperText}</HelperTextItem>
+      </HelperText>
+    </FormHelperText>
+  );
@@
-  const excludedModelsDescriptionTxtNode = (
-    <FormHelperText>
-      <HelperText>
-        <HelperTextItem>{getExcludedModelsHelp(organization)}</HelperTextItem>
-      </HelperText>
-    </FormHelperText>
-  );
-
-  const excludedModelsHelperTxtNode = (
-    <FormHelperText>
-      <HelperText>
-        <HelperTextItem>{getExcludedModelsFieldHelperText}</HelperTextItem>
-      </HelperText>
-    </FormHelperText>
-  );
+  const excludedModelsDescriptionTxtNode = (
+    <FormHelperText>
+      <HelperText>
+        <HelperTextItem>{getExcludedModelsHelp(organization)}</HelperTextItem>
+        <HelperTextItem>{getExcludedModelsFieldHelperText}</HelperTextItem>
+      </HelperText>
+    </FormHelperText>
+  );
@@
-        <ThemeAwareFormGroupWrapper
-          label={FORM_LABELS.ALLOWED_MODELS}
-          fieldId="allowed-models"
-          descriptionTextNode={allowedModelsDescriptionTxtNode}
-          helperTextNode={allowedModelsHelperTxtNode}
-        >
+        <ThemeAwareFormGroupWrapper
+          label={FORM_LABELS.ALLOWED_MODELS}
+          fieldId="allowed-models"
+          descriptionTextNode={allowedModelsDescriptionTxtNode}
+        >
@@
-        <ThemeAwareFormGroupWrapper
-          label={FORM_LABELS.EXCLUDED_MODELS}
-          fieldId="excluded-models"
-          descriptionTextNode={excludedModelsDescriptionTxtNode}
-          helperTextNode={excludedModelsHelperTxtNode}
-        >
+        <ThemeAwareFormGroupWrapper
+          label={FORM_LABELS.EXCLUDED_MODELS}
+          fieldId="excluded-models"
+          descriptionTextNode={excludedModelsDescriptionTxtNode}
+        >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@clients/ui/frontend/src/app/pages/modelCatalogSettings/components/ModelVisibilitySection.tsx`
around lines 66 - 132, The two helper nodes allowedModelsHelperTxtNode and
excludedModelsHelperTxtNode are being passed into
ThemeAwareFormGroupWrapper.helperTextNode which forces pf-m-error styling;
delete those helper node variables, and instead merge their informational copy
(getIncludedModelsFieldHelperText / getExcludedModelsFieldHelperText) into the
corresponding descriptionTextNode (allowedModelsDescriptionTxtNode /
excludedModelsDescriptionTxtNode) so the informational text is rendered as
description, not an error. Update the two ThemeAwareFormGroupWrapper usages to
remove the helperTextNode prop entirely and supply the combined
descriptionTextNode (retain existing description generators like
getExcludedModelsHelp(organization) and append the helper text inside the same
FormHelperText/HelperText/HelperTextItem node). Ensure no other code references
allowedModelsHelperTxtNode or excludedModelsHelperTxtNode after the change.
clients/ui/frontend/src/app/pages/mcpCatalog/screens/McpCatalogGalleryView.tsx (1)

54-62: ⚠️ Potential issue | 🟠 Major

Use singleCategoryLabel in the server query.

In single-category mode, selectedSourceLabel can still be undefined on the first render, so this requests all servers while the UI is meant to show one category.

Proposed fix
   const filterQuery = React.useMemo(() => mcpFiltersToFilterQuery(filters), [filters]);
+  const effectiveSourceLabel = isSingleCategory
+    ? singleCategoryLabel ?? selectedSourceLabel
+    : selectedSourceLabel;
 
   const { mcpServers, mcpServersLoaded, mcpServersLoadError } = useMcpServersBySourceLabelWithAPI(
     mcpApiState,
     {
-      sourceLabel: selectedSourceLabel,
+      sourceLabel: effectiveSourceLabel,
       pageSize: PAGE_SIZE,
       searchQuery,
       filterQuery: filterQuery || undefined,
@@
-  const effectiveCategoryLabel = singleCategoryLabel || selectedSourceLabel || '';
+  const effectiveCategoryLabel = effectiveSourceLabel || '';

Also applies to: 103-114

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@clients/ui/frontend/src/app/pages/mcpCatalog/screens/McpCatalogGalleryView.tsx`
around lines 54 - 62, The server query currently uses selectedSourceLabel which
may be undefined on first render; update the call to
useMcpServersBySourceLabelWithAPI so it prefers singleCategoryLabel when present
(e.g., sourceLabel: singleCategoryLabel || selectedSourceLabel) so
single-category mode always queries the correct category; make the same change
for the other occurrence around the use of useMcpServersBySourceLabelWithAPI
later in the file (lines with the second call).
clients/ui/frontend/src/app/pages/modelCatalog/screens/ModelCatalogGalleryView.tsx (1)

108-119: ⚠️ Potential issue | 🟠 Major

Use the single-category label for the data request too.

singleCategoryLabel only drives the header. In single-category mode, selectedSourceLabel can still be CategoryName.allModels on the first render, so the hook requests all models before the auto-select effect catches up.

Proposed fix
+  const effectiveSourceLabel = isSingleCategory
+    ? singleCategoryLabel || selectedSourceLabel
+    : selectedSourceLabel;
+
   const { catalogModels, catalogModelsLoaded, catalogModelsLoadError } = useCatalogModelsBySources(
     '',
-    selectedSourceLabel === CategoryName.allModels ? undefined : selectedSourceLabel,
+    effectiveSourceLabel === CategoryName.allModels ? undefined : effectiveSourceLabel,
     10,
     searchTerm,
     effectiveFilterData,
@@
-  const effectiveCategoryLabel = singleCategoryLabel || selectedSourceLabel || '';
+  const effectiveCategoryLabel = effectiveSourceLabel || '';

Also applies to: 281-287

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@clients/ui/frontend/src/app/pages/modelCatalog/screens/ModelCatalogGalleryView.tsx`
around lines 108 - 119, When calling useCatalogModelsBySources in
ModelCatalogGalleryView, ensure the hook receives the single-category label when
singleCategoryLabel is set so the initial request doesn't fetch all models;
replace the current source arg logic with one that prefers singleCategoryLabel
(e.g., use singleCategoryLabel ? singleCategoryLabel : (selectedSourceLabel ===
CategoryName.allModels ? undefined : selectedSourceLabel)). Apply the same
change to the other useCatalogModelsBySources call later in the file (the one
around the second occurrence).
🧹 Nitpick comments (6)
jobs/async-upload/tests/integration/test_integration_async_upload.py (1)

214-214: Centralize the namespace value before adding more call sites.

Line 214 and Line 227 duplicate "default" alongside delete, wait/log, and cleanup paths. A later namespace change can make apply/describe drift from the rest of the test flow; use one shared constant.

Proposed refactor
+K8S_NAMESPACE = "default"
+
 ...
-            "default",
+            K8S_NAMESPACE,
 ...
-        ["kubectl", "apply", "-k", ".", "-n", "default"],
+        ["kubectl", "apply", "-k", ".", "-n", K8S_NAMESPACE],
 ...
-        ["kubectl", "describe", "jobs/my-async-upload-job", "-n", "default"],
+        ["kubectl", "describe", "jobs/my-async-upload-job", "-n", K8S_NAMESPACE],

Also applies to: 227-227

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jobs/async-upload/tests/integration/test_integration_async_upload.py` at line
214, The test duplicates the literal "default" namespace in multiple kubectl
invocations (apply, delete, describe, wait/log and cleanup) in
test_integration_async_upload.py; introduce a single constant (e.g., NAMESPACE =
"default") at the top of the test module or the test function and replace all
occurrences of the string literal in the kubectl command lists (the arrays used
for ["kubectl", "apply", "-k", ".", "-n", "default"], ["kubectl", "delete", ...
"-n", "default"], and the describe/wait/cleanup command arrays) with that
constant so future namespace changes only require one edit.
clients/ui/frontend/src/app/pages/modelRegistry/screens/ModelTransferJobs/ModelTransferJobTableRow.tsx (1)

164-179: Extract inline style to a CSS class.

Inline style={{ textDecoration: 'underline dotted', textUnderlineOffset: 'var(--pf-t--global--spacer--xs)' }} on a PF Button variant="link" is repeated per row and bypasses the design-system stylesheet. Consider a dedicated class (e.g., pf-v6-u-text-decoration-underline-dotted equivalent in your CSS module) so the rule can be restyled centrally and memoized by the browser.

♻️ Suggested refactor
-              <Button
-                variant="link"
-                isInline
-                onClick={() => setIsStatusModalOpen(true)}
-                data-testid="job-error-message"
-                style={{
-                  textDecoration: 'underline dotted',
-                  textUnderlineOffset: 'var(--pf-t--global--spacer--xs)',
-                }}
-              >
+              <Button
+                variant="link"
+                isInline
+                onClick={() => setIsStatusModalOpen(true)}
+                data-testid="job-error-message"
+                className="mr-transfer-job-error-link"
+              >
                 <Truncate content={job.errorMessage} />
               </Button>

…and add the rule to the accompanying stylesheet:

.mr-transfer-job-error-link {
  text-decoration: underline dotted;
  text-underline-offset: var(--pf-t--global--spacer--xs);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@clients/ui/frontend/src/app/pages/modelRegistry/screens/ModelTransferJobs/ModelTransferJobTableRow.tsx`
around lines 164 - 179, Extract the inline style on the PF Button in
ModelTransferJobTableRow (the Button rendered when job.status ===
ModelTransferJobStatus.FAILED) into a dedicated CSS class (e.g.,
mr-transfer-job-error-link), remove the style prop and apply the class via
className on the Button, add the CSS rule (text-decoration: underline dotted;
text-underline-offset: var(--pf-t--global--spacer--xs);) to the component
stylesheet or CSS module, and ensure the stylesheet is imported (and class
referenced) where the Button and Truncate are used so the visual rule is
centralized and not repeated per row.
clients/ui/frontend/src/app/pages/modelCatalogSettings/components/CatalogSourceStatus.tsx (1)

54-59: Stale data-testid after label rename.

The label text flipped from "Connected" to "Ready", but data-testid={source-status-connected-${id}} still carries the old vocabulary. Cypress still relies on this id (see modelCatalogSettings.cy.ts line 447), so renaming is a follow-up, but the mismatch will bite the next person grepping for "Ready". Consider aligning the testid (e.g., source-status-ready-…) and updating the Cypress selector in lockstep.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@clients/ui/frontend/src/app/pages/modelCatalogSettings/components/CatalogSourceStatus.tsx`
around lines 54 - 59, In the CatalogSourceStatus.tsx branch for
CatalogSourceStatusEnum.AVAILABLE, update the stale data-testid value from
`source-status-connected-${catalogSourceConfig.id}` to
`source-status-ready-${catalogSourceConfig.id}` to match the label text change,
and then update the Cypress selector(s) in the corresponding model catalog
settings test that currently reference the old `source-status-connected-…` id to
the new `source-status-ready-…` id so tests remain in sync.
clients/ui/frontend/src/app/pages/modelCatalogSettings/constants.tsx (1)

134-140: get*FieldHelperText are strings, not getters — rename to avoid misuse.

Both getIncludedModelsFieldHelperText and getExcludedModelsFieldHelperText are plain string constants but carry the get prefix idiomatic of functions/accessors (contrast with genuine getters getFilterInfoWithOrg / getAllowedModelsHelp in the same file). Consumers have already been tripped up — e.g. ModelVisibilitySection.tsx lines 69, 98 embed them as {getIncludedModelsFieldHelperText} which works by accident; any future refactor that tries to "call" them (getIncludedModelsFieldHelperText()) will return undefined at runtime.

-/** Same for HF and YAML sources. */
-export const getIncludedModelsFieldHelperText =
-  'Separate model names using commas. To include all models with a specific prefix, enter the prefix followed by an asterisk. Example, Llama*';
-
-/** Same for HF and YAML sources. */
-export const getExcludedModelsFieldHelperText =
-  'Separate model names using commas. To exclude all models with a specific prefix, enter the prefix followed by an asterisk. Example, Llama*';
+/** Same for HF and YAML sources. */
+export const INCLUDED_MODELS_FIELD_HELPER_TEXT =
+  'Separate model names using commas. To include all models with a specific prefix, enter the prefix followed by an asterisk. Example, Llama*';
+
+/** Same for HF and YAML sources. */
+export const EXCLUDED_MODELS_FIELD_HELPER_TEXT =
+  'Separate model names using commas. To exclude all models with a specific prefix, enter the prefix followed by an asterisk. Example, Llama*';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/ui/frontend/src/app/pages/modelCatalogSettings/constants.tsx` around
lines 134 - 140, Rename the misleading constants
getIncludedModelsFieldHelperText and getExcludedModelsFieldHelperText to
non-getter names (e.g., includedModelsFieldHelperText and
excludedModelsFieldHelperText) and update all usages to the new identifiers
(including JSX embeddings like where ModelVisibilitySection references them) so
callers don't accidentally try to invoke them as functions; keep the string
values unchanged and ensure any exports/imports are updated accordingly.
clients/ui/bff/internal/integrations/kubernetes/k8mocks/base_testenv.go (1)

350-350: Use the ConfigMap name constant in the error path.

Line 350 duplicates the value already sourced from k8s.CatalogSourceDefaultConfigMapName on Line 340, so future renames can make this diagnostic stale.

Keep diagnostics tied to the source of truth
-		return fmt.Errorf("failed to create default-catalog-sources configmap: %w", err)
+		return fmt.Errorf("failed to create %s configmap: %w", k8s.CatalogSourceDefaultConfigMapName, err)

As per coding guidelines, **: REVIEW PRIORITIES: 3. Bug-prone patterns and error handling gaps.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/ui/bff/internal/integrations/kubernetes/k8mocks/base_testenv.go` at
line 350, The error message currently hardcodes "default-catalog-sources"
instead of referencing the shared constant; update the failing return in the
configmap creation path to use k8s.CatalogSourceDefaultConfigMapName (the same
constant used on the creation line) so the error text stays in sync with the
source-of-truth; locate the configmap creation function/return that currently
does `fmt.Errorf("failed to create default-catalog-sources configmap: %w", err)`
and replace the hardcoded name with the constant.
clients/ui/bff/go.mod (1)

15-17: Align direct Kubernetes module versions.

Lines 15–17 mix k8s.io/api and k8s.io/apimachinery at v0.35.4 with k8s.io/client-go at v0.35.3. v0.35.4 of client-go is available; upgrade it for consistent API machinery and client versioning.

Version alignment patch
 	k8s.io/api v0.35.4
 	k8s.io/apimachinery v0.35.4
-	k8s.io/client-go v0.35.3
+	k8s.io/client-go v0.35.4
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/ui/bff/go.mod` around lines 15 - 17, Update the Kubernetes module
versions so they are consistent: change the k8s.io/client-go module version from
v0.35.3 to v0.35.4 to match k8s.io/api and k8s.io/apimachinery (both v0.35.4);
after updating the k8s.io/client-go version in go.mod run go mod tidy (or the
equivalent dependency refresh) to ensure the dependency tree is reconciled.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@catalog/clients/python/patches/include-readonly-timestamps.patch`:
- Around line 4-11: Remove the duplicated docstring line in BaseResource and
make BaseResource.to_dict()'s excluded_fields consistent with CatalogModel by
ensuring create_time_since_epoch and last_update_time_since_epoch are not in the
excluded_fields set (so timestamps are serialized on reads); update the
excluded_fields variable in BaseResource.to_dict() accordingly and keep only one
instance of the "OpenAPI `readOnly` fields are excluded." sentence in the
docstring.

In
`@clients/ui/frontend/src/__tests__/cypress/cypress/tests/mocked/modelCatalog/modelCatalog.cy.ts`:
- Around line 550-558: The negative assertions run before the catalog finishes
rendering; after calling initIntercepts(...) and modelCatalog.visit(), wait for
a positive "catalog loaded" signal before asserting absence of the toggles —
e.g., use an existing helper or element (for example call
modelCatalog.waitForLoad() if available or assert
cy.findByTestId('model-catalog-list').should('exist')) and only then check
cy.findByTestId('label-Provider one').should('not.exist') and
cy.findByTestId('all').should('not.exist').

In
`@clients/ui/frontend/src/__tests__/cypress/cypress/tests/mocked/modelRegistry/modelTransferJobs.cy.ts`:
- Around line 572-589: The test registers an intercept alias '@retryJob' but
never triggers or asserts it; update the test in the it('should show retry
button...') block to actually submit the retry and wait for the PUT request:
after opening the retry modal via modelTransferJobsPage.findRetryModal() and
verifying the submit button
(modelTransferJobsPage.findRetryModalSubmitButton()), click that submit button,
then cy.wait('@retryJob') and assert the request/response (status or request
body) as needed; alternatively remove the cy.intercept(...) alias if you choose
not to exercise the retry path—update the code around
interceptWithSingleFailedJob, the cy.intercept(...) alias '@retryJob', and
modelTransferJobsPage.findRetryModalSubmitButton() usage accordingly.

In `@clients/ui/frontend/src/app/pages/modelCatalog/screens/ModelDetailsView.tsx`:
- Around line 168-175: ModelDetailsView renders an actionable ExternalLink for
the license even when model.licenseLink is falsy, causing window.open('') and a
blank tab; update the JSX in the ModelDetailsView component to conditionally
render the ExternalLink only when model.licenseLink is truthy and otherwise
render a plain textual fallback (as done in McpServerDetailsView), using the
same testId "model-license-link" for the link case and ensuring no clickable
element is produced when licenseLink is missing.

In `@clients/ui/frontend/src/app/pages/modelCatalog/utils/modelCatalogUtils.ts`:
- Around line 753-766: getActiveSourceLabels is losing sources because
getUniqueSourceLabels treats sources with enabled === undefined as inactive
while filterEnabledCatalogSources already treats only enabled === false as
disabled; update getUniqueSourceLabels so it treats any source with enabled !==
false as active (i.e., consider undefined as enabled) when collecting labels,
ensuring behavior matches filterEnabledCatalogSources and preserving labeled
sources for hasSourcesWithoutLabels and ordering via orderLabelsByPriority.

In
`@clients/ui/frontend/src/app/pages/modelCatalogSettings/components/CredentialsSection.tsx`:
- Around line 130-147: accessTokenHelperTxtNode is always passed into
ThemeAwareFormGroupWrapper causing hasError to be true; move the static HF token
hint out of helperTextNode and into descriptionTextNode (or render it as a
sibling FormHelperText outside the wrapper) and only pass helperTextNode when
there is a real validation error. Update accessTokenFormGroup to use
accessTokenDescriptionTxtNode (or a combined description node that includes the
static hint) for the static message and only supply helperTextNode conditionally
(e.g., when validationError exists), leaving ThemeAwareFormGroupWrapper,
accessTokenHelperTxtNode, accessTokenDescriptionTxtNode and accessTokenFormGroup
as the reference points to change.

In `@clients/ui/frontend/src/app/pages/modelCatalogSettings/constants.tsx`:
- Around line 98-104: EMPTY_STATE_TEXT has inconsistent terminal punctuation
between NO_MODELS_INCLUDED_BODY (ends with a period) and NO_MODELS_EXCLUDED_BODY
(no period); update the NO_MODELS_EXCLUDED_BODY string in the EMPTY_STATE_TEXT
constant so it ends with a period to match NO_MODELS_INCLUDED_BODY and ensure
consistent presentation in PreviewPanel rendering.

---

Outside diff comments:
In
`@clients/ui/frontend/src/__tests__/cypress/cypress/tests/mocked/modelCatalogSettings/modelCatalogSettings.cy.ts`:
- Around line 436-448: The test title in the it(...) block for the case using
mockCatalogSource('hf-google') is stale — it says "should show \"Connected\"
status for available sources" but the assertion uses
row.shouldHaveValidationStatus('Ready') and checks for
'source-status-connected-hf-google'; update the test description to match the
assertion (e.g., change the it(...) string to "should show 'Ready' status for
available sources" or equivalent) so the test name reflects
modelCatalogSettings.getRow('HuggingFace Google') expectations and keeps
greppability accurate.

In
`@clients/ui/frontend/src/app/pages/mcpCatalog/screens/McpCatalogGalleryView.tsx`:
- Around line 54-62: The server query currently uses selectedSourceLabel which
may be undefined on first render; update the call to
useMcpServersBySourceLabelWithAPI so it prefers singleCategoryLabel when present
(e.g., sourceLabel: singleCategoryLabel || selectedSourceLabel) so
single-category mode always queries the correct category; make the same change
for the other occurrence around the use of useMcpServersBySourceLabelWithAPI
later in the file (lines with the second call).

In
`@clients/ui/frontend/src/app/pages/modelCatalog/screens/ModelCatalogGalleryView.tsx`:
- Around line 108-119: When calling useCatalogModelsBySources in
ModelCatalogGalleryView, ensure the hook receives the single-category label when
singleCategoryLabel is set so the initial request doesn't fetch all models;
replace the current source arg logic with one that prefers singleCategoryLabel
(e.g., use singleCategoryLabel ? singleCategoryLabel : (selectedSourceLabel ===
CategoryName.allModels ? undefined : selectedSourceLabel)). Apply the same
change to the other useCatalogModelsBySources call later in the file (the one
around the second occurrence).

In
`@clients/ui/frontend/src/app/pages/modelCatalogSettings/components/ModelVisibilitySection.tsx`:
- Around line 66-132: The two helper nodes allowedModelsHelperTxtNode and
excludedModelsHelperTxtNode are being passed into
ThemeAwareFormGroupWrapper.helperTextNode which forces pf-m-error styling;
delete those helper node variables, and instead merge their informational copy
(getIncludedModelsFieldHelperText / getExcludedModelsFieldHelperText) into the
corresponding descriptionTextNode (allowedModelsDescriptionTxtNode /
excludedModelsDescriptionTxtNode) so the informational text is rendered as
description, not an error. Update the two ThemeAwareFormGroupWrapper usages to
remove the helperTextNode prop entirely and supply the combined
descriptionTextNode (retain existing description generators like
getExcludedModelsHelp(organization) and append the helper text inside the same
FormHelperText/HelperText/HelperTextItem node). Ensure no other code references
allowedModelsHelperTxtNode or excludedModelsHelperTxtNode after the change.

---

Nitpick comments:
In `@clients/ui/bff/go.mod`:
- Around line 15-17: Update the Kubernetes module versions so they are
consistent: change the k8s.io/client-go module version from v0.35.3 to v0.35.4
to match k8s.io/api and k8s.io/apimachinery (both v0.35.4); after updating the
k8s.io/client-go version in go.mod run go mod tidy (or the equivalent dependency
refresh) to ensure the dependency tree is reconciled.

In `@clients/ui/bff/internal/integrations/kubernetes/k8mocks/base_testenv.go`:
- Line 350: The error message currently hardcodes "default-catalog-sources"
instead of referencing the shared constant; update the failing return in the
configmap creation path to use k8s.CatalogSourceDefaultConfigMapName (the same
constant used on the creation line) so the error text stays in sync with the
source-of-truth; locate the configmap creation function/return that currently
does `fmt.Errorf("failed to create default-catalog-sources configmap: %w", err)`
and replace the hardcoded name with the constant.

In
`@clients/ui/frontend/src/app/pages/modelCatalogSettings/components/CatalogSourceStatus.tsx`:
- Around line 54-59: In the CatalogSourceStatus.tsx branch for
CatalogSourceStatusEnum.AVAILABLE, update the stale data-testid value from
`source-status-connected-${catalogSourceConfig.id}` to
`source-status-ready-${catalogSourceConfig.id}` to match the label text change,
and then update the Cypress selector(s) in the corresponding model catalog
settings test that currently reference the old `source-status-connected-…` id to
the new `source-status-ready-…` id so tests remain in sync.

In `@clients/ui/frontend/src/app/pages/modelCatalogSettings/constants.tsx`:
- Around line 134-140: Rename the misleading constants
getIncludedModelsFieldHelperText and getExcludedModelsFieldHelperText to
non-getter names (e.g., includedModelsFieldHelperText and
excludedModelsFieldHelperText) and update all usages to the new identifiers
(including JSX embeddings like where ModelVisibilitySection references them) so
callers don't accidentally try to invoke them as functions; keep the string
values unchanged and ensure any exports/imports are updated accordingly.

In
`@clients/ui/frontend/src/app/pages/modelRegistry/screens/ModelTransferJobs/ModelTransferJobTableRow.tsx`:
- Around line 164-179: Extract the inline style on the PF Button in
ModelTransferJobTableRow (the Button rendered when job.status ===
ModelTransferJobStatus.FAILED) into a dedicated CSS class (e.g.,
mr-transfer-job-error-link), remove the style prop and apply the class via
className on the Button, add the CSS rule (text-decoration: underline dotted;
text-underline-offset: var(--pf-t--global--spacer--xs);) to the component
stylesheet or CSS module, and ensure the stylesheet is imported (and class
referenced) where the Button and Truncate are used so the visual rule is
centralized and not repeated per row.

In `@jobs/async-upload/tests/integration/test_integration_async_upload.py`:
- Line 214: The test duplicates the literal "default" namespace in multiple
kubectl invocations (apply, delete, describe, wait/log and cleanup) in
test_integration_async_upload.py; introduce a single constant (e.g., NAMESPACE =
"default") at the top of the test module or the test function and replace all
occurrences of the string literal in the kubectl command lists (the arrays used
for ["kubectl", "apply", "-k", ".", "-n", "default"], ["kubectl", "delete", ...
"-n", "default"], and the describe/wait/cleanup command arrays) with that
constant so future namespace changes only require one edit.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 81faf559-86e5-46d0-ad30-718d5a33ebe7

📥 Commits

Reviewing files that changed from the base of the PR and between 8a440df and c60c036.

⛔ Files ignored due to path filters (1)
  • clients/ui/bff/go.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (36)
  • .github/workflows/go-mod-tidy-diff-check.yml
  • catalog/clients/python/patches/include-readonly-timestamps.patch
  • clients/ui/bff/go.mod
  • clients/ui/bff/internal/integrations/kubernetes/client.go
  • clients/ui/bff/internal/integrations/kubernetes/k8mocks/base_testenv.go
  • clients/ui/frontend/src/__mocks__/mockCatalogSourceList.ts
  • clients/ui/frontend/src/__tests__/cypress/cypress/pages/modelCatalog.ts
  • clients/ui/frontend/src/__tests__/cypress/cypress/pages/modelCatalogSettings.ts
  • clients/ui/frontend/src/__tests__/cypress/cypress/pages/modelRegistryView/modelTransferJobs.ts
  • clients/ui/frontend/src/__tests__/cypress/cypress/tests/mocked/modelCatalog/modelCatalog.cy.ts
  • clients/ui/frontend/src/__tests__/cypress/cypress/tests/mocked/modelCatalog/modelCatalogDetails.cy.ts
  • clients/ui/frontend/src/__tests__/cypress/cypress/tests/mocked/modelCatalogSettings/modelCatalogSettings.cy.ts
  • clients/ui/frontend/src/__tests__/cypress/cypress/tests/mocked/modelRegistry/modelTransferJobs.cy.ts
  • clients/ui/frontend/src/app/pages/mcpCatalog/screens/McpCatalog.tsx
  • clients/ui/frontend/src/app/pages/mcpCatalog/screens/McpCatalogGalleryView.tsx
  • clients/ui/frontend/src/app/pages/mcpCatalog/screens/McpCatalogSourceLabelBlocks.tsx
  • clients/ui/frontend/src/app/pages/modelCatalog/screens/ModelCatalog.tsx
  • clients/ui/frontend/src/app/pages/modelCatalog/screens/ModelCatalogGalleryView.tsx
  • clients/ui/frontend/src/app/pages/modelCatalog/screens/ModelCatalogSourceLabelBlocks.tsx
  • clients/ui/frontend/src/app/pages/modelCatalog/screens/ModelDetailsView.tsx
  • clients/ui/frontend/src/app/pages/modelCatalog/utils/__tests__/modelCatalogUtils.spec.ts
  • clients/ui/frontend/src/app/pages/modelCatalog/utils/modelCatalogUtils.ts
  • clients/ui/frontend/src/app/pages/modelCatalogSettings/components/CatalogSourceStatus.tsx
  • clients/ui/frontend/src/app/pages/modelCatalogSettings/components/CatalogSourceStatusErrorModal.tsx
  • clients/ui/frontend/src/app/pages/modelCatalogSettings/components/CredentialsSection.tsx
  • clients/ui/frontend/src/app/pages/modelCatalogSettings/components/ManageSourceForm.tsx
  • clients/ui/frontend/src/app/pages/modelCatalogSettings/components/ManageSourceFormFooter.tsx
  • clients/ui/frontend/src/app/pages/modelCatalogSettings/components/ModelVisibilitySection.tsx
  • clients/ui/frontend/src/app/pages/modelCatalogSettings/components/PreviewPanel.tsx
  • clients/ui/frontend/src/app/pages/modelCatalogSettings/components/YamlSection.tsx
  • clients/ui/frontend/src/app/pages/modelCatalogSettings/components/__tests__/PreviewPanel.spec.tsx
  • clients/ui/frontend/src/app/pages/modelCatalogSettings/constants.tsx
  • clients/ui/frontend/src/app/pages/modelCatalogSettings/screens/CatalogSourceConfigsTableColumns.tsx
  • clients/ui/frontend/src/app/pages/modelRegistry/screens/ModelTransferJobs/ModelTransferJobTableRow.tsx
  • jobs/async-upload/tests/integration/test_integration_async_upload.py
  • manifests/kustomize/options/catalog/overlays/odh/kustomization.yaml

Comment on lines +4 to +11
* OpenAPI `readOnly` fields are excluded.
* OpenAPI `readOnly` fields are excluded.
"""
- excluded_fields: Set[str] = set([
- "create_time_since_epoch",
- "last_update_time_since_epoch",
- ])
+ excluded_fields: Set[str] = set([])
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 1. Find all call sites of CatalogModel.to_dict / model serialization into requests.
rg -nP -C3 '\bCatalogModel\b' --type=py
rg -nP -C2 'obj\.to_dict\(\)|\.to_dict\(\)' catalog/clients/python --type=py

# 2. Check whether other models in the generated client still exclude these fields.
rg -nP -C2 '"(create_time_since_epoch|last_update_time_since_epoch)"' catalog/clients/python --type=py

# 3. Look at the OpenAPI spec to confirm readOnly status.
fd -t f -e yaml -e yml -e json . catalog | xargs rg -nP -l 'create_time_since_epoch|last_update_time_since_epoch' 2>/dev/null

Repository: opendatahub-io/model-registry

Length of output: 50387


🏁 Script executed:

# Check the actual CatalogModel.to_dict implementation (after patch applied)
head -150 catalog/clients/python/src/catalog_openapi/models/catalog_model.py

Repository: opendatahub-io/model-registry

Length of output: 6657


🏁 Script executed:

# Check BaseResource.to_dict to see if it excludes create_time_since_epoch and last_update_time_since_epoch
cat -n catalog/clients/python/src/catalog_openapi/models/base_resource.py | head -100

Repository: opendatahub-io/model-registry

Length of output: 5245


🏁 Script executed:

# Search for excluded_fields in BaseResource specifically
rg -A10 "excluded_fields" catalog/clients/python/src/catalog_openapi/models/base_resource.py

Repository: opendatahub-io/model-registry

Length of output: 845


🏁 Script executed:

# Look for the OpenAPI spec file(s) with timestamp definitions
fd -t f \( -name "*.yaml" -o -name "*.yml" -o -name "*.json" \) | head -20

Repository: opendatahub-io/model-registry

Length of output: 303


🏁 Script executed:

# Find OpenAPI spec files
find catalog -type f \( -name "*.yaml" -o -name "*.yml" -o -name "*.json" \) | grep -i -E "(openapi|spec|swagger)" | head -10

Repository: opendatahub-io/model-registry

Length of output: 139


🏁 Script executed:

# Search for OpenAPI spec with timestamp fields
rg -l "create_time_since_epoch|createTimeSinceEpoch" catalog --type yaml --type json

Repository: opendatahub-io/model-registry

Length of output: 441


🏁 Script executed:

# Check class hierarchy - does CatalogModel inherit from BaseResource?
rg -B5 "class CatalogModel" catalog/clients/python/src/catalog_openapi/models/catalog_model.py

Repository: opendatahub-io/model-registry

Length of output: 205


🏁 Script executed:

# Search for API client usage: how is CatalogModel.to_dict() used in request bodies?
rg -B3 -A3 "\.to_dict\(\)" catalog/clients/python/src/catalog_openapi/api_client.py | head -50

Repository: opendatahub-io/model-registry

Length of output: 334


🏁 Script executed:

# Find the main OpenAPI spec definition
find catalog -type f -name "*.yaml" -o -name "*.yml" | xargs rg -l "createTimeSinceEpoch|create_time_since_epoch" 2>/dev/null | head -5

Repository: opendatahub-io/model-registry

Length of output: 441


🏁 Script executed:

# Check the openapitools.json configuration
cat catalog/openapitools.json | head -50

Repository: opendatahub-io/model-registry

Length of output: 226


🏁 Script executed:

# Search for readOnly in OpenAPI schema definition
rg "readOnly|read_only" catalog --type yaml --type json -B2 -A2 | head -100

Repository: opendatahub-io/model-registry

Length of output: 55


🏁 Script executed:

# Look for the actual OpenAPI spec in common locations
find catalog -type f \( -name "*.proto" -o -name "*.proto.yaml" \) | head -10

Repository: opendatahub-io/model-registry

Length of output: 55


🏁 Script executed:

# Search for go server files that might define the schema
find catalog -path "*/server/*" -name "*.go" | head -5

Repository: opendatahub-io/model-registry

Length of output: 370


🏁 Script executed:

# Check if there's a go service definition
find catalog -name "*service*.go" | head -5

Repository: opendatahub-io/model-registry

Length of output: 383


🏁 Script executed:

# Look at the patch file directly to understand context
cat -n catalog/clients/python/patches/include-readonly-timestamps.patch

Repository: opendatahub-io/model-registry

Length of output: 698


🏁 Script executed:

# Check the Go OpenAPI API definition for CatalogModel/Model schema
grep -A30 "type.*Model" catalog/internal/server/openapi/api_model_catalog_service.go | head -60

Repository: opendatahub-io/model-registry

Length of output: 1266


🏁 Script executed:

# Search for the protobuf or swagger definition
find catalog -name "*.proto" | head -5

Repository: opendatahub-io/model-registry

Length of output: 55


🏁 Script executed:

# Look for generated swagger/OpenAPI spec
find catalog -type f -name "swagger.json" -o -name "openapi.json" -o -name "openapi.yaml"

Repository: opendatahub-io/model-registry

Length of output: 55


🏁 Script executed:

# Check git log for context on this patch
git log --oneline --all --grep="readOnly\|readonly\|timestamp" 2>/dev/null | head -10

Repository: opendatahub-io/model-registry

Length of output: 685


🏁 Script executed:

# Search for server validation logic that might reject readOnly fields
rg "readOnly|readonly" catalog/internal --type go -B2 -A2 | head -50

Repository: opendatahub-io/model-registry

Length of output: 4698


🏁 Script executed:

# Check the actual patch hunk context - what's shown in lines 4-6 before the change
git show HEAD:catalog/clients/python/src/catalog_openapi/models/catalog_model.py | sed -n '75,90p'

Repository: opendatahub-io/model-registry

Length of output: 768


🏁 Script executed:

# Verify CatalogModel and BaseResource are separate classes (not inheritance)
rg -A2 "class BaseResource|class CatalogModel" catalog/clients/python/src/catalog_openapi/models/*.py | grep -E "(class|inherit)"

Repository: opendatahub-io/model-registry

Length of output: 706


🏁 Script executed:

# Look for API request body generation - does it call to_dict on CatalogModel?
rg "request_body|body.*to_dict|to_dict.*body" catalog/clients/python/src/catalog_openapi --type py -B2 -A2 | head -30

Repository: opendatahub-io/model-registry

Length of output: 1979


🏁 Script executed:

# Check if there's any validation logic in Go service that rejects readOnly fields
rg "create_time_since_epoch|createTimeSinceEpoch" catalog/internal --type go -B5 -A5 | head -60

Repository: opendatahub-io/model-registry

Length of output: 5509


🏁 Script executed:

# Look at the api_client code where body is prepared for requests
cat -n catalog/clients/python/src/catalog_openapi/api_client.py | sed -n '200,250p'

Repository: opendatahub-io/model-registry

Length of output: 1888


🏁 Script executed:

# Search for any request validation or field stripping in the Go backend
rg "readOnly|read.only" catalog/internal/server --type go -B3 -A3 | head -50

Repository: opendatahub-io/model-registry

Length of output: 4772


CatalogModel and BaseResource now serialize timestamps differently, and BaseResource docstring is duplicated.

The patch intentionally includes create_time_since_epoch and last_update_time_since_epoch in CatalogModel's serialization for e2e test coverage (docstring explains this at lines 82–86). However, BaseResource's to_dict() still excludes both fields. Since CatalogModel and BaseResource are independent Pydantic models (not parent-child), this behavioral divergence is not an inheritance issue, but it creates type-dependent serialization that can surprise users: the same timestamp fields serialize or exclude depending on which model type holds them.

Additionally, BaseResource's docstring at lines 68–69 duplicates "OpenAPI readOnly fields are excluded."—remove one instance.

If the goal is exposing timestamps on reads while excluding them on writes, consider whether CatalogModel and BaseResource should have consistent behavior, or document clearly why they differ.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@catalog/clients/python/patches/include-readonly-timestamps.patch` around
lines 4 - 11, Remove the duplicated docstring line in BaseResource and make
BaseResource.to_dict()'s excluded_fields consistent with CatalogModel by
ensuring create_time_since_epoch and last_update_time_since_epoch are not in the
excluded_fields set (so timestamps are serialized on reads); update the
excluded_fields variable in BaseResource.to_dict() accordingly and keep only one
instance of the "OpenAPI `readOnly` fields are excluded." sentence in the
docstring.

Comment on lines +550 to +558
it('should hide category toggle when only one category is active', () => {
initIntercepts({
sources: [mockCatalogSource({ labels: ['Provider one'] })],
});
modelCatalog.visit();

cy.findByTestId('label-Provider one').should('not.exist');
cy.findByTestId('all').should('not.exist');
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Gate the negative assertions on a loaded catalog.

These not.exist checks can pass before the selector renders. Wait for a positive loaded signal first so the test actually verifies the toggle is hidden after data loads.

Proposed fix
     });
     modelCatalog.visit();
+    modelCatalog.findModelCatalogCards().should('have.length.at.least', 1);
 
     cy.findByTestId('label-Provider one').should('not.exist');
     cy.findByTestId('all').should('not.exist');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('should hide category toggle when only one category is active', () => {
initIntercepts({
sources: [mockCatalogSource({ labels: ['Provider one'] })],
});
modelCatalog.visit();
cy.findByTestId('label-Provider one').should('not.exist');
cy.findByTestId('all').should('not.exist');
});
it('should hide category toggle when only one category is active', () => {
initIntercepts({
sources: [mockCatalogSource({ labels: ['Provider one'] })],
});
modelCatalog.visit();
modelCatalog.findModelCatalogCards().should('have.length.at.least', 1);
cy.findByTestId('label-Provider one').should('not.exist');
cy.findByTestId('all').should('not.exist');
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@clients/ui/frontend/src/__tests__/cypress/cypress/tests/mocked/modelCatalog/modelCatalog.cy.ts`
around lines 550 - 558, The negative assertions run before the catalog finishes
rendering; after calling initIntercepts(...) and modelCatalog.visit(), wait for
a positive "catalog loaded" signal before asserting absence of the toggles —
e.g., use an existing helper or element (for example call
modelCatalog.waitForLoad() if available or assert
cy.findByTestId('model-catalog-list').should('exist')) and only then check
cy.findByTestId('label-Provider one').should('not.exist') and
cy.findByTestId('all').should('not.exist').

Comment on lines +572 to +589
it('should show retry button for failed jobs and open retry modal on click', () => {
interceptWithSingleFailedJob('job-retry-test', 'Connection error');

cy.intercept(
'PUT',
`**/api/${MODEL_REGISTRY_API_VERSION}/model_registry/${modelRegistryName}/model_transfer_jobs/job-retry-test*`,
{ statusCode: 200, body: {} },
).as('retryJob');

modelTransferJobsPage.visit(modelRegistryName);

const row = modelTransferJobsPage.getRow('job-retry-test');
row.findRetryButton().should('be.visible').click();

modelTransferJobsPage.findRetryModal().should('be.visible');
modelTransferJobsPage.findRetryModal().contains('Retry model transfer job?').should('exist');
modelTransferJobsPage.findRetryModalSubmitButton().should('be.visible');
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Retry test does not assert the PUT request is fired.

@retryJob is registered on line 579 but never cy.wait('@retryJob')-ed. The test only opens the modal and checks the submit button is visible — it never clicks it, so the intercept is dead weight and the actual retry path is untested. Either drop the intercept or click the submit button and assert the request payload/status.

🧪 Suggested extension
     modelTransferJobsPage.findRetryModal().should('be.visible');
     modelTransferJobsPage.findRetryModal().contains('Retry model transfer job?').should('exist');
-    modelTransferJobsPage.findRetryModalSubmitButton().should('be.visible');
+    modelTransferJobsPage.findRetryModalSubmitButton().should('be.visible').click();
+    cy.wait('@retryJob').its('response.statusCode').should('eq', 200);
+    modelTransferJobsPage.findRetryModal().should('not.exist');
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@clients/ui/frontend/src/__tests__/cypress/cypress/tests/mocked/modelRegistry/modelTransferJobs.cy.ts`
around lines 572 - 589, The test registers an intercept alias '@retryJob' but
never triggers or asserts it; update the test in the it('should show retry
button...') block to actually submit the retry and wait for the PUT request:
after opening the retry modal via modelTransferJobsPage.findRetryModal() and
verifying the submit button
(modelTransferJobsPage.findRetryModalSubmitButton()), click that submit button,
then cy.wait('@retryJob') and assert the request/response (status or request
body) as needed; alternatively remove the cy.intercept(...) alias if you choose
not to exercise the retry path—update the code around
interceptWithSingleFailedJob, the cy.intercept(...) alias '@retryJob', and
modelTransferJobsPage.findRetryModalSubmitButton() usage accordingly.

Comment on lines 168 to +175
<DescriptionListTerm>License</DescriptionListTerm>
<ExternalLink
text="Agreement"
to={model.licenseLink || ''}
testId="model-license-link"
/>
<DescriptionListDescription>
<ExternalLink
text="Agreement"
to={model.licenseLink || ''}
testId="model-license-link"
/>
</DescriptionListDescription>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Broken "Agreement" link when licenseLink is missing.

to={model.licenseLink || ''} still renders a clickable "Agreement" button even when no license URL exists; clicking invokes window.open(''), which opens a blank tab — a confusing UX regression and an accessibility issue (an actionable control that does nothing meaningful). Mirror the pattern already used in McpServerDetailsView.tsx (lines 160-171): render the link only when licenseLink is truthy, otherwise show a textual fallback.

🔧 Proposed fix
                 <DescriptionListGroup>
                   <DescriptionListTerm>License</DescriptionListTerm>
                   <DescriptionListDescription>
-                    <ExternalLink
-                      text="Agreement"
-                      to={model.licenseLink || ''}
-                      testId="model-license-link"
-                    />
+                    {model.licenseLink ? (
+                      <ExternalLink
+                        text="Agreement"
+                        to={model.licenseLink}
+                        testId="model-license-link"
+                      />
+                    ) : (
+                      <span data-testid="model-license">N/A</span>
+                    )}
                   </DescriptionListDescription>
                 </DescriptionListGroup>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<DescriptionListTerm>License</DescriptionListTerm>
<ExternalLink
text="Agreement"
to={model.licenseLink || ''}
testId="model-license-link"
/>
<DescriptionListDescription>
<ExternalLink
text="Agreement"
to={model.licenseLink || ''}
testId="model-license-link"
/>
</DescriptionListDescription>
<DescriptionListTerm>License</DescriptionListTerm>
<DescriptionListDescription>
{model.licenseLink ? (
<ExternalLink
text="Agreement"
to={model.licenseLink}
testId="model-license-link"
/>
) : (
<span data-testid="model-license">N/A</span>
)}
</DescriptionListDescription>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/ui/frontend/src/app/pages/modelCatalog/screens/ModelDetailsView.tsx`
around lines 168 - 175, ModelDetailsView renders an actionable ExternalLink for
the license even when model.licenseLink is falsy, causing window.open('') and a
blank tab; update the JSX in the ModelDetailsView component to conditionally
render the ExternalLink only when model.licenseLink is truthy and otherwise
render a plain textual fallback (as done in McpServerDetailsView), using the
same testId "model-license-link" for the link case and ensuring no clickable
element is produced when licenseLink is missing.

Comment on lines +753 to +766
export const getActiveSourceLabels = (
catalogSources: CatalogSourceList | null,
catalogLabels: CatalogLabelList | null,
): string[] => {
const enabledSources = filterEnabledCatalogSources(catalogSources);
const uniqueLabels = getUniqueSourceLabels(enabledSources);
const orderedLabels = orderLabelsByPriority(uniqueLabels, catalogLabels);

if (hasSourcesWithoutLabels(enabledSources)) {
return [...orderedLabels, SourceLabel.other];
}

return orderedLabels;
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Treat omitted enabled as active consistently.

filterEnabledCatalogSources keeps sources unless enabled === false, but getUniqueSourceLabels later drops sources where enabled is undefined. Since enabled is optional, available labeled sources can disappear from activeCategories, causing false empty states or missed single-category selection.

Proposed fix
-    if (source.enabled && isSourceStatusWithModels(source.status) && source.labels.length > 0) {
+    if (source.enabled !== false && isSourceStatusWithModels(source.status) && source.labels.length > 0) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/ui/frontend/src/app/pages/modelCatalog/utils/modelCatalogUtils.ts`
around lines 753 - 766, getActiveSourceLabels is losing sources because
getUniqueSourceLabels treats sources with enabled === undefined as inactive
while filterEnabledCatalogSources already treats only enabled === false as
disabled; update getUniqueSourceLabels so it treats any source with enabled !==
false as active (i.e., consider undefined as enabled) when collecting labels,
ensuring behavior matches filterEnabledCatalogSources and preserving labeled
sources for hasSourcesWithoutLabels and ordering via orderLabelsByPriority.

Comment on lines +130 to +147
const accessTokenHelperTxtNode = (
<FormHelperText>
<HelperText>
<HelperTextItem>{HELPER_TEXT.ACCESS_TOKEN}</HelperTextItem>
</HelperText>
</FormHelperText>
);

const accessTokenFormGroup = (
<>
<FormGroup label={FORM_LABELS.ACCESS_TOKEN} fieldId="access-token">
<FormHelperText>
<HelperText>
<HelperTextItem>{HELP_TEXT.ACCESS_TOKEN}</HelperTextItem>
</HelperText>
</FormHelperText>
<FormFieldset component={accessTokenInput} field="Access token" />
</FormGroup>
<ThemeAwareFormGroupWrapper
label={FORM_LABELS.ACCESS_TOKEN}
fieldId="access-token"
descriptionTextNode={accessTokenDescriptionTxtNode}
helperTextNode={accessTokenHelperTxtNode}
>
{accessTokenInput}
</ThemeAwareFormGroupWrapper>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Access-token field will be stuck in error styling.

accessTokenHelperTxtNode is defined unconditionally (lines 130–136) and passed as helperTextNode to the wrapper at line 144. Per ThemeAwareFormGroupWrapper, hasError = !!helperTextNode — so the access-token FormGroup will always receive pf-m-error regardless of validation state, mis-communicating a failure to the user and conflicting with the separate validationError Alert below.

Use descriptionTextNode (or a combined description node) for the static HF token hint, and only populate helperTextNode on an actual error condition.

🔧 Suggested fix
-  const accessTokenHelperTxtNode = (
-    <FormHelperText>
-      <HelperText>
-        <HelperTextItem>{HELPER_TEXT.ACCESS_TOKEN}</HelperTextItem>
-      </HelperText>
-    </FormHelperText>
-  );
-
   const accessTokenFormGroup = (
     <>
       <ThemeAwareFormGroupWrapper
         label={FORM_LABELS.ACCESS_TOKEN}
         fieldId="access-token"
         descriptionTextNode={accessTokenDescriptionTxtNode}
-        helperTextNode={accessTokenHelperTxtNode}
       >
         {accessTokenInput}
       </ThemeAwareFormGroupWrapper>
+      <FormHelperText>
+        <HelperText>
+          <HelperTextItem>{HELPER_TEXT.ACCESS_TOKEN}</HelperTextItem>
+        </HelperText>
+      </FormHelperText>

Note: the organization block (lines 90–107) already follows this pattern correctly — ORGANIZATION_SLUG is rendered as a sibling FormHelperText outside the wrapper, and organizationHelperTxtNode is conditional.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@clients/ui/frontend/src/app/pages/modelCatalogSettings/components/CredentialsSection.tsx`
around lines 130 - 147, accessTokenHelperTxtNode is always passed into
ThemeAwareFormGroupWrapper causing hasError to be true; move the static HF token
hint out of helperTextNode and into descriptionTextNode (or render it as a
sibling FormHelperText outside the wrapper) and only pass helperTextNode when
there is a real validation error. Update accessTokenFormGroup to use
accessTokenDescriptionTxtNode (or a combined description node that includes the
static hint) for the static message and only supply helperTextNode conditionally
(e.g., when validationError exists), leaving ThemeAwareFormGroupWrapper,
accessTokenHelperTxtNode, accessTokenDescriptionTxtNode and accessTokenFormGroup
as the reference points to change.

Comment on lines +98 to +104
export const EMPTY_STATE_TEXT = {
NO_MODELS_INCLUDED: 'No models included',
NO_MODELS_INCLUDED_BODY:
'No models from this source are visible in the model catalog. To include models, edit the model visibility settings of this source.',
NO_MODELS_EXCLUDED: 'No models excluded',
NO_MODELS_EXCLUDED_BODY: 'No models from this source are excluded by this filter',
} as const;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Inconsistent terminal punctuation in EMPTY_STATE_TEXT.

NO_MODELS_INCLUDED_BODY ends with a period; NO_MODELS_EXCLUDED_BODY does not. Minor, but since these are rendered side-by-side via PreviewPanel.tsx (lines 189–191), the inconsistency will be visible to users.

-  NO_MODELS_EXCLUDED_BODY: 'No models from this source are excluded by this filter',
+  NO_MODELS_EXCLUDED_BODY: 'No models from this source are excluded by this filter.',
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const EMPTY_STATE_TEXT = {
NO_MODELS_INCLUDED: 'No models included',
NO_MODELS_INCLUDED_BODY:
'No models from this source are visible in the model catalog. To include models, edit the model visibility settings of this source.',
NO_MODELS_EXCLUDED: 'No models excluded',
NO_MODELS_EXCLUDED_BODY: 'No models from this source are excluded by this filter',
} as const;
export const EMPTY_STATE_TEXT = {
NO_MODELS_INCLUDED: 'No models included',
NO_MODELS_INCLUDED_BODY:
'No models from this source are visible in the model catalog. To include models, edit the model visibility settings of this source.',
NO_MODELS_EXCLUDED: 'No models excluded',
NO_MODELS_EXCLUDED_BODY: 'No models from this source are excluded by this filter.',
} as const;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/ui/frontend/src/app/pages/modelCatalogSettings/constants.tsx` around
lines 98 - 104, EMPTY_STATE_TEXT has inconsistent terminal punctuation between
NO_MODELS_INCLUDED_BODY (ends with a period) and NO_MODELS_EXCLUDED_BODY (no
period); update the NO_MODELS_EXCLUDED_BODY string in the EMPTY_STATE_TEXT
constant so it ends with a period to match NO_MODELS_INCLUDED_BODY and ensure
consistent presentation in PreviewPanel rendering.

@jonburdo
Copy link
Copy Markdown
Member Author

/ok-to-test
/lgtm
/retest

@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented Apr 22, 2026

@jonburdo: you cannot LGTM your own PR.

Details

In response to this:

/ok-to-test
/lgtm
/retest

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@openshift-ci openshift-ci Bot added the lgtm label Apr 22, 2026
@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented Apr 22, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: dbasunag, jonburdo

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-merge-bot openshift-merge-bot Bot merged commit 754a8fb into opendatahub-io:main Apr 22, 2026
36 of 38 checks passed
@jonburdo jonburdo deleted the jonburdo-20260422-sync branch April 22, 2026 20:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants