Skip to content

Add operator lifecycle metadata to installed operators table#16551

Open
perdasilva wants to merge 7 commits into
openshift:mainfrom
perdasilva:upgrade-planner-2.1
Open

Add operator lifecycle metadata to installed operators table#16551
perdasilva wants to merge 7 commits into
openshift:mainfrom
perdasilva:upgrade-planner-2.1

Conversation

@perdasilva
Copy link
Copy Markdown

@perdasilva perdasilva commented Jun 4, 2026

Summary

  • Adds three new columns to the Installed Operators table (behind the OPERATOR_LIFECYCLE_METADATA feature flag): Cluster compatibility (green check / red exclamation), Support phase (neutral badge with phase name), and Support phase ends (end date in "MMM d, YYYY" format)
  • Clicking the support phase badge opens a popover showing all lifecycle phases with their end dates and a SKU disclaimer
  • Fetches lifecycle data from the console backend via /api/olm/lifecycle/ with in-memory caching (5 min TTL)

Analysis / Root cause:
The Installed Operators table did not surface operator lifecycle metadata (cluster compatibility, support phase, and support end dates), making it difficult for cluster administrators to assess operator upgrade readiness and support status at a glance.

Solution description:

  • Added useOperatorLifecycle hook that fetches lifecycle data from /api/olm/lifecycle/{namespace}/{catalog}/{package} with request deduplication and caching
  • Added getClusterCompatibility() and getSupportPhase() utility functions to compute compatibility and support status from lifecycle data
  • Added ClusterCompatibilityStatus, SupportPhaseBadge, SupportPhaseEndDate components for rendering the three new columns
  • Added LifecycleDatesPopover component that displays all lifecycle phases and end dates when clicking the support phase badge
  • Updated i18n keys for new strings

Screenshots / screen recording:

Test setup:
Requires a cluster with the OPERATOR_LIFECYCLE_METADATA feature flag enabled and operators with lifecycle metadata available via the OLM lifecycle API.

Test cases:

  • Verify "Cluster compatibility" column shows green check "Compatible" for compatible operators
  • Verify "Cluster compatibility" column shows red exclamation "Incompatible" for incompatible operators
  • Verify "Support phase" column shows the current phase name in a neutral badge
  • Verify "Support phase ends" column shows the current phase end date in "MMM d, YYYY" format
  • Verify clicking the support phase badge opens a popover with all lifecycle phases and end dates
  • Verify columns show "-" when lifecycle data is unavailable
  • Verify columns are hidden when the feature flag is disabled

Browser conformance:

  • Chrome
  • Firefox
  • Safari (or Epiphany on Linux)

Additional info:
Unit tests added for all new components and utility functions (29 tests passing).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Operator lifecycle status added: cluster compatibility indicators, support phase badges, and phase end dates
    • New API endpoint to fetch operator lifecycle metadata
    • Lifecycle UI gated by a tech-preview feature flag; column visibility adapts when enabled
  • Localization

    • Added English translations for cluster compatibility, support phases, lifecycle dates, and related status text
  • Tests

    • Added unit and UI tests covering lifecycle logic, API routing, and UI components

Per G. da Silva and others added 3 commits June 4, 2026 12:26
Adds lifecycle metadata (support status, cluster compatibility) to the
installed operators table by querying the CatalogSource catalog pod's
ExperimentalListPackageCustomSchemas gRPC endpoint on the
ExperimentalRegistry service.

Backend: New lifecycle handler dials the CatalogSource catalog pod at
{catalogName}.{namespace}.svc:50051 and calls
ExperimentalListPackageCustomSchemas with the x-acknowledge-experimental
metadata header and schema "io.openshift.operators.lifecycles.v1alpha1".
The gRPC call is wrapped in a 10-second timeout to prevent hanging on
unresponsive catalog pods. CatalogSources running older opm versions
that don't implement the endpoint return 503, so the frontend gracefully
shows "-" for those operators. Uses upstream
operator-framework/operator-registry v1.69.0.

Frontend: New useOperatorLifecycle hook fetches lifecycle data from the
backend proxy with client-side caching (5 min success, 30s error TTL)
and request deduplication. Abort errors are excluded from the cache to
prevent a single unmounting component from poisoning shared requests.
The installed operators table gains "Cluster Compatibility" and "Support"
columns that display platform compatibility and lifecycle phase
information from the catalog. Lifecycle data uses the
io.openshift.operators.lifecycles.v1alpha1 FBC schema with startDate/
endDate phase fields and platformCompatibility arrays. Date comparisons
use local-timezone boundaries to avoid off-by-one errors at phase edges.
Feature-gated behind the OPERATOR_LIFECYCLE_METADATA tech preview flag.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…phase end date

- Cluster compatibility: green check "Compatible" / red exclamation "Incompatible"
- Support phase: neutral badge showing phase name from lifecycle data
- Support phase ends: current phase end date in "MMM d, YYYY" format

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Clicking the support phase badge now opens a popover showing all
lifecycle phases with their end dates and a SKU disclaimer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 4, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 4227e547-02aa-451c-b23a-a7097dddee50

📥 Commits

Reviewing files that changed from the base of the PR and between b51aebb and ae6675b.

📒 Files selected for processing (1)
  • frontend/packages/operator-lifecycle-manager/src/components/operator-lifecycle-status.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/packages/operator-lifecycle-manager/src/components/operator-lifecycle-status.tsx

Walkthrough

Adds backend lifecycle endpoint and frontend tech-preview feature to show operator cluster compatibility and support-phase in the installed-operators table, implements a cached lifecycle-fetch hook, UI components, tests, and Go dependency bumps.

Changes

Operator Lifecycle Metadata Feature

Layer / File(s) Summary
Feature flag registration and localization
src/const.ts, src/features.ts, console-extensions.json, package.json, locales/en/olm.json
Registers OPERATOR_LIFECYCLE_METADATA feature flag using techPreview; exposes features module; adds console flag handler and UI translation keys for compatibility, support phase, and lifecycle information.
Lifecycle data models and status computation
src/components/operator-lifecycle-status.tsx
Defines LifecycleData types, getClusterCompatibility to match cluster minor versions against OpenShift compatibility matrix, and getSupportPhase to determine current phase or self-support status based on date ranges.
Lifecycle status UI components
src/components/operator-lifecycle-status.tsx
Implements ClusterCompatibilityStatus, LifecycleDatesPopover, SupportPhaseBadge, and SupportPhaseEndDate components that render translated labels, icons, badges, and a popover listing phase end dates.
Lifecycle data fetching hook and helpers
src/hooks/useOperatorLifecycle.ts
Implements useOperatorLifecycle hook with in-memory caching, request deduplication, and abort handling; exports helpers to extract catalog/package info from subscriptions and CSVs and to retrieve cluster version.
Operator table UI integration
src/components/clusterserviceversion.tsx
Threads lifecycleEnabled through rows/customData, conditionally adds lifecycle columns and cells, integrates user column-management state, and computes per-row lifecycle values when enabled.
Frontend lifecycle feature tests
src/components/__tests__/operator-lifecycle-status.spec.tsx
Tests getClusterCompatibility and getSupportPhase logic across version matching and date resolution cases; tests component rendering of status labels and badges with mocked i18next.
Backend lifecycle API and tests
pkg/olm/lifecycle.go, pkg/olm/handler.go, pkg/olm/lifecycle_test.go, pkg/olm/handler_test.go
Adds GET endpoint /api/olm/lifecycle/{catalogNamespace}/{catalogName}/{packageName} that validates parameters, fetches lifecycle metadata via catalog gRPC, returns JSON, maps gRPC errors to HTTP statuses, and covers validation/error mappings in unit tests.

