Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,23 +32,6 @@
"flag": "CLUSTER_EXTENSION_API"
}
},
{
"type": "console.navigation/resource-cluster",
"properties": {
"id": "installed-extensions",
"section": "ecosystem",
"name": "%olm-v1~Installed Operators%",
"model": {
"kind": "ClusterExtension",
"version": "v1",
"group": "olm.operatorframework.io"
},
"startsWith": ["olm.operatorframework.io"]
},
"flags": {
"required": ["CLUSTER_EXTENSION_API", "TECH_PREVIEW", "OLMV1_ENABLED"]
}
},
{
"type": "console.catalog/item-provider",
"properties": {
Expand Down Expand Up @@ -117,5 +100,45 @@
"flags": {
"required": ["CLUSTER_EXTENSION_API"]
}
},
{
"type": "console.action/resource-provider",
"properties": {
"model": {
"group": "olm.operatorframework.io",
"version": "v1",
"kind": "ClusterExtension"
},
"provider": { "$codeRef": "defaultActionsProvider.useDefaultActionsProvider" }
},
"flags": {
"required": ["CLUSTER_EXTENSION_API"]
}
},
{
"type": "console.navigation/href",
"properties": {
"id": "installed-software",
"section": "ecosystem",
"name": "%olm-v1~Installed Software%",
"insertAfter": "developer-catalog",
"href": "/installed-software",
"namespaced": true,
"startsWith": ["installed-software"]
},
"flags": {
"required": ["TECH_PREVIEW", "OLMV1_ENABLED", "CLUSTER_EXTENSION_API"]
}
},
{
"type": "console.page/route",
"properties": {
"exact": false,
"path": ["/installed-software/all-namespaces", "/installed-software/ns/:ns"],
"component": { "$codeRef": "installedSoftwarePage.default" }
},
"flags": {
"required": ["CLUSTER_EXTENSION_API"]
}
}
]
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"Installed Operators": "Installed Operators",
"Operators": "Operators",
"OLMv1 Catalog": "OLMv1 Catalog",
"Enable the OLMv1 catalog experience. OLMv1 is a technology preview feature that provides an updated operator catalog interface.": "Enable the OLMv1 catalog experience. OLMv1 is a technology preview feature that provides an updated operator catalog interface.",
"Enable OLMv1 catalog": "Enable OLMv1 catalog",
"Installed Software": "Installed Software",
"An error occurred creating the ClusterExtension: {{error}}": "An error occurred creating the ClusterExtension: {{error}}",
"Name": "Name",
"Package name": "Package name",
Expand Down Expand Up @@ -43,17 +43,22 @@
"Package will only be resolved from specified catalogs.": "Package will only be resolved from specified catalogs.",
"Create": "Create",
"Cancel": "Cancel",
"Status": "Status",
"Package": "Package",
"ClusterExtensions": "ClusterExtensions",
"Create ClusterExtension": "Create ClusterExtension",
"Create a ClusterExtension to add functionality to your cluster. Operator Lifecycle Manager v1 manages ClusterExtensions.": "Create a ClusterExtension to add functionality to your cluster. Operator Lifecycle Manager v1 manages ClusterExtensions.",
"An error occurred. Please try again.": "An error occurred. Please try again.",
"Create ServiceAccount": "Create ServiceAccount",
"An error occurred": "An error occurred",
"Cluster extensions (OLMv1)": "Cluster extensions (OLMv1)",
"Operators (OLMv0)": "Operators (OLMv0)",
"Operator Lifecycle Management version 1": "Operator Lifecycle Management version 1",
"Learn more about OLMv1": "Learn more about OLMv1",
"With OLMv1, you'll get a much simpler API that's easier to work with and understand. Plus, you have more direct control over updates. You can define update ranges and decide exactly how they are rolled out.": "With OLMv1, you'll get a much simpler API that's easier to work with and understand. Plus, you have more direct control over updates. You can define update ranges and decide exactly how they are rolled out.",
"Lets you use OLMv1 (Tech Preview), a streamlined redesign of OLMv0. OLMv1 simplifies operator management with declarative APIs, enhanced security, and direct, GitOps-friendly control over upgrades.": "Lets you use OLMv1 (Tech Preview), a streamlined redesign of OLMv0. OLMv1 simplifies operator management with declarative APIs, enhanced security, and direct, GitOps-friendly control over upgrades.",
"Enable OLMv1": "Enable OLMv1",
"Toggle OLMv1 UI": "Toggle OLMv1 UI",
"Lets you use OLMv1 (Tech Preview), a streamlined redesign of OLMv0. OLMv1 simplifies operator management with declarative APIs, enhanced security, and direct, GitOps-friendly control over upgrades.": "Lets you use OLMv1 (Tech Preview), a streamlined redesign of OLMv0. OLMv1 simplifies operator management with declarative APIs, enhanced security, and direct, GitOps-friendly control over upgrades.",
"Tech Preview": "Tech Preview",
"OLMv1 information": "OLMv1 information"
}
4 changes: 3 additions & 1 deletion frontend/packages/operator-lifecycle-manager-v1/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
"useCatalogItems": "src/hooks/useCatalogItems.ts",
"useCatalogCategories": "src/hooks/useCatalogCategories.ts",
"useOLMv1FlagProvider": "src/hooks/useOLMv1FlagProvider.ts",
"CreateClusterExtension": "src/components/cluster-extension/CreateClusterExtension.tsx"
"CreateClusterExtension": "src/components/cluster-extension/CreateClusterExtension.tsx",
"defaultActionsProvider": "src/actions/providers/default-actions-provider.ts",
"installedSoftwarePage": "src/components/installed-software/InstalledSoftwarePage.tsx"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { useDefaultActionsProvider } from '@console/app/src/actions/providers/default-provider';
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { useCallback } from 'react';
import type { FC, FormEvent } from 'react';
import { Button, Flex, FlexItem, Label, Popover, Switch } from '@patternfly/react-core';
import { OutlinedQuestionCircleIcon } from '@patternfly/react-icons/dist/esm/icons/outlined-question-circle-icon';
import { Flex, FlexItem, Switch } from '@patternfly/react-core';
import { useTranslation } from 'react-i18next';
import { FLAG_TECH_PREVIEW } from '@console/app/src/consts';
import { useFlag } from '@console/dynamic-plugin-sdk/src/utils/flags';
import { useUserSettings } from '@console/shared/src/hooks/useUserSettings';
import { OLMV1_ENABLED_USER_SETTING_KEY } from '../const';
import { OLMv1TechPreviewBadge } from './OLMv1TechPreviewBadge';

/**
* Toolbar component for toggling OLMv1 UI visibility in the operator catalog.
Expand All @@ -28,14 +28,6 @@ export const OLMv1Switch: FC = () => {
[setOlmv1Enabled],
);

const popoverContent = (
<div>
{t(
'olm-v1~Lets you use OLMv1 (Tech Preview), a streamlined redesign of OLMv0. OLMv1 simplifies operator management with declarative APIs, enhanced security, and direct, GitOps-friendly control over upgrades.',
)}
</div>
);

return (
<Flex alignItems={{ default: 'alignItemsCenter' }} spaceItems={{ default: 'spaceItemsSm' }}>
<FlexItem>
Expand All @@ -48,19 +40,7 @@ export const OLMv1Switch: FC = () => {
/>
</FlexItem>
<FlexItem>
<Label color="yellow" isCompact>
{t('olm-v1~Tech Preview')}
</Label>
</FlexItem>
<FlexItem>
<Popover aria-label={t('olm-v1~OLMv1 information')} bodyContent={popoverContent}>
<Button
icon={<OutlinedQuestionCircleIcon />}
aria-label={t('olm-v1~OLMv1 information')}
variant="link"
isInline
/>
</Popover>
<OLMv1TechPreviewBadge />
</FlexItem>
</Flex>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { FC } from 'react';
import { Button, Label, Popover } from '@patternfly/react-core';
import { OutlinedQuestionCircleIcon } from '@patternfly/react-icons/dist/esm/icons/outlined-question-circle-icon';
import { useTranslation } from 'react-i18next';

/**
* Shared tech preview badge with info popover for OLMv1 features.
* Displays a yellow outline label with "Tech Preview" and an info icon that shows
* a popover with details about OLMv1 when clicked.
* Renders inline for use in tabs or toolbars.
*/
export const OLMv1TechPreviewBadge: FC = () => {
const { t } = useTranslation();

const popoverContent = (
<div>
{t(
'olm-v1~Lets you use OLMv1 (Tech Preview), a streamlined redesign of OLMv0. OLMv1 simplifies operator management with declarative APIs, enhanced security, and direct, GitOps-friendly control over upgrades.',
)}
</div>
);

return (
<>
<Label color="yellow" isCompact variant="outline">
{t('olm-v1~Tech Preview')}
</Label>{' '}
<Popover aria-label={t('olm-v1~OLMv1 information')} bodyContent={popoverContent}>
<Button
icon={<OutlinedQuestionCircleIcon aria-hidden="true" />}
aria-label={t('olm-v1~OLMv1 information')}
variant="link"
isInline
/>
</Popover>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import type { FC } from 'react';
import { useMemo } from 'react';
import { Label } from '@patternfly/react-core';
import { useTranslation } from 'react-i18next';
import {
actionsCellProps,
cellIsStickyProps,
getNameCellProps,
ConsoleDataView,
} from '@console/app/src/components/data-view/ConsoleDataView';
import type { GetDataViewRows } from '@console/app/src/components/data-view/types';
import Status from '@console/dynamic-plugin-sdk/src/app/components/status/Status';
import type { TableColumn } from '@console/dynamic-plugin-sdk/src/extensions/console-types';
import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook';
import { ResourceLink } from '@console/internal/components/utils/resource-link';
import { referenceForModel } from '@console/internal/module/k8s';
import LazyActionMenu from '@console/shared/src/components/actions/LazyActionMenu';
import PaneBody from '@console/shared/src/components/layout/PaneBody';
import { DASH } from '@console/shared/src/constants/ui';
import { ClusterExtensionModel } from '../../models';
import type { ClusterExtensionKind } from '../../types';

export const tableColumnInfo = [
{ id: 'name' },
{ id: 'status' },
{ id: 'version' },
{ id: 'channel' },
{ id: 'namespace' },
{ id: 'package' },
{ id: '' },
];

const getDataViewRows: GetDataViewRows<ClusterExtensionKind> = (data, columns) => {
return data.map(({ obj }) => {
const name = obj.metadata?.name ?? '';
const namespace = obj.spec?.namespace ?? '';
const packageName = obj.spec?.source?.catalog?.packageName ?? '';
const version = obj.spec?.source?.catalog?.version ?? '';
const channels = obj.spec?.source?.catalog?.channels;
const status =
obj.status?.conditions?.find((condition) => condition.type === 'Installed')?.reason || '';

Comment on lines +40 to +42
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Status column may show for resources not yet in the Installed condition.

Only the 'Installed' condition's reason is used. A freshly created or progressing ClusterExtension that hasn't reached that condition yet will silently render DASH, giving users no feedback about its current state (e.g., Progressing, Failed, etc.).

Consider falling back to other notable condition types (e.g., the first non-True condition or a general Ready-equivalent), or at minimum the first available condition reason, so the status column is never vacuously empty for a live resource.

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

In
`@frontend/packages/operator-lifecycle-manager-v1/src/components/cluster-extension/ClusterExtensionListPage.tsx`
around lines 42 - 44, The status computation currently only returns the
'Installed' condition reason and falls back to empty; update the logic used
where `status` is derived from `obj.status?.conditions` so it prefers the
Installed reason but if missing falls back to the first available informative
condition (for example: first condition with a non-empty reason, or if none, the
first condition's type or a Ready-equivalent like the first non-True condition's
reason/type) so the status column never renders as a bare dash; locate the
`status` assignment (the line using `.find((condition) => condition.type ===
'Installed')?.reason`) and replace it with fallback selection of
condition.reason or condition.type in the described order.

const resourceKind = referenceForModel(ClusterExtensionModel);
const context = { [resourceKind]: obj };

const rowCells = {
[tableColumnInfo[0].id]: {
cell: <ResourceLink kind={resourceKind} name={name} />,
props: getNameCellProps(name),
},
[tableColumnInfo[1].id]: {
cell: status ? <Status status={status} /> : DASH,
},
[tableColumnInfo[2].id]: {
cell: version || DASH,
},
[tableColumnInfo[3].id]: {
cell:
channels && channels.length > 0 ? (
<>
{channels.map((ch) => (
<Label key={ch} color="grey" isCompact>
{ch}
</Label>
))}
</>
) : (
DASH
),
},
[tableColumnInfo[4].id]: {
cell: namespace ? <ResourceLink kind="Namespace" name={namespace} /> : DASH,
},
[tableColumnInfo[5].id]: {
cell: packageName || DASH,
},
[tableColumnInfo[6].id]: {
cell: <LazyActionMenu context={context} />,
props: actionsCellProps,
},
};

return columns.map(({ id }) => {
const cell = rowCells[id]?.cell || DASH;
return {
id,
props: rowCells[id]?.props,
cell,
};
});
});
};

const useClusterExtensionColumns = (): TableColumn<ClusterExtensionKind>[] => {
const { t } = useTranslation();
const columns = useMemo<TableColumn<ClusterExtensionKind>[]>(
() => [
{
title: t('olm-v1~Name'),
id: tableColumnInfo[0].id,
sort: 'metadata.name',
props: {
...cellIsStickyProps,
modifier: 'nowrap',
},
},
{
title: t('olm-v1~Status'),
id: tableColumnInfo[1].id,
props: {
modifier: 'nowrap',
},
},
{
title: t('olm-v1~Version'),
id: tableColumnInfo[2].id,
props: {
modifier: 'nowrap',
},
},
{
title: t('olm-v1~Channels'),
id: tableColumnInfo[3].id,
},
{
title: t('olm-v1~Namespace'),
id: tableColumnInfo[4].id,
sort: 'spec.namespace',
props: {
modifier: 'nowrap',
},
},
{
title: t('olm-v1~Package'),
id: tableColumnInfo[5].id,
props: {
modifier: 'nowrap',
},
},
{
title: '',
id: tableColumnInfo[6].id,
props: {
...cellIsStickyProps,
},
},
],
[t],
);

return columns;
};

interface ClusterExtensionListPageProps {
namespace?: string;
}

const ClusterExtensionListPage: FC<ClusterExtensionListPageProps> = () => {
const { t } = useTranslation();
const [clusterExtensions, loaded, loadError] = useK8sWatchResource<ClusterExtensionKind[]>({
kind: referenceForModel(ClusterExtensionModel),
isList: true,
namespaced: false,
});

const columns = useClusterExtensionColumns();

return (
<PaneBody>
<ConsoleDataView<ClusterExtensionKind>
label={t('olm-v1~ClusterExtensions')}
data={clusterExtensions ?? []}
loaded={loaded}
loadError={loadError}
columns={columns}
getDataViewRows={getDataViewRows}
hideColumnManagement
showNamespaceOverride
/>
</PaneBody>
);
};

export default ClusterExtensionListPage;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export { default as ClusterExtensionListPage } from './ClusterExtensionListPage';
export { default as CreateClusterExtension } from './CreateClusterExtension';
export { default as ClusterExtensionForm } from './ClusterExtensionForm';
export { ClusterExtensionYAMLEditor } from './ClusterExtensionYAMLEditor';
export { ServiceAccountDropdown } from './ServiceAccountDropdown';
Loading