diff --git a/cmd/bridge/main.go b/cmd/bridge/main.go index 75d862defe8..1fd27be5a1b 100644 --- a/cmd/bridge/main.go +++ b/cmd/bridge/main.go @@ -358,6 +358,7 @@ func main() { NodeOperatingSystems: nodeOperatingSystems, K8sMode: *fK8sMode, CopiedCSVsDisabled: *fCopiedCSVsDisabled, + TechPreview: *fTechPreview, Capabilities: capabilities, } diff --git a/frontend/@types/console/index.d.ts b/frontend/@types/console/index.d.ts index 6539a368d8d..6939d18e592 100644 --- a/frontend/@types/console/index.d.ts +++ b/frontend/@types/console/index.d.ts @@ -71,6 +71,7 @@ declare interface Window { nodeOperatingSystems: string[]; hubConsoleURL: string; k8sMode: string; + techPreview: boolean; capabilities: { name: string; visibility: { state: 'Enabled' | 'Disabled' }; diff --git a/frontend/packages/console-app/console-extensions.json b/frontend/packages/console-app/console-extensions.json index ff5979854b9..070683f6306 100644 --- a/frontend/packages/console-app/console-extensions.json +++ b/frontend/packages/console-app/console-extensions.json @@ -1008,6 +1008,9 @@ "group": "operators.coreos.com" }, "startsWith": ["operators.coreos.com", "clusterserviceversions"] + }, + "flags": { + "disallowed": ["OLMV1_ENABLED"] } }, { @@ -2527,6 +2530,12 @@ "provider": { "$codeRef": "defaultProvider.useDefaultActionsProvider" } } }, + { + "type": "console.flag/hookProvider", + "properties": { + "handler": { "$codeRef": "useTechPreviewFlagProvider" } + } + }, { "type": "console.navigation/href", "properties": { diff --git a/frontend/packages/console-app/package.json b/frontend/packages/console-app/package.json index 44fb122b930..9d1a316b059 100644 --- a/frontend/packages/console-app/package.json +++ b/frontend/packages/console-app/package.json @@ -77,6 +77,7 @@ "consolePluginBackendDetail": "src/components/console-operator/ConsolePluginBackendDetail.tsx", "consolePluginProxyDetail": "src/components/console-operator/ConsolePluginProxyDetail.tsx", "getConsoleOperatorConfigFlag": "src/hooks/useCanGetConsoleOperatorConfig.ts", + "useTechPreviewFlagProvider": "src/hooks/useTechPreviewFlagProvider.ts", "usePerspectivesAvailable": "src/components/user-preferences/perspective/usePerspectivesAvailable.ts", "defaultProvider": "src/actions/providers/default-provider.ts", "OperatorStatus": "src/components/dashboards-page/OperatorStatus.tsx", diff --git a/frontend/packages/console-app/src/consts.ts b/frontend/packages/console-app/src/consts.ts index f7d5aabf7e5..f452b376559 100644 --- a/frontend/packages/console-app/src/consts.ts +++ b/frontend/packages/console-app/src/consts.ts @@ -8,6 +8,7 @@ export const FLAG_DEVELOPER_PERSPECTIVE = 'DEVELOPER_PERSPECTIVE'; export const ACM_PERSPECTIVE_ID = 'acm'; export const ADMIN_PERSPECTIVE_ID = 'admin'; export const FLAG_CAN_GET_CONSOLE_OPERATOR_CONFIG = 'CAN_GET_CONSOLE_OPERATOR_CONFIG'; +export const FLAG_TECH_PREVIEW = 'TECH_PREVIEW'; export const FAVORITES_CONFIG_MAP_KEY = 'console.favorites'; export const FAVORITES_LOCAL_STORAGE_KEY = `${STORAGE_PREFIX}/favorites`; diff --git a/frontend/packages/console-app/src/hooks/useTechPreviewFlagProvider.ts b/frontend/packages/console-app/src/hooks/useTechPreviewFlagProvider.ts new file mode 100644 index 00000000000..da0222acf50 --- /dev/null +++ b/frontend/packages/console-app/src/hooks/useTechPreviewFlagProvider.ts @@ -0,0 +1,9 @@ +import { SetFeatureFlag } from '@console/dynamic-plugin-sdk/src/extensions/feature-flags'; +import { FLAG_TECH_PREVIEW } from '../consts'; + +type UseTechPreviewFlagProvider = (setFeatureFlag: SetFeatureFlag) => void; +const useTechPreviewFlagProvider: UseTechPreviewFlagProvider = (setFeatureFlag) => { + setFeatureFlag(FLAG_TECH_PREVIEW, !!window.SERVER_FLAGS.techPreview); +}; + +export default useTechPreviewFlagProvider; diff --git a/frontend/packages/console-shared/src/components/catalog/CatalogController.tsx b/frontend/packages/console-shared/src/components/catalog/CatalogController.tsx index 9efab42e95e..ae590d0c651 100644 --- a/frontend/packages/console-shared/src/components/catalog/CatalogController.tsx +++ b/frontend/packages/console-shared/src/components/catalog/CatalogController.tsx @@ -2,13 +2,17 @@ import * as React from 'react'; import * as _ from 'lodash'; import { useTranslation } from 'react-i18next'; import { useLocation } from 'react-router-dom-v5-compat'; +import { FLAG_TECH_PREVIEW } from '@console/app/src/consts'; import { ResolvedExtension, CatalogItemType, CatalogCategory } from '@console/dynamic-plugin-sdk'; import { CatalogItem } from '@console/dynamic-plugin-sdk/src/extensions'; import { removeQueryArgument, setQueryArgument } from '@console/internal/components/utils/router'; import { skeletonCatalog } from '@console/internal/components/utils/skeleton-catalog'; import { StatusBox } from '@console/internal/components/utils/status-box'; +import OLMv1Alert from '@console/operator-lifecycle-manager-v1/src/components/OLMv1Alert'; +import { FLAG_OLMV1_ENABLED } from '@console/operator-lifecycle-manager-v1/src/const'; import { DocumentTitle } from '@console/shared/src/components/document-title/DocumentTitle'; import { PageHeading } from '@console/shared/src/components/heading/PageHeading'; +import { useFlag } from '../../hooks/flag'; import { useQueryParams } from '../../hooks/useQueryParams'; import PageBody from '../layout/PageBody'; import CatalogView from './catalog-view/CatalogView'; @@ -50,6 +54,11 @@ const CatalogController: React.FC = ({ const { pathname } = useLocation(); const queryParams = useQueryParams(); const [disabledSubCatalogs] = useGetAllDisabledSubCatalogs(); + const techPreviewEnabled = useFlag(FLAG_TECH_PREVIEW); + const olmv1Enabled = useFlag(FLAG_OLMV1_ENABLED); + + // TODO(CONSOLE-4823): Remove this hard-coded alert when OLMv1 GAs + const showOLMv1Alert = techPreviewEnabled && olmv1Enabled && type === 'operator'; const typeExtension: ResolvedExtension = React.useMemo( () => catalogExtensions?.find((extension) => extension.properties.type === type), @@ -183,6 +192,12 @@ const CatalogController: React.FC = ({ breadcrumbs={type ? breadcrumbs : null} helpText={getCatalogTypeDescription()} /> + {/* TODO(CONSOLE-4823): Remove this hard-coded alert when OLMv1 GAs */} + {showOLMv1Alert && ( +
+ +
+ )} = ({ selectedCategoryID: string, toplevelCategory: boolean, ) => { - if (!categorizedIds[category.id]) return null; + if (!category || !categorizedIds[category.id]) return null; const { id, label, subcategories } = category; const active = id === selectedCategory; diff --git a/frontend/packages/console-shared/src/components/catalog/catalog-view/CatalogToolbar.tsx b/frontend/packages/console-shared/src/components/catalog/catalog-view/CatalogToolbar.tsx index bf94ff2cb5d..60ccfb0df9a 100644 --- a/frontend/packages/console-shared/src/components/catalog/catalog-view/CatalogToolbar.tsx +++ b/frontend/packages/console-shared/src/components/catalog/catalog-view/CatalogToolbar.tsx @@ -1,9 +1,12 @@ -import { forwardRef } from 'react'; +import { forwardRef, Suspense } from 'react'; import { Flex, FlexItem, SearchInput } from '@patternfly/react-core'; import * as _ from 'lodash'; import { useTranslation } from 'react-i18next'; +import { FLAG_TECH_PREVIEW } from '@console/app/src/consts'; import { ConsoleSelect } from '@console/internal/components/utils/console-select'; -import { useDebounceCallback } from '@console/shared/src/hooks/debounce'; +// TODO(CONSOLE-4823): Remove this hard-coded component when OLMv1 GAs +import { OLMv1Switch } from '@console/operator-lifecycle-manager-v1/src/components/OLMv1Switch'; +import { useDebounceCallback, useFlag } from '@console/shared/src/hooks'; import { NO_GROUPING } from '../utils/category-utils'; import { CatalogSortOrder, CatalogStringMap } from '../utils/types'; import CatalogPageHeader from './CatalogPageHeader'; @@ -18,6 +21,7 @@ type CatalogToolbarProps = { sortOrder: CatalogSortOrder; groupings: CatalogStringMap; activeGrouping: string; + catalogType?: string; onGroupingChange: (grouping: string) => void; onSearchKeywordChange: (searchKeyword: string) => void; onSortOrderChange: (sortOrder: CatalogSortOrder) => void; @@ -32,6 +36,7 @@ const CatalogToolbar = forwardRef( sortOrder, groupings, activeGrouping, + catalogType, onGroupingChange, onSearchKeywordChange, onSortOrderChange, @@ -39,6 +44,10 @@ const CatalogToolbar = forwardRef( inputRef, ) => { const { t } = useTranslation(); + const techPreviewEnabled = useFlag(FLAG_TECH_PREVIEW); + + // TODO(CONSOLE-4823): Remove this hard-coded toggle when OLMv1 GAs + const showOLMv1Toggle = techPreviewEnabled && catalogType === 'operator'; const catalogSortItems = { [CatalogSortOrder.RELEVANCE]: t('console-shared~Relevance'), @@ -94,6 +103,14 @@ const CatalogToolbar = forwardRef( /> )} + {/* TODO(CONSOLE-4823): Remove this hard-coded toggle when OLMv1 GAs */} + {showOLMv1Toggle && ( + + + + + + )} {t('console-shared~{{totalItems}} items', { totalItems })} diff --git a/frontend/packages/console-shared/src/components/catalog/catalog-view/CatalogView.tsx b/frontend/packages/console-shared/src/components/catalog/catalog-view/CatalogView.tsx index 1e396723a76..983501fa8c4 100644 --- a/frontend/packages/console-shared/src/components/catalog/catalog-view/CatalogView.tsx +++ b/frontend/packages/console-shared/src/components/catalog/catalog-view/CatalogView.tsx @@ -118,7 +118,7 @@ const CatalogView: React.FC = ({ } }, [catalogType, items.length]); - const handleCategoryChange = (categoryId) => { + const handleCategoryChange = (categoryId: string) => { updateURLParams(CatalogQueryParams.CATEGORY, categoryId); }; @@ -154,8 +154,8 @@ const CatalogView: React.FC = ({ const allCategory = { id: ALL_CATEGORY, label: t('console-shared~All items') }; const otherCategory = { id: OTHER_CATEGORY, label: t('console-shared~Other') }; const sortedCategories = (categories ?? []) - .filter((cat) => cat.id !== ALL_CATEGORY && cat.id !== OTHER_CATEGORY) - .sort((a, b) => a.label.localeCompare(b.label)); + .filter((cat) => cat && cat.id !== ALL_CATEGORY && cat.id !== OTHER_CATEGORY) + .sort((a, b) => (a.label ?? '').localeCompare(b.label ?? '') ?? 0); return [allCategory, ...sortedCategories, otherCategory]; }, [categories, t]); @@ -341,6 +341,7 @@ const CatalogView: React.FC = ({ sortOrder={sortOrder} groupings={groupings} activeGrouping={activeGrouping} + catalogType={catalogType} onGroupingChange={handleGroupingChange} onSortOrderChange={handleSortOrderChange} onSearchKeywordChange={handleSearchKeywordChange} diff --git a/frontend/packages/console-shared/src/components/catalog/service/CatalogServiceProvider.tsx b/frontend/packages/console-shared/src/components/catalog/service/CatalogServiceProvider.tsx index b3348f38e24..e1cc7adfad6 100644 --- a/frontend/packages/console-shared/src/components/catalog/service/CatalogServiceProvider.tsx +++ b/frontend/packages/console-shared/src/components/catalog/service/CatalogServiceProvider.tsx @@ -103,8 +103,13 @@ const CatalogServiceProvider: React.FC = ({ return applyCatalogItemMetadata(preCatalogItems, metadataProviderMap); }, [loaded, preCatalogItems, metadataProviderMap]); - const onCategoryValueResolved = React.useCallback((categories, id) => { - setCategoryProviderMap((prev) => ({ ...prev, [id]: categories })); + const onCategoryValueResolved = React.useCallback((newCategories, id) => { + setCategoryProviderMap((prev) => { + if (_.isEqual(prev[id], newCategories)) { + return prev; + } + return { ...prev, [id]: newCategories }; + }); }, []); const onValueResolved = React.useCallback((items, uid) => { @@ -135,6 +140,9 @@ const CatalogServiceProvider: React.FC = ({ result[e.properties.type] = []; }); catalogItems.forEach((item) => { + if (!result[item.type]) { + result[item.type] = []; + } result[item.type].push(item); }); return result; @@ -157,9 +165,10 @@ const CatalogServiceProvider: React.FC = ({ ? new Error('failed loading catalog data') : new IncompleteDataError(failedExtensions); - const categories = React.useMemo(() => _.flatten(Object.values(categoryProviderMap)), [ - categoryProviderMap, - ]); + const categories = React.useMemo( + () => _.uniqBy(_.flatten(Object.values(categoryProviderMap)), 'id'), + [categoryProviderMap], + ); const catalogService: CatalogService = { type: catalogType, diff --git a/frontend/packages/console-shared/src/components/catalog/utils/category-utils.ts b/frontend/packages/console-shared/src/components/catalog/utils/category-utils.ts index cb68b0f6e4b..f4ad8949c4d 100644 --- a/frontend/packages/console-shared/src/components/catalog/utils/category-utils.ts +++ b/frontend/packages/console-shared/src/components/catalog/utils/category-utils.ts @@ -9,8 +9,8 @@ export const matchSubcategories = ( category: CatalogCategory, item: CatalogItem, ): (CatalogCategory | CatalogSubcategory)[] => { - if (!category.subcategories) { - if (!category.tags) { + if (!category?.subcategories) { + if (!category?.tags) { return []; } diff --git a/frontend/packages/console-shared/src/components/query-browser/QueryBrowser.tsx b/frontend/packages/console-shared/src/components/query-browser/QueryBrowser.tsx index f3f955b4533..56df742fbbb 100644 --- a/frontend/packages/console-shared/src/components/query-browser/QueryBrowser.tsx +++ b/frontend/packages/console-shared/src/components/query-browser/QueryBrowser.tsx @@ -60,12 +60,12 @@ import { timeFormatter, timeFormatterWithSeconds, } from '@console/internal/components/utils/datetime'; -import { usePoll } from '@console/internal/components/utils/poll-hook'; import { useRefWidth } from '@console/internal/components/utils/ref-width-hook'; import { useSafeFetch } from '@console/internal/components/utils/safe-fetch-hook'; import { LoadingInline } from '@console/internal/components/utils/status-box'; import { humanizeNumberSI } from '@console/internal/components/utils/units'; import { RootState } from '@console/internal/redux'; +import { usePoll } from '../../hooks/usePoll'; import withFallback from '../error/fallbacks/withFallback'; import { queryBrowserTheme } from './theme'; diff --git a/frontend/public/components/utils/poll-hook.ts b/frontend/packages/console-shared/src/hooks/usePoll.ts similarity index 97% rename from frontend/public/components/utils/poll-hook.ts rename to frontend/packages/console-shared/src/hooks/usePoll.ts index 1a741073fe4..ba077d3f5d4 100644 --- a/frontend/public/components/utils/poll-hook.ts +++ b/frontend/packages/console-shared/src/hooks/usePoll.ts @@ -21,6 +21,7 @@ export const usePoll = (callback, delay, ...dependencies) => { const id = setInterval(tick, delay); return () => clearInterval(id); } + return () => {}; // eslint-disable-next-line react-hooks/exhaustive-deps }, [delay, ...dependencies]); }; diff --git a/frontend/packages/helm-plugin/src/providers/helm-detection-provider.ts b/frontend/packages/helm-plugin/src/providers/helm-detection-provider.ts index 714ec8f3c76..9268c7c4cbf 100644 --- a/frontend/packages/helm-plugin/src/providers/helm-detection-provider.ts +++ b/frontend/packages/helm-plugin/src/providers/helm-detection-provider.ts @@ -1,10 +1,10 @@ import { useState, useCallback } from 'react'; import { SetFeatureFlag } from '@console/dynamic-plugin-sdk'; import { settleAllPromises } from '@console/dynamic-plugin-sdk/src/utils/promise'; -import { usePoll } from '@console/internal/components/utils/poll-hook'; import { fetchK8s } from '@console/internal/graphql/client'; import { K8sResourceKind, ListKind } from '@console/internal/module/k8s'; import { useActiveNamespace } from '@console/shared/src/hooks/useActiveNamespace'; +import { usePoll } from '@console/shared/src/hooks/usePoll'; import { FLAG_OPENSHIFT_HELM } from '../const'; import { HelmChartRepositoryModel, ProjectHelmChartRepositoryModel } from '../models'; diff --git a/frontend/packages/operator-lifecycle-manager-v1/README.md b/frontend/packages/operator-lifecycle-manager-v1/README.md new file mode 100644 index 00000000000..a72fc769808 --- /dev/null +++ b/frontend/packages/operator-lifecycle-manager-v1/README.md @@ -0,0 +1,47 @@ +# OLMv1 Package + +This package provides components and utilities for OLMv1 (Operator Lifecycle Manager v1) catalog functionality. + +## Feature Flag + +The package exports a `FLAG_OLMV1_ENABLED` feature flag that mirrors the user's OLMv1 toggle switch setting. + +### Usage Example + +```typescript +import { useFlag } from '@console/shared/src/hooks/flag'; +import { FLAG_OLMV1_ENABLED } from '@console/operator-lifecycle-manager-v1/src/const'; + +const MyComponent = () => { + const olmv1Enabled = useFlag(FLAG_OLMV1_ENABLED); + + return ( +
+ {olmv1Enabled && } +
+ ); +}; +``` + +### In Console Extensions + +You can also use the flag in `console-extensions.json`: + +```json +{ + "type": "console.some-extension-type", + "properties": { + ... + }, + "flags": { + "required": ["OLMV1_ENABLED"] + } +} +``` + +This flag is automatically synchronized with the user setting `console.olmv1.enabled` which can be controlled through: + +1. **User Preferences**: Navigate to User Preferences → Operators → Enable OLMv1 catalog +2. **Catalog Toolbar Toggle**: Use the toggle switch in the Developer Catalog when viewing operators + +Both controls are synchronized and modify the same user setting, ensuring a consistent experience across the console. diff --git a/frontend/packages/operator-lifecycle-manager-v1/console-extensions.json b/frontend/packages/operator-lifecycle-manager-v1/console-extensions.json index cc9af5d286b..62b656a329d 100644 --- a/frontend/packages/operator-lifecycle-manager-v1/console-extensions.json +++ b/frontend/packages/operator-lifecycle-manager-v1/console-extensions.json @@ -1,4 +1,15 @@ [ + { + "type": "console.flag/hookProvider", + "properties": { + "handler": { + "$codeRef": "useOLMv1FlagProvider" + } + }, + "flags": { + "required": ["TECH_PREVIEW"] + } + }, { "type": "console.flag/model", "properties": { @@ -21,37 +32,12 @@ "flag": "CLUSTER_EXTENSION_API" } }, - { - "type": "console.navigation/href", - "properties": { - "id": "extension-catalog", - "perspective": "admin", - "section": "ecosystem", - "name": "%olm-v1~Extension Catalog%", - "href": "/ecosystem/catalog" - }, - "flags": { - "required": ["CLUSTER_CATALOG_API", "FALSE"] // TODO re-enable - } - }, - { - "type": "console.page/route", - "properties": { - "path": "/ecosystem/catalog", - "component": { - "$codeRef": "ExtensionCatalog" - } - }, - "flags": { - "required": ["CLUSTER_CATALOG_API", "FALSE"] // TODO re-enable - } - }, { "type": "console.navigation/resource-cluster", "properties": { "id": "installed-extensions", "section": "ecosystem", - "name": "%olm-v1~Installed Extensions%", + "name": "%olm-v1~Installed Operators%", "model": { "kind": "ClusterExtension", "version": "v1", @@ -60,83 +46,60 @@ "startsWith": ["olm.operatorframework.io"] }, "flags": { - "required": ["CLUSTER_EXTENSION_API", "FALSE"] // TODO re-enable + "required": ["CLUSTER_EXTENSION_API", "TECH_PREVIEW", "OLMV1_ENABLED"] } }, { - "type": "console.context-provider", + "type": "console.catalog/item-provider", "properties": { - "provider": { "$codeRef": "ExtensionCatalogDatabaseContextProvider" }, - "useValueHook": { "$codeRef": "useExtensionCatalogDatabaseContextValues" } + "catalogId": "dev-catalog", + "type": "operator", + "title": "%olm-v1~Operators%", + "provider": { "$codeRef": "useCatalogItems" } }, "flags": { - "required": ["CLUSTER_CATALOG_API", "FALSE"] // TODO re-enable + "required": ["CLUSTER_CATALOG_API", "TECH_PREVIEW", "OLMV1_ENABLED"] } }, { - "type": "console.catalog/item-provider", + "type": "console.catalog/categories-provider", "properties": { - "catalogId": "olm-extension-catalog", - "type": "ExtensionCatalogItem", - "provider": { "$codeRef": "useExtensionCatalogItems" } + "catalogId": "dev-catalog", + "type": "operator", + "provider": { "$codeRef": "useCatalogCategories" } }, "flags": { - "required": ["CLUSTER_CATALOG_API", "FALSE"] // TODO re-enable + "required": ["CLUSTER_CATALOG_API", "TECH_PREVIEW", "OLMV1_ENABLED"] } }, { - "type": "console.catalog/item-type", + "type": "console.user-preference/group", "properties": { - "type": "ExtensionCatalogItem", - "title": "%olm-v1~Extension Catalog Items%", - "filters": [ - { - "label": "%olm-v1~Source%", - "attribute": "source", - "comparator": { - "$codeRef": "filters.sourceComparator" - } - }, - { - "label": "%olm-v1~Provider%", - "attribute": "provider", - "comparator": { - "$codeRef": "filters.providerComparator" - } - }, - { - "label": "%olm-v1~Capability level%", - "attribute": "capabilities", - "comparator": { - "$codeRef": "filters.capabilityLevelComparator" - } - }, - { - "label": "%olm-v1~Infrastructure features%", - "attribute": "infrastructureFeatures", - "comparator": { - "$codeRef": "filters.infrastructureFeatureComparator" - } - }, - { - "label": "%olm-v1~Valid subscription%", - "attribute": "validSubscription", - "comparator": { - "$codeRef": "filters.validSubscriptionComparator" - } - } - ] + "id": "olmv1-catalog", + "label": "%olm-v1~OLMv1 Catalog%" }, "flags": { - "required": ["CLUSTER_CATALOG_API", "FALSE"] // TODO re-enable + "required": ["TECH_PREVIEW"] } }, { - "type": "console.catalog/categories-provider", + "type": "console.user-preference/item", "properties": { - "catalogId": "olm-extension-catalog", - "type": "ExtensionCatalogItem", - "provider": { "$codeRef": "useExtensionCatalogCategories" } + "id": "console.olmv1.enabled", + "label": "%olm-v1~OLMv1 Catalog%", + "groupId": "olmv1-catalog", + "description": "%olm-v1~Enable the OLMv1 catalog experience. OLMv1 is a technology preview feature that provides an updated operator catalog interface.%", + "field": { + "type": "checkbox", + "userSettingsKey": "console.olmv1.enabled", + "label": "%olm-v1~Enable OLMv1 catalog%", + "trueValue": true, + "falseValue": false, + "defaultValue": false + } + }, + "flags": { + "required": ["TECH_PREVIEW"] } } ] diff --git a/frontend/packages/operator-lifecycle-manager-v1/locales/en/olm-v1.json b/frontend/packages/operator-lifecycle-manager-v1/locales/en/olm-v1.json index 36f8bd84fe8..42fd0780b93 100644 --- a/frontend/packages/operator-lifecycle-manager-v1/locales/en/olm-v1.json +++ b/frontend/packages/operator-lifecycle-manager-v1/locales/en/olm-v1.json @@ -1,11 +1,15 @@ { - "Extension Catalog": "Extension Catalog", - "Installed Extensions": "Installed Extensions", - "Extension Catalog Items": "Extension Catalog Items", - "Source": "Source", - "Provider": "Provider", - "Capability level": "Capability level", - "Infrastructure features": "Infrastructure features", - "Valid subscription": "Valid subscription", - "Discover Operators from the Kubernetes community and Red Hat partners, curated by Red Hat. You can install Operators on your clusters to provide optional add-ons and shared services to your developers.": "Discover Operators from the Kubernetes community and Red Hat partners, curated by Red Hat. You can install Operators on your clusters to provide optional add-ons and shared services to your developers." + "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", + "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", + "Tech Preview": "Tech Preview", + "OLMv1 information": "OLMv1 information" } \ No newline at end of file diff --git a/frontend/packages/operator-lifecycle-manager-v1/package.json b/frontend/packages/operator-lifecycle-manager-v1/package.json index 27624705173..7d64aceeac3 100644 --- a/frontend/packages/operator-lifecycle-manager-v1/package.json +++ b/frontend/packages/operator-lifecycle-manager-v1/package.json @@ -6,12 +6,9 @@ "consolePlugin": { "entry": "src/plugin.ts", "exposedModules": { - "ExtensionCatalog": "src/components/ExtensionCatalog.tsx", - "useExtensionCatalogDatabaseContextValues": "src/contexts/useExtensionCatalogDatabaseContextValues.ts", - "ExtensionCatalogDatabaseContextProvider": "src/contexts/ExtensionCatalogDatabaseContext.ts", - "useExtensionCatalogItems": "src/hooks/useExtensionCatalogItems.ts", - "useExtensionCatalogCategories": "src/hooks/useExtensionCatalogCategories.ts", - "filters": "src/utils/filters.ts" + "useCatalogItems": "src/hooks/useCatalogItems.ts", + "useCatalogCategories": "src/hooks/useCatalogCategories.ts", + "useOLMv1FlagProvider": "src/hooks/useOLMv1FlagProvider.ts" } } } diff --git a/frontend/packages/operator-lifecycle-manager-v1/src/components/ExtensionCatalog.tsx b/frontend/packages/operator-lifecycle-manager-v1/src/components/ExtensionCatalog.tsx deleted file mode 100644 index e9fc918080c..00000000000 --- a/frontend/packages/operator-lifecycle-manager-v1/src/components/ExtensionCatalog.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { Trans, useTranslation } from 'react-i18next'; -import { CatalogController, CatalogServiceProvider } from '@console/shared/src/components/catalog'; -import { useActiveNamespace } from '@console/shared/src/hooks/useActiveNamespace'; - -const ExtensionCatalog = () => { - const { t } = useTranslation('olm-v1'); - const [namespace] = useActiveNamespace(); - return ( - - {(service) => ( - - Discover Operators from the Kubernetes community and Red Hat partners, curated by Red - Hat. You can install Operators on your clusters to provide optional add-ons and shared - services to your developers. - - } - /> - )} - - ); -}; - -export default ExtensionCatalog; diff --git a/frontend/packages/operator-lifecycle-manager-v1/src/components/OLMv1Alert.tsx b/frontend/packages/operator-lifecycle-manager-v1/src/components/OLMv1Alert.tsx new file mode 100644 index 00000000000..b60f724322d --- /dev/null +++ b/frontend/packages/operator-lifecycle-manager-v1/src/components/OLMv1Alert.tsx @@ -0,0 +1,24 @@ +import { Alert } from '@patternfly/react-core'; +import { useTranslation } from 'react-i18next'; +import { ExternalLink } from '@console/shared/src/components/links/ExternalLink'; + +const OLMv1Alert = () => { + const { t } = useTranslation('olm-v1'); + return ( + + {t('Learn more about OLMv1')} + + } + > + {t( + "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.", + )} + + ); +}; + +export default OLMv1Alert; diff --git a/frontend/packages/operator-lifecycle-manager-v1/src/components/OLMv1Switch.tsx b/frontend/packages/operator-lifecycle-manager-v1/src/components/OLMv1Switch.tsx new file mode 100644 index 00000000000..f41d605493a --- /dev/null +++ b/frontend/packages/operator-lifecycle-manager-v1/src/components/OLMv1Switch.tsx @@ -0,0 +1,66 @@ +import { useCallback, 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 { 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'; + +/** + * Toolbar component for toggling OLMv1 UI visibility in the operator catalog. + * Uses user settings to persist the toggle state. + */ +export const OLMv1Switch: React.FC = () => { + const { t } = useTranslation(); + const techPreviewEnabled = useFlag(FLAG_TECH_PREVIEW); + const [olmv1Enabled, setOlmv1Enabled] = useUserSettings( + OLMV1_ENABLED_USER_SETTING_KEY, + techPreviewEnabled ?? false, + true, + ); + + const handleToggle = useCallback( + (_event: FormEvent, checked: boolean) => { + setOlmv1Enabled(checked); + }, + [setOlmv1Enabled], + ); + + const popoverContent = ( +
+ {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.', + )} +
+ ); + + return ( + + + + + + + + + +