Infrastructure and Dependencies

Layer / File(s) Summary
Go module dependency updates
go.mod
Bumps versions for Kubernetes, OpenShift, operator-framework, and many transitive dependencies; adds replace pins for github.com/docker/docker and github.com/docker/cli.
Async cache test improvement
pkg/serverutils/asynccache/asyncccache_test.go
Replaces flaky polling-based verification with deterministic timing assertions within and after the refresh interval.

🎯 4 (Complex) | ⏱️ ~60 minutes


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
No-Sensitive-Data-In-Logs ❌ Error pkg/olm/lifecycle.go logs internal infrastructure: line 61 logs Kubernetes service hostnames, lines 132/135 log uncontrolled gRPC error messages. Remove line 61 service address logging. Replace st.Message() calls with generic error descriptions to prevent exposing implementation details.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (13 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding operator lifecycle metadata columns to the installed operators table, matching the core purpose of the PR.
Description check ✅ Passed The description provides comprehensive coverage of analysis, solution, test setup, and test cases, though some visual evidence sections (screenshots, browser testing) are incomplete.
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.
Stable And Deterministic Test Names ✅ Passed No Ginkgo tests present. PR uses Go testing package and Jest tests only, making this check inapplicable.
Test Structure And Quality ✅ Passed PR contains no Ginkgo tests; check is inapplicable. Tests use standard Go testing (testing.T, t.Run) and Jest patterns. Ginkgo framework is not present in the repository.
Microshift Test Compatibility ✅ Passed No Ginkgo e2e tests were added in this PR. Tests added are Go unit tests using standard testing.T and Jest TypeScript tests, not Ginkgo framework tests. Check is not applicable.
Single Node Openshift (Sno) Test Compatibility ✅ Passed No Ginkgo e2e tests added in this PR. All test additions are unit tests: Jest tests for React components and Go standard library tests for backend handlers. Check does not apply.
Topology-Aware Scheduling Compatibility ✅ Passed PR does not introduce deployment manifests, operator controllers, or pod scheduling constraints. Changes are UI/API enhancements only with no topology-related scheduling assumptions.
Ote Binary Stdout Contract ✅ Passed No OTE Binary Stdout Contract violations detected. New code contains no process-level stdout writes; all logging is via klog in HTTP handlers which defaults to stderr output in klog v2.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed No Ginkgo e2e tests added in this PR. Tests added are standard Go unit tests (using testing.T) and Jest/React tests, which are not subject to IPv6 and disconnected network compatibility requirements.
No-Weak-Crypto ✅ Passed No weak cryptographic algorithms (MD5, SHA1, DES, RC4, etc.), custom crypto implementations, or non-constant-time secret comparisons found in the PR code.
Container-Privileges ✅ Passed PR modifies only frontend/backend application code and dependencies; no Kubernetes manifests, Dockerfiles, or container security configurations present.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@openshift-ci openshift-ci Bot requested review from cajieh and rhamilto June 4, 2026 13:11
@openshift-ci openshift-ci Bot added the component/backend Related to backend label Jun 4, 2026
@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented Jun 4, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: perdasilva
Once this PR has been reviewed and has the lgtm label, please assign spadgett for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found 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-ci openshift-ci Bot added component/olm Related to OLM component/shared Related to console-shared kind/i18n Indicates issue or PR relates to internationalization or has content that needs to be translated labels Jun 4, 2026
@perdasilva
Copy link
Copy Markdown
Author

/hold experimental

@perdasilva perdasilva changed the title NO-ISUE: Upgrade planner 2.1 Add operator lifecycle metadata to installed operators table Jun 4, 2026
@openshift-ci openshift-ci Bot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Jun 4, 2026
Copy link
Copy Markdown
Contributor

@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: 10

🧹 Nitpick comments (4)
pkg/serverutils/asynccache/asyncccache_test.go (1)

51-54: ⚖️ Poor tradeoff

Consider potential flakiness from fixed sleep duration.

The 100ms sleep may be insufficient on heavily loaded or slow systems, potentially causing the test to capture the item before the first refresh completes. However, given that wait.UntilWithContext (used in AsyncCache.Run) fires immediately and doesn't expose a completion signal, this fixed sleep is likely the most practical approach.

Alternative: If flakiness is observed, consider using Eventually with a short timeout:

// wait for the first refresh to complete
require.Eventually(t, func() bool {
    newItem := c.GetItem()
    return newItem != item
}, 1*time.Second, 50*time.Millisecond, "first refresh should complete")
item = c.GetItem()

Though this would change the test logic, as it explicitly waits for a change rather than just ensuring the first refresh has fired.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/serverutils/asynccache/asyncccache_test.go` around lines 51 - 54, The
fixed 100ms sleep in the test may be flaky; update the test near the
AsyncCache.Run usage to wait for the first refresh deterministically by polling
instead of sleeping: replace the time.Sleep(100 * time.Millisecond) + item =
c.GetItem() logic with a short Eventually-style loop that calls c.GetItem()
until it observes a change (or times out), or use require.Eventually with a 1s
timeout and 50ms interval to assert the first refresh completed; reference
AsyncCache.Run / wait.UntilWithContext and c.GetItem to locate where to
implement the polling/Eventually replacement.
frontend/packages/operator-lifecycle-manager/src/components/clusterserviceversion.tsx (2)

812-816: 💤 Low value

useMemo dependency array may be incomplete.

The allHeaders memo depends on allNamespaceActive and lifecycleEnabled, but the header definitions also depend on t (the translation function). If the user changes language at runtime, headers won't re-translate until another dependency changes. This is a minor issue since language rarely changes mid-session.

♻️ Add t to dependencies for completeness
   const allHeaders = useMemo(
     () => (allNamespaceActive ? AllProjectsTableHeader() : SingleProjectTableHeader()),
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-    [allNamespaceActive, lifecycleEnabled],
+    [allNamespaceActive, lifecycleEnabled, t],
   );

Remove the eslint-disable comment if adding t.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/packages/operator-lifecycle-manager/src/components/clusterserviceversion.tsx`
around lines 812 - 816, The memo for allHeaders (created with useMemo) currently
omits the translation function t from its dependency array so header labels
won’t update when language changes; update the dependency array to include t
(i.e., [allNamespaceActive, lifecycleEnabled, t]) and remove the
eslint-disable-next-line react-hooks/exhaustive-deps comment, ensuring
AllProjectsTableHeader and SingleProjectTableHeader will re-run when t changes.

725-728: 💤 Low value

Kebab header lacks stable id for column management.

All other headers now include an id field for column management integration, but the kebab action column header does not. While kebab columns are typically not hideable, the missing id creates inconsistency with the updated Header type definition (line 1609).

♻️ Add id for consistency
 const kebabHeader: Header = {
+  id: 'kebab',
   title: '',
   props: { className: KEBAB_COLUMN_CLASS },
 };

Alternatively, mark the kebab column as excluded from column management by adding additional: false if that pattern is used elsewhere.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/packages/operator-lifecycle-manager/src/components/clusterserviceversion.tsx`
around lines 725 - 728, The kebabHeader constant lacks the stable `id` required
by the updated Header type; update the kebabHeader object (the variable named
kebabHeader in this file) to include a unique `id` string (e.g., "kebab" or
"actions") so it matches other headers and works with column management, or
alternatively add `additional: false` to explicitly exclude it from column
management if that pattern is used elsewhere; ensure the change is applied to
the kebabHeader declaration that currently sets title and props.
frontend/packages/operator-lifecycle-manager/src/components/operator-lifecycle-status.tsx (1)

180-182: 💤 Low value

Inefficient double date conversion.

The code converts endDate to a timestamp with parseLocalEndOfDay(p.endDate), then immediately wraps it in new Date() to format it. This double conversion (string→timestamp→Date) is unnecessary since formatDate accepts a Date object directly.

♻️ Simplify date handling
 <DescriptionListDescription>
-  {formatDate(new Date(parseLocalEndOfDay(p.endDate)))}
+  {formatDate(new Date(p.endDate + 'T23:59:59'))}
 </DescriptionListDescription>

Alternatively, add a helper that returns a Date directly:

const parseLocalEndOfDayAsDate = (dateStr: string): Date => {
  const [y, m, d] = dateStr.split('-').map(Number);
  return new Date(y, m - 1, d, 23, 59, 59, 999);
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/packages/operator-lifecycle-manager/src/components/operator-lifecycle-status.tsx`
around lines 180 - 182, The code does an unnecessary double conversion by
calling formatDate(new Date(parseLocalEndOfDay(p.endDate))); update usage to
pass a Date directly (either change parseLocalEndOfDay to return a Date or add a
new helper like parseLocalEndOfDayAsDate) and call
formatDate(parseLocalEndOfDayAsDate(p.endDate)) in the
DescriptionListDescription render; reference the existing symbols formatDate,
parseLocalEndOfDay (or the new parseLocalEndOfDayAsDate) and p.endDate to locate
and replace the conversion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@frontend/packages/operator-lifecycle-manager/src/components/clusterserviceversion.tsx`:
- Around line 381-388: Destructure the full return from useOperatorLifecycle
(e.g., const [lifecycleData, lifecycleLoading, lifecycleError] =
useOperatorLifecycle(...)) instead of only data, then propagate those states
into the lifecycle UI: when lifecycleLoading is true show a loading spinner/icon
in the lifecycle-related table cells (where compatible and supportPhase are
rendered) and when lifecycleError is present show an error indicator/message
rather than the static '-' placeholder; update any calls or UI logic that
compute compatible and supportPhase (getClusterCompatibility, getSupportPhase)
to use lifecycleData only when not loading/error so you don't compute against
undefined.

In
`@frontend/packages/operator-lifecycle-manager/src/components/operator-lifecycle-status.tsx`:
- Around line 159-161: The formatDate function currently hardcodes the 'en-US'
locale, breaking i18n; update formatDate to use the user's locale instead—either
call toLocaleDateString without a locale (pass undefined) or read the active
locale from react-i18next (useTranslation()/i18n.language) and pass that value;
modify the formatDate implementation (function name: formatDate) to remove the
'en-US' literal and use the browser/default or i18n-provided locale so dates
respect user settings.
- Around line 84-92: The parsing functions parseLocalStartOfDay and
parseLocalEndOfDay currently assume a well-formed "YYYY-MM-DD" string; add
defensive validation: ensure dateStr.split('-') yields exactly 3 parts, convert
each part to Number and assert none are NaN (use Number.isFinite or
!Number.isNaN), validate month is 1–12 and day is 1–31 (optional stricter day
check per month), then construct the Date and verify resulting date.getTime() is
finite; if validation fails return NaN or throw a clear Error so callers can
handle malformed backend dates.

In
`@frontend/packages/operator-lifecycle-manager/src/hooks/useOperatorLifecycle.ts`:
- Around line 7-29: The lifecycleCache Map has no eviction and can grow
unbounded; add a bounded eviction policy (or periodic cleanup) to remove
stale/old entries: define a MAX_CACHE_SIZE constant and an evictOldEntries
function that sorts lifecycleCache entries by entry.timestamp and removes the
oldest until size <= MAX_CACHE_SIZE, and also add a periodic sweep that deletes
entries whose timestamp + TTL (use CACHE_TTL_SUCCESS/CACHE_TTL_ERROR depending
on entry.error) is expired; invoke evictOldEntries (and/or the sweep) after any
insertion into lifecycleCache (where entries are created using buildCacheKey and
LifecycleCacheEntry) and/or schedule it on an interval to prevent memory leaks
while keeping isCacheValid logic intact.
- Around line 31-42: The extractPackageName function currently calls JSON.parse
on the olmProperties string which may be untrusted; before parsing in
extractPackageName(olmProperties) validate that olmProperties is a string and
below a safe length (e.g., a configurable small max bytes) and return undefined
if it exceeds that limit or is not a string, then proceed to JSON.parse inside
the existing try/catch; this prevents expensive or maliciously large payloads
from being parsed while preserving the graceful undefined fallback handled by
the current catch.
- Around line 169-171: The getClusterVersion function currently relies on
optional chaining but should explicitly guard against missing SERVER_FLAGS:
update getClusterVersion to first check that window && typeof window !==
"undefined" and that window.SERVER_FLAGS is truthy (or use a null-check like if
(!window.SERVER_FLAGS) return undefined) before returning
window.SERVER_FLAGS.releaseVersion, ensuring the function safely handles
environments where SERVER_FLAGS is undefined and preserves the string |
undefined return type.

In `@go.mod`:
- Line 13: The go.mod currently pins a pre-release module
"github.com/golang/mock v1.7.0-rc.1"; replace this pre-release with a stable
release or the actively maintained fork. Locate the dependency entry for
github.com/golang/mock v1.7.0-rc.1 and either change it to the latest stable
semver (e.g., v1.6.x or the latest non-rc release) or replace the module path
with the maintained fork go.uber.org/mock at its current stable version; then
run go get / go mod tidy to update the lockfile and verify build/tests.
- Line 21: The go.mod currently pulls golang.org/x/net at a version flagged by
OSV (see golang.org/x/net); update the golang.org/x/net dependency in go.mod to
the patched release that fixes GO-2026-5025 through GO-2026-5030, then run
module resolution (e.g., bump via the Go tool and run tidy) to pin the fixed
version; finally run your full test/build matrix and verify compatibility across
the Kubernetes/operator-framework/api, gRPC and protobuf dependencies (rebuild,
run unit/integration tests and ensure no module version conflicts) and commit
the updated go.mod/go.sum.

In `@pkg/olm/lifecycle.go`:
- Around line 66-67: Replace the responses that include raw backend errors in
serverutils.SendResponse calls (e.g., the ApiError constructed with
fmt.Sprintf("failed to create gRPC client: %v", err) and the similar occurrence
around line 136) with a stable, generic client-facing message (for example
"internal server error" or "failed to create client") and do not include err
details in the ApiError payload; instead, log the full err server-side using the
existing logger or fmt/remote logger (referencing the same scope where the error
occurs) so internals stay out of the API contract.
- Line 69: The defer conn.Close() and the w.Write(jsonBytes) calls currently
ignore returned errors; change defer conn.Close() to capture and log its error
(e.g., defer func(){ if err := conn.Close(); err != nil {
logger.Errorf("conn.Close failed: %v", err) }}()) and handle the result of
w.Write(jsonBytes) by checking the returned (n, err) and logging or returning
the error from the function (e.g., if _, err := w.Write(jsonBytes); err != nil {
logger.Errorf("write response failed: %v", err); return }) so transport/close
failures from conn.Close() and w.Write(...) are observable. Ensure you use the
local logger/context used in this file for consistency and preserve existing
control flow in the function where conn.Close() and w.Write(jsonBytes) are used.

---

Nitpick comments:
In
`@frontend/packages/operator-lifecycle-manager/src/components/clusterserviceversion.tsx`:
- Around line 812-816: The memo for allHeaders (created with useMemo) currently
omits the translation function t from its dependency array so header labels
won’t update when language changes; update the dependency array to include t
(i.e., [allNamespaceActive, lifecycleEnabled, t]) and remove the
eslint-disable-next-line react-hooks/exhaustive-deps comment, ensuring
AllProjectsTableHeader and SingleProjectTableHeader will re-run when t changes.
- Around line 725-728: The kebabHeader constant lacks the stable `id` required
by the updated Header type; update the kebabHeader object (the variable named
kebabHeader in this file) to include a unique `id` string (e.g., "kebab" or
"actions") so it matches other headers and works with column management, or
alternatively add `additional: false` to explicitly exclude it from column
management if that pattern is used elsewhere; ensure the change is applied to
the kebabHeader declaration that currently sets title and props.

In
`@frontend/packages/operator-lifecycle-manager/src/components/operator-lifecycle-status.tsx`:
- Around line 180-182: The code does an unnecessary double conversion by calling
formatDate(new Date(parseLocalEndOfDay(p.endDate))); update usage to pass a Date
directly (either change parseLocalEndOfDay to return a Date or add a new helper
like parseLocalEndOfDayAsDate) and call
formatDate(parseLocalEndOfDayAsDate(p.endDate)) in the
DescriptionListDescription render; reference the existing symbols formatDate,
parseLocalEndOfDay (or the new parseLocalEndOfDayAsDate) and p.endDate to locate
and replace the conversion.

In `@pkg/serverutils/asynccache/asyncccache_test.go`:
- Around line 51-54: The fixed 100ms sleep in the test may be flaky; update the
test near the AsyncCache.Run usage to wait for the first refresh
deterministically by polling instead of sleeping: replace the time.Sleep(100 *
time.Millisecond) + item = c.GetItem() logic with a short Eventually-style loop
that calls c.GetItem() until it observes a change (or times out), or use
require.Eventually with a 1s timeout and 50ms interval to assert the first
refresh completed; reference AsyncCache.Run / wait.UntilWithContext and
c.GetItem to locate where to implement the polling/Eventually replacement.
🪄 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: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: d430fc51-11ff-4d29-9db4-24f47e29148e

📥 Commits

Reviewing files that changed from the base of the PR and between a7c07d8 and 1559116.

⛔ Files ignored due to path filters (284)
  • go.sum is excluded by !**/*.sum
  • vendor/dario.cat/mergo/FUNDING.json is excluded by !**/vendor/**, !vendor/**
  • vendor/dario.cat/mergo/README.md is excluded by !**/vendor/**, !vendor/**
  • vendor/dario.cat/mergo/SECURITY.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/AdaLogics/go-fuzz-headers/consumer.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/ProtonMail/go-crypto/internal/byteutil/byteutil.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/ProtonMail/go-crypto/ocb/ocb.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/ProtonMail/go-crypto/openpgp/armor/encode.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/ProtonMail/go-crypto/openpgp/errors/errors.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/ProtonMail/go-crypto/openpgp/packet/aead_crypter.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/ProtonMail/go-crypto/openpgp/packet/aead_encrypted.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/ProtonMail/go-crypto/openpgp/packet/config.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/ProtonMail/go-crypto/openpgp/packet/public_key.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/ProtonMail/go-crypto/openpgp/packet/signature.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetrically_encrypted_aead.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/antlr4-go/antlr/v4/antlrdoc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/antlr4-go/antlr/v4/atn.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/antlr4-go/antlr/v4/atn_config.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/antlr4-go/antlr/v4/input_stream.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/antlr4-go/antlr/v4/jcollect.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/antlr4-go/antlr/v4/lexer.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/antlr4-go/antlr/v4/ll1_analyzer.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/antlr4-go/antlr/v4/mutex.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/antlr4-go/antlr/v4/mutex_nomutex.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/antlr4-go/antlr/v4/parser_atn_simulator.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/antlr4-go/antlr/v4/prediction_context.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/antlr4-go/antlr/v4/recognizer.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/antlr4-go/antlr/v4/statistics.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/antlr4-go/antlr/v4/token.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/antlr4-go/antlr/v4/utils.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/clipperhouse/uax29/v2/LICENSE is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/clipperhouse/uax29/v2/graphemes/README.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/clipperhouse/uax29/v2/graphemes/ansi.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/clipperhouse/uax29/v2/graphemes/ansi8.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/clipperhouse/uax29/v2/graphemes/iterator.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/clipperhouse/uax29/v2/graphemes/reader.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/clipperhouse/uax29/v2/graphemes/splitfunc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/clipperhouse/uax29/v2/graphemes/trie.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/cloudflare/circl/dh/x25519/curve_amd64.s is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/cloudflare/circl/dh/x448/curve_amd64.s is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/cloudflare/circl/ecc/goldilocks/curve.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/cloudflare/circl/internal/conv/conv.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/cloudflare/circl/math/fp25519/fp_amd64.s is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/cloudflare/circl/math/fp448/fp_amd64.s is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/cloudflare/circl/math/integer.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/cloudflare/circl/sign/ed25519/point.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/cloudflare/circl/sign/ed448/ed448.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/cloudflare/circl/sign/sign.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/containerd/containerd/version/version.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/containerd/platforms/.golangci.yml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/containerd/platforms/compare.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/containerd/platforms/cpuinfo_linux.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/containerd/platforms/cpuinfo_other.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/containerd/platforms/database.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/containerd/platforms/defaults_windows.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/containerd/platforms/platform_compat_windows.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/containerd/platforms/platform_windows_compat.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/containerd/platforms/platforms.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/containerd/platforms/platforms_other.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/containerd/platforms/platforms_windows.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/docker/docker-credential-helpers/client/command.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/docker/go-connections/tlsconfig/certpool.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/docker/go-connections/tlsconfig/config.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/docker/go-connections/tlsconfig/config_client_ciphers.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/emicklei/go-restful/v3/.travis.yml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/emicklei/go-restful/v3/CHANGES.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/emicklei/go-restful/v3/README.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/emicklei/go-restful/v3/curly.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/emicklei/go-restful/v3/custom_verb.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/emicklei/go-restful/v3/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-git/go-billy/v5/fs.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-git/go-billy/v5/helper/chroot/chroot.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-git/go-billy/v5/memfs/memory.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-git/go-billy/v5/memfs/storage.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-git/go-billy/v5/osfs/os_bound.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-git/go-billy/v5/osfs/os_chroot.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-git/go-git/v5/options.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-git/go-git/v5/plumbing/format/idxfile/idxfile.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-git/go-git/v5/plumbing/object/commit.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-git/go-git/v5/plumbing/transport/common.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-git/go-git/v5/plumbing/transport/http/common.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-git/go-git/v5/plumbing/transport/http/transport.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-git/go-git/v5/plumbing/transport/ssh/auth_method.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-git/go-git/v5/plumbing/transport/ssh/common.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-git/go-git/v5/remote.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-git/go-git/v5/repository.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-git/go-git/v5/utils/merkletrie/change.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-git/go-git/v5/utils/merkletrie/difftree.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-git/go-git/v5/utils/merkletrie/index/node.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-git/go-git/v5/worktree.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-git/go-git/v5/worktree_status.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/jsonpointer/.cliff.toml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/jsonpointer/.gitignore is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/jsonpointer/.golangci.yml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/jsonpointer/CONTRIBUTORS.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/jsonpointer/LICENSE is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/jsonpointer/NOTICE is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/jsonpointer/README.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/jsonpointer/SECURITY.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/jsonpointer/errors.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/jsonpointer/pointer.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/jsonreference/.cliff.toml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/jsonreference/.editorconfig is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/jsonreference/.golangci.yml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/jsonreference/CONTRIBUTORS.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/jsonreference/NOTICE is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/jsonreference/README.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/jsonreference/SECURITY.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/jsonreference/internal/normalize_url.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/jsonreference/reference.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/.codecov.yml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/.golangci.yml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/.mockery.yml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/README.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/SECURITY.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/cmdutils/LICENSE is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/cmdutils/cmd_utils.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/cmdutils/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/cmdutils_iface.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/conv/LICENSE is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/conv/convert.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/conv/convert_types.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/conv/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/conv/format.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/conv/sizeof.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/conv/type_constraints.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/conv_iface.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/convert.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/convert_types.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/file.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/fileutils/LICENSE is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/fileutils/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/fileutils/file.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/fileutils/path.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/fileutils_iface.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/go.work is excluded by !**/*.work, !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/go.work.sum is excluded by !**/*.sum, !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/initialism_index.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/json.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/jsonname/LICENSE is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/jsonname/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/jsonname/name_provider.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/jsonname_iface.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/jsonutils/LICENSE is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/jsonutils/README.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/jsonutils/adapters/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/jsonutils/adapters/ifaces/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/jsonutils/adapters/ifaces/ifaces.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/jsonutils/adapters/ifaces/registry_iface.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/jsonutils/adapters/registry.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/adapter.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/lexer.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/ordered_map.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/pool.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/register.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/jsonutils/adapters/stdlib/json/writer.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/jsonutils/concat.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/jsonutils/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/jsonutils/json.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/jsonutils/ordered_map.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/jsonutils_iface.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/loading/LICENSE is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/loading/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/loading/errors.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/loading/json.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/loading/loading.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/loading/options.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/loading/yaml.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/loading_iface.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/mangling/BENCHMARK.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/mangling/LICENSE is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/mangling/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/mangling/initialism_index.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/mangling/name_lexem.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/mangling/name_mangler.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/mangling/options.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/mangling/pools.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/mangling/split.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/mangling/string_bytes.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/mangling/util.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/mangling_iface.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/name_lexem.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/net.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/netutils/LICENSE is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/netutils/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/netutils/net.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/netutils_iface.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/split.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/stringutils/LICENSE is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/stringutils/collection_formats.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/stringutils/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/stringutils/strings.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/stringutils_iface.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/typeutils/LICENSE is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/typeutils/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/typeutils/types.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/typeutils_iface.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/util.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/yaml.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/yamlutils/LICENSE is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/yamlutils/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/yamlutils/errors.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/yamlutils/ordered_map.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/yamlutils/yaml.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/go-openapi/swag/yamlutils_iface.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang/mock/gomock/call.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang/mock/gomock/controller.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/golang/mock/gomock/doc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/cel/BUILD.bazel is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/cel/env.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/cel/folding.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/cel/library.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/cel/optimizer.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/cel/program.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/cel/templates/authoring.tmpl is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/cel/validator.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/checker/checker.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/checker/env.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/checker/scopes.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/common/ast/ast.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/common/debug/debug.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/common/env/BUILD.bazel is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/common/env/env.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/common/types/BUILD.bazel is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/common/types/bool.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/common/types/bytes.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/common/types/double.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/common/types/duration.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/common/types/int.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/common/types/json_value.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/common/types/list.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/common/types/map.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/common/types/null.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/common/types/object.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/common/types/pb/type.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/common/types/string.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/common/types/timestamp.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/common/types/uint.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/interpreter/attribute_patterns.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/interpreter/attributes.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/interpreter/interpretable.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/interpreter/interpreter.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/interpreter/planner.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/cel-go/parser/helper.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/gnostic-models/extensions/extension.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/gnostic-models/openapiv2/OpenAPIv2.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/gnostic-models/openapiv3/OpenAPIv3.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/google/gnostic-models/openapiv3/annotations.proto is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/hashicorp/go-retryablehttp/.go-version is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/hashicorp/go-retryablehttp/.golangci.yml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/hashicorp/go-retryablehttp/CODEOWNERS is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/hashicorp/go-retryablehttp/Makefile is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/hashicorp/go-retryablehttp/client.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/josharian/intern/README.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/josharian/intern/intern.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/josharian/intern/license.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/klauspost/compress/.gitattributes is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/klauspost/compress/.goreleaser.yml is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/klauspost/compress/README.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/klauspost/compress/fse/README.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/klauspost/compress/fse/bitwriter.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/klauspost/compress/fse/compress.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/klauspost/compress/huff0/README.md is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/klauspost/compress/huff0/bitwriter.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/klauspost/compress/huff0/compress.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/klauspost/compress/huff0/decompress.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/klauspost/compress/huff0/decompress_amd64.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/klauspost/compress/huff0/decompress_generic.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/klauspost/compress/huff0/huff0.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/klauspost/compress/internal/cpuinfo/cpuinfo_amd64.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/klauspost/compress/internal/le/unsafe_disabled.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/klauspost/compress/internal/le/unsafe_enabled.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/klauspost/compress/internal/snapref/decode.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/klauspost/compress/internal/snapref/encode.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/klauspost/compress/zstd/bitwriter.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/klauspost/compress/zstd/blockdec.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/klauspost/compress/zstd/blockenc.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/klauspost/compress/zstd/decoder.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/klauspost/compress/zstd/decoder_options.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/klauspost/compress/zstd/dict.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/klauspost/compress/zstd/enc_base.go is excluded by !**/vendor/**, !vendor/**
  • vendor/github.com/klauspost/compress/zstd/enc_best.go is excluded by !**/vendor/**, !vendor/**
📒 Files selected for processing (16)
  • frontend/packages/console-shared/src/constants/time.ts
  • frontend/packages/operator-lifecycle-manager/console-extensions.json
  • frontend/packages/operator-lifecycle-manager/locales/en/olm.json
  • frontend/packages/operator-lifecycle-manager/package.json
  • frontend/packages/operator-lifecycle-manager/src/components/__tests__/operator-lifecycle-status.spec.tsx
  • frontend/packages/operator-lifecycle-manager/src/components/clusterserviceversion.tsx
  • frontend/packages/operator-lifecycle-manager/src/components/operator-lifecycle-status.tsx
  • frontend/packages/operator-lifecycle-manager/src/const.ts
  • frontend/packages/operator-lifecycle-manager/src/features.ts
  • frontend/packages/operator-lifecycle-manager/src/hooks/useOperatorLifecycle.ts
  • go.mod
  • pkg/olm/handler.go
  • pkg/olm/handler_test.go
  • pkg/olm/lifecycle.go
  • pkg/olm/lifecycle_test.go
  • pkg/serverutils/asynccache/asyncccache_test.go

Comment on lines +381 to +388
const [lifecycleData] = useOperatorLifecycle(
lifecycleEnabled ? packageName : undefined,
catalogName,
catalogNamespace,
);
const clusterVersion = getClusterVersion();
const compatible = getClusterCompatibility(lifecycleData, version, clusterVersion);
const supportPhase = getSupportPhase(lifecycleData, version);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Loading and error states from lifecycle hook are ignored.

The useOperatorLifecycle hook returns [data, loading, error], but only the data is destructured. While loading or encountering errors, the UI displays - in the lifecycle columns, providing no feedback to users. This could be confusing when data is temporarily unavailable or the backend is slow.

💡 Proposed enhancement for loading/error states
   const [lifecycleData] = useOperatorLifecycle(
+  const [lifecycleData, lifecycleLoading, lifecycleError] = useOperatorLifecycle(
     lifecycleEnabled ? packageName : undefined,
     catalogName,
     catalogNamespace,
   );
   const clusterVersion = getClusterVersion();
-  const compatible = getClusterCompatibility(lifecycleData, version, clusterVersion);
-  const supportPhase = getSupportPhase(lifecycleData, version);
+  const compatible = lifecycleLoading ? 'no-data' : getClusterCompatibility(lifecycleData, version, clusterVersion);
+  const supportPhase = lifecycleLoading ? 'no-data' : getSupportPhase(lifecycleData, version);

Or show a loading spinner icon in the table cells when lifecycleLoading is true.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/packages/operator-lifecycle-manager/src/components/clusterserviceversion.tsx`
around lines 381 - 388, Destructure the full return from useOperatorLifecycle
(e.g., const [lifecycleData, lifecycleLoading, lifecycleError] =
useOperatorLifecycle(...)) instead of only data, then propagate those states
into the lifecycle UI: when lifecycleLoading is true show a loading spinner/icon
in the lifecycle-related table cells (where compatible and supportPhase are
rendered) and when lifecycleError is present show an error indicator/message
rather than the static '-' placeholder; update any calls or UI logic that
compute compatible and supportPhase (getClusterCompatibility, getSupportPhase)
to use lifecycleData only when not loading/error so you don't compute against
undefined.

Comment on lines +84 to +92
const parseLocalStartOfDay = (dateStr: string): number => {
const [y, m, d] = dateStr.split('-').map(Number);
return new Date(y, m - 1, d).getTime();
};

const parseLocalEndOfDay = (dateStr: string): number => {
const [y, m, d] = dateStr.split('-').map(Number);
return new Date(y, m - 1, d, 23, 59, 59, 999).getTime();
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Date parsing lacks input validation.

The parseLocalStartOfDay and parseLocalEndOfDay functions split the date string and convert to numbers without validating the input format. If the backend returns malformed dates (e.g., missing components, wrong separator), split('-').map(Number) will produce NaN values, resulting in Invalid Date objects and incorrect timestamp calculations.

🛡️ Proposed fix to add validation
 const parseLocalStartOfDay = (dateStr: string): number => {
+  if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
+    return NaN;
+  }
   const [y, m, d] = dateStr.split('-').map(Number);
+  if (!y || !m || !d) {
+    return NaN;
+  }
   return new Date(y, m - 1, d).getTime();
 };

 const parseLocalEndOfDay = (dateStr: string): number => {
+  if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
+    return NaN;
+  }
   const [y, m, d] = dateStr.split('-').map(Number);
+  if (!y || !m || !d) {
+    return NaN;
+  }
   return new Date(y, m - 1, d, 23, 59, 59, 999).getTime();
 };

Callers should also check for NaN results, or wrap date operations in try-catch.

📝 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
const parseLocalStartOfDay = (dateStr: string): number => {
const [y, m, d] = dateStr.split('-').map(Number);
return new Date(y, m - 1, d).getTime();
};
const parseLocalEndOfDay = (dateStr: string): number => {
const [y, m, d] = dateStr.split('-').map(Number);
return new Date(y, m - 1, d, 23, 59, 59, 999).getTime();
};
const parseLocalStartOfDay = (dateStr: string): number => {
if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
return NaN;
}
const [y, m, d] = dateStr.split('-').map(Number);
if (!y || !m || !d) {
return NaN;
}
return new Date(y, m - 1, d).getTime();
};
const parseLocalEndOfDay = (dateStr: string): number => {
if (!/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
return NaN;
}
const [y, m, d] = dateStr.split('-').map(Number);
if (!y || !m || !d) {
return NaN;
}
return new Date(y, m - 1, d, 23, 59, 59, 999).getTime();
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/packages/operator-lifecycle-manager/src/components/operator-lifecycle-status.tsx`
around lines 84 - 92, The parsing functions parseLocalStartOfDay and
parseLocalEndOfDay currently assume a well-formed "YYYY-MM-DD" string; add
defensive validation: ensure dateStr.split('-') yields exactly 3 parts, convert
each part to Number and assert none are NaN (use Number.isFinite or
!Number.isNaN), validate month is 1–12 and day is 1–31 (optional stricter day
check per month), then construct the Date and verify resulting date.getTime() is
finite; if validation fails return NaN or throw a clear Error so callers can
handle malformed backend dates.

Comment on lines +159 to +161
const formatDate = (date: Date): string => {
return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Hardcoded locale breaks internationalization.

The formatDate function hardcodes the 'en-US' locale, which conflicts with the i18n infrastructure already in place (react-i18next). Users in different locales will see dates in US format regardless of their locale settings.

🌍 Proposed fix to respect user locale
 const formatDate = (date: Date): string => {
-  return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
+  return date.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });
 };

Passing undefined as the locale uses the user's browser locale automatically.

📝 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
const formatDate = (date: Date): string => {
return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
};
const formatDate = (date: Date): string => {
return date.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/packages/operator-lifecycle-manager/src/components/operator-lifecycle-status.tsx`
around lines 159 - 161, The formatDate function currently hardcodes the 'en-US'
locale, breaking i18n; update formatDate to use the user's locale instead—either
call toLocaleDateString without a locale (pass undefined) or read the active
locale from react-i18next (useTranslation()/i18n.language) and pass that value;
modify the formatDate implementation (function name: formatDate) to remove the
'en-US' literal and use the browser/default or i18n-provided locale so dates
respect user settings.

Comment on lines +7 to +29
const CACHE_TTL_SUCCESS = 5 * 60 * 1000; // 5 minutes
const CACHE_TTL_ERROR = 30 * 1000; // 30 seconds

type LifecycleCacheEntry = {
data: LifecycleData | null;
error: Error | null;
timestamp: number;
promise?: Promise<LifecycleData>;
};

const lifecycleCache = new Map<string, LifecycleCacheEntry>();

const buildCacheKey = (catalogNamespace: string, catalogName: string, packageName: string) =>
`${catalogNamespace}/${catalogName}/${packageName}`;

const isCacheValid = (entry: LifecycleCacheEntry): boolean => {
if (entry.promise) {
return true;
}
const age = Date.now() - entry.timestamp;
const ttl = entry.error ? CACHE_TTL_ERROR : CACHE_TTL_SUCCESS;
return age < ttl;
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚖️ Poor tradeoff

Cache grows unbounded leading to potential memory leak.

The lifecycleCache Map has no eviction policy. While entries have TTL checks in isCacheValid, stale entries are never removed from the Map itself. Over time (e.g., long-running console sessions visiting many operators), the cache will accumulate expired entries, consuming memory unnecessarily.

Consider adding periodic cleanup or size-based eviction:

const MAX_CACHE_SIZE = 100;

const evictOldEntries = () => {
  if (lifecycleCache.size > MAX_CACHE_SIZE) {
    const entries = Array.from(lifecycleCache.entries());
    entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
    const toDelete = entries.slice(0, entries.length - MAX_CACHE_SIZE);
    toDelete.forEach(([key]) => lifecycleCache.delete(key));
  }
};

// Call evictOldEntries() after each cache insertion

Or use a proper LRU cache library if available in the project.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/packages/operator-lifecycle-manager/src/hooks/useOperatorLifecycle.ts`
around lines 7 - 29, The lifecycleCache Map has no eviction and can grow
unbounded; add a bounded eviction policy (or periodic cleanup) to remove
stale/old entries: define a MAX_CACHE_SIZE constant and an evictOldEntries
function that sorts lifecycleCache entries by entry.timestamp and removes the
oldest until size <= MAX_CACHE_SIZE, and also add a periodic sweep that deletes
entries whose timestamp + TTL (use CACHE_TTL_SUCCESS/CACHE_TTL_ERROR depending
on entry.error) is expired; invoke evictOldEntries (and/or the sweep) after any
insertion into lifecycleCache (where entries are created using buildCacheKey and
LifecycleCacheEntry) and/or schedule it on an interval to prevent memory leaks
while keeping isCacheValid logic intact.

Comment on lines +31 to +42
const extractPackageName = (olmProperties: string): string | undefined => {
try {
const props = JSON.parse(olmProperties);
const packageProp = props.find(
(p: { type: string; value: { packageName?: string } }) =>
p.type === 'olm.package' && p.value?.packageName,
);
return packageProp?.value?.packageName;
} catch {
return undefined;
}
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

JSON.parse on untrusted annotation data could throw.

The extractPackageName function parses the olm.properties annotation using JSON.parse with a try-catch. While the catch block returns undefined, this assumes annotations are benign. If annotations can be controlled by external operators or untrusted sources, malformed or malicious JSON could cause denial-of-service through excessive parsing time or throw unexpected errors.

The current try-catch already handles parse failures gracefully. However, ensure OLM annotations are validated at ingestion time, or add a size check before parsing:

 const extractPackageName = (olmProperties: string): string | undefined => {
+  if (!olmProperties || olmProperties.length > 10000) {
+    return undefined;
+  }
   try {
     const props = JSON.parse(olmProperties);

Based on learnings from similar annotation parsing patterns in the codebase.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/packages/operator-lifecycle-manager/src/hooks/useOperatorLifecycle.ts`
around lines 31 - 42, The extractPackageName function currently calls JSON.parse
on the olmProperties string which may be untrusted; before parsing in
extractPackageName(olmProperties) validate that olmProperties is a string and
below a safe length (e.g., a configurable small max bytes) and return undefined
if it exceeds that limit or is not a string, then proceed to JSON.parse inside
the existing try/catch; this prevents expensive or maliciously large payloads
from being parsed while preserving the graceful undefined fallback handled by
the current catch.

Comment on lines +169 to +171
export const getClusterVersion = (): string | undefined => {
return window.SERVER_FLAGS?.releaseVersion;
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Potential null dereference on window.SERVER_FLAGS.

The getClusterVersion function accesses window.SERVER_FLAGS?.releaseVersion using optional chaining on the first property, but window.SERVER_FLAGS itself is not checked for existence. In environments where SERVER_FLAGS is undefined (e.g., tests, certain build configurations), this will return undefined as expected. However, explicit defensive checks improve clarity.

🛡️ Proposed fix for explicit null safety
 export const getClusterVersion = (): string | undefined => {
-  return window.SERVER_FLAGS?.releaseVersion;
+  return typeof window !== 'undefined' ? window.SERVER_FLAGS?.releaseVersion : undefined;
 };

Or if window is guaranteed in browser context:

 export const getClusterVersion = (): string | undefined => {
-  return window.SERVER_FLAGS?.releaseVersion;
+  return window?.SERVER_FLAGS?.releaseVersion;
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/packages/operator-lifecycle-manager/src/hooks/useOperatorLifecycle.ts`
around lines 169 - 171, The getClusterVersion function currently relies on
optional chaining but should explicitly guard against missing SERVER_FLAGS:
update getClusterVersion to first check that window && typeof window !==
"undefined" and that window.SERVER_FLAGS is truthy (or use a null-check like if
(!window.SERVER_FLAGS) return undefined) before returning
window.SERVER_FLAGS.releaseVersion, ensuring the function safely handles
environments where SERVER_FLAGS is undefined and preserves the string |
undefined return type.

Comment thread go.mod
github.com/devfile/registry-support/index/generator v0.0.0-20240419194226-cca4c9a81f8d
github.com/devfile/registry-support/registry-library v0.0.0-20240521161747-89fc566cb024
github.com/golang/mock v1.6.0
github.com/golang/mock v1.7.0-rc.1
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Remove pre-release version from production dependencies.

github.com/golang/mock v1.7.0-rc.1 is a release candidate (pre-release version). As per coding guidelines, pre-release versions should not be used in production.

🔧 Recommended fix

Either pin to the latest stable release of github.com/golang/mock or use the maintained fork go.uber.org/mock (the original repository is archived):

-	github.com/golang/mock v1.7.0-rc.1
+	go.uber.org/mock v0.5.0

Note: golang/mock is archived; go.uber.org/mock is the actively maintained fork.

As per coding guidelines: "No pre-release or yanked versions in production" for supply chain security.

📝 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
github.com/golang/mock v1.7.0-rc.1
go.uber.org/mock v0.5.0
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go.mod` at line 13, The go.mod currently pins a pre-release module
"github.com/golang/mock v1.7.0-rc.1"; replace this pre-release with a stable
release or the actively maintained fork. Locate the dependency entry for
github.com/golang/mock v1.7.0-rc.1 and either change it to the latest stable
semver (e.g., v1.6.x or the latest non-rc release) or replace the module path
with the maintained fork go.uber.org/mock at its current stable version; then
run go get / go mod tidy to update the lockfile and verify build/tests.

Comment thread go.mod
github.com/openshift/client-go v0.0.0-20260108185524-48f4ccfc4e13
github.com/openshift/library-go v0.0.0-20231020125034-5a2d9fe760b3
github.com/operator-framework/api v0.30.0
github.com/operator-framework/api v0.42.0
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check for CVEs in updated Go dependencies

# List of updated direct dependencies to check
PACKAGES=(
  "github.com/operator-framework/api@v0.42.0"
  "github.com/operator-framework/operator-registry@v1.69.0"
  "github.com/prometheus/common@v0.67.5"
  "golang.org/x/mod@v0.36.0"
  "golang.org/x/net@v0.54.0"
  "golang.org/x/oauth2@v0.36.0"
  "google.golang.org/grpc@v1.81.1"
  "google.golang.org/protobuf@v1.36.11"
  "k8s.io/api@v0.35.4"
  "k8s.io/apiextensions-apiserver@v0.35.4"
  "k8s.io/apimachinery@v0.35.4"
  "k8s.io/client-go@v0.35.4"
  "k8s.io/utils@v0.0.0-20260108192941-914a6e750570"
  "sigs.k8s.io/controller-runtime@v0.23.3"
)

echo "Checking for known vulnerabilities..."
for pkg in "${PACKAGES[@]}"; do
  echo "Checking $pkg"
  # Query OSV database for vulnerabilities
  curl -s -X POST "https://api.osv.dev/v1/query" \
    -H "Content-Type: application/json" \
    -d "{\"package\": {\"name\": \"${pkg%@*}\", \"ecosystem\": \"Go\"}, \"version\": \"${pkg#*@}\"}" | \
    jq -r '.vulns[]? | "  CVE: \(.id) - \(.summary)"'
done

echo -e "\nChecking if versions exist in proxy..."
for pkg in "${PACKAGES[@]}"; do
  module="${pkg%@*}"
  version="${pkg#*@}"
  # Check Go module proxy
  status=$(curl -s -o /dev/null -w "%{http_code}" "https://proxy.golang.org/${module}/@v/${version}.info")
  if [ "$status" != "200" ]; then
    echo "WARNING: $pkg returned HTTP $status (may be yanked or invalid)"
  else
    echo "$pkg exists"
  fi
done

Repository: openshift/console

Length of output: 1993


Address OSV-flagged CVEs in updated golang.org/x/net
The vulnerability scan of the updated direct dependencies reports multiple OSV advisories for golang.org/x/net@v0.54.0 (GO-2026-5025 through GO-2026-5030; issues include HTML parsing/DoS and potential XSS in golang.org/x/net/html). Update golang.org/x/net to a fixed version and confirm compatibility across the Kubernetes/operator/grpc/protobuf stack.
All checked versions (including golang.org/x/net@v0.54.0) exist on proxy.golang.org (HTTP 200), so this is not a yanked/invalid-version problem.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go.mod` at line 21, The go.mod currently pulls golang.org/x/net at a version
flagged by OSV (see golang.org/x/net); update the golang.org/x/net dependency in
go.mod to the patched release that fixes GO-2026-5025 through GO-2026-5030, then
run module resolution (e.g., bump via the Go tool and run tidy) to pin the fixed
version; finally run your full test/build matrix and verify compatibility across
the Kubernetes/operator-framework/api, gRPC and protobuf dependencies (rebuild,
run unit/integration tests and ensure no module version conflicts) and commit
the updated go.mod/go.sum.

Comment thread pkg/olm/lifecycle.go
Comment on lines +66 to +67
serverutils.SendResponse(w, http.StatusInternalServerError, serverutils.ApiError{Err: fmt.Sprintf("failed to create gRPC client: %v", err)})
return
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid exposing backend internals in API error payloads.

Line 66 and Line 136 return raw transport/backend error details to the client. This leaks internal implementation details and creates an unstable public error contract. Return a stable generic message to clients and keep specifics in server logs.

Suggested hardening
- serverutils.SendResponse(w, http.StatusInternalServerError, serverutils.ApiError{Err: fmt.Sprintf("failed to create gRPC client: %v", err)})
+ serverutils.SendResponse(w, http.StatusInternalServerError, serverutils.ApiError{Err: "failed to connect to catalog source"})
...
- serverutils.SendResponse(w, http.StatusBadGateway, serverutils.ApiError{Err: fmt.Sprintf("catalog source error: %s", st.Message())})
+ serverutils.SendResponse(w, http.StatusBadGateway, serverutils.ApiError{Err: "catalog source error"})

Also applies to: 136-136

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/olm/lifecycle.go` around lines 66 - 67, Replace the responses that
include raw backend errors in serverutils.SendResponse calls (e.g., the ApiError
constructed with fmt.Sprintf("failed to create gRPC client: %v", err) and the
similar occurrence around line 136) with a stable, generic client-facing message
(for example "internal server error" or "failed to create client") and do not
include err details in the ApiError payload; instead, log the full err
server-side using the existing logger or fmt/remote logger (referencing the same
scope where the error occurs) so internals stay out of the API contract.

Comment thread pkg/olm/lifecycle.go
serverutils.SendResponse(w, http.StatusInternalServerError, serverutils.ApiError{Err: fmt.Sprintf("failed to create gRPC client: %v", err)})
return
}
defer conn.Close()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ls -la
rg -n "conn\.Close\(" -S pkg/olm/lifecycle.go || true
rg -n "\bw\.Write\(" -S pkg/olm/lifecycle.go || true

echo "---- context around conn.Close ----"
sed -n '40,95p' pkg/olm/lifecycle.go

echo "---- context around w.Write ----"
sed -n '95,160p' pkg/olm/lifecycle.go

Repository: openshift/console

Length of output: 8123


Handle errors returned by conn.Close() and w.Write(...)

defer conn.Close() drops any close error, and w.Write(jsonBytes) discards the write error. Log (or otherwise handle) both so transport failures are observable.

Suggested fix
- defer conn.Close()
+ defer func() {
+ 	if cerr := conn.Close(); cerr != nil {
+ 		klog.Warningf("[lifecycle] Failed to close gRPC client for %s: %v", target, cerr)
+ 	}
+ }()
...
- w.Write(jsonBytes)
+ if _, err := w.Write(jsonBytes); err != nil {
+ 	klog.Errorf("[lifecycle] Failed to write lifecycle response body: %v", err)
+ }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/olm/lifecycle.go` at line 69, The defer conn.Close() and the
w.Write(jsonBytes) calls currently ignore returned errors; change defer
conn.Close() to capture and log its error (e.g., defer func(){ if err :=
conn.Close(); err != nil { logger.Errorf("conn.Close failed: %v", err) }}()) and
handle the result of w.Write(jsonBytes) by checking the returned (n, err) and
logging or returning the error from the function (e.g., if _, err :=
w.Write(jsonBytes); err != nil { logger.Errorf("write response failed: %v",
err); return }) so transport/close failures from conn.Close() and w.Write(...)
are observable. Ensure you use the local logger/context used in this file for
consistency and preserve existing control flow in the function where
conn.Close() and w.Write(jsonBytes) are used.

Per G. da Silva and others added 4 commits June 4, 2026 16:02
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use appendTo="inline" so the popover stays anchored to its trigger
element through table re-renders.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented Jun 4, 2026

@perdasilva: The following test failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/e2e-gcp-console ae6675b link true /test e2e-gcp-console

Full PR test history. Your PR dashboard.

Details

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. I understand the commands that are listed here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component/backend Related to backend component/olm Related to OLM component/shared Related to console-shared do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. kind/i18n Indicates issue or PR relates to internationalization or has content that needs to be translated

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant