diff --git a/frontend/packages/console-dynamic-plugin-sdk/docs/console-extensions.md b/frontend/packages/console-dynamic-plugin-sdk/docs/console-extensions.md index 6763cca0492..6c26bbdeaba 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/docs/console-extensions.md +++ b/frontend/packages/console-dynamic-plugin-sdk/docs/console-extensions.md @@ -1158,7 +1158,7 @@ Topology Data Model Factory Extension | ---- | ---------- | -------- | ----------- | | `id` | `string` | no | Unique ID for the factory. | | `priority` | `number` | no | Priority for the factory | -| `resources` | `WatchK8sResourcesGeneric` | yes | Resources to be fetched from useK8sWatchResources hook. | +| `resources` | `WatchK8sResourcesGeneric \| CodeRef<() => Promise>` | yes | Resources to be fetched from useK8sWatchResources hook. | | `workloadKeys` | `string[]` | yes | Keys in resources containing workloads. | | `getDataModel` | `CodeRef` | yes | Getter for the data model factory | | `isResourceDepicted` | `CodeRef` | yes | Getter for function to determine if a resource is depicted by this model factory | diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/extensions/topology.ts b/frontend/packages/console-dynamic-plugin-sdk/src/extensions/topology.ts index b395c5e0737..f930df6937b 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/extensions/topology.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/extensions/topology.ts @@ -41,7 +41,7 @@ export type TopologyDataModelFactory = ExtensionDeclaration< /** Priority for the factory */ priority: number; /** Resources to be fetched from useK8sWatchResources hook. */ - resources?: WatchK8sResourcesGeneric; + resources?: WatchK8sResourcesGeneric | CodeRef<() => Promise>; /** Keys in resources containing workloads. */ workloadKeys?: string[]; /** Getter for the data model factory */ diff --git a/frontend/packages/knative-plugin/console-extensions.json b/frontend/packages/knative-plugin/console-extensions.json index f50077e223f..5efedae81dd 100644 --- a/frontend/packages/knative-plugin/console-extensions.json +++ b/frontend/packages/knative-plugin/console-extensions.json @@ -2008,5 +2008,105 @@ "flags": { "required": ["KNATIVE_EVENTING"] } + }, + { + "type": "console.topology/data/factory", + "properties": { + "id": "knative-serving-topology-model-factory", + "priority": 100, + "resources": { + "revisions": { + "model": { "group": "serving.knative.dev", "version": "v1", "kind": "Revision" }, + "opts": { "isList": true, "optional": true, "namespaced": true } + }, + "configurations": { + "model": { "group": "serving.knative.dev", "version": "v1", "kind": "Configuration" }, + "opts": { "isList": true, "optional": true, "namespaced": true } + }, + "ksroutes": { + "model": { "group": "serving.knative.dev", "version": "v1", "kind": "Route" }, + "opts": { "isList": true, "optional": true, "namespaced": true } + }, + "ksservices": { + "model": { "group": "serving.knative.dev", "version": "v1", "kind": "Service" }, + "opts": { "isList": true, "optional": true, "namespaced": true } + }, + "domainmappings": { + "model": { "group": "serving.knative.dev", "version": "v1beta1", "kind": "DomainMapping" }, + "opts": { "isList": true, "optional": true, "namespaced": true } + } + }, + "workloadKeys": ["ksservices"], + "getDataModel": { + "$codeRef": "dataTransformer.getKnativeServingTopologyDataModel" + }, + "isResourceDepicted": { + "$codeRef": "isKnativeResource.isKnativeResource" + } + }, + "flags": { + "required": [ + "KNATIVE_SERVING_CONFIGURATION", + "KNATIVE_SERVING", + "KNATIVE_SERVING_REVISION", + "KNATIVE_SERVING_ROUTE", + "KNATIVE_SERVING_SERVICE" + ] + } + }, + { + "type": "console.topology/data/factory", + "properties": { + "id": "knative-eventing-topology-model-factory", + "priority": 100, + "resources": { + "$codeRef": "getKnativeResources.getKnativeEventingResources" + }, + "workloadKeys": ["eventingsubscription"], + "getDataModel": { + "$codeRef": "dataTransformer.getKnativeEventingTopologyDataModel" + }, + "isResourceDepicted": { + "$codeRef": "isKnativeResource.isKnativeResource" + } + }, + "flags": { + "required": ["KNATIVE_EVENTING", "FLAG_KNATIVE_EVENTING_ENABLED"] + } + }, + { + "type": "console.topology/data/factory", + "properties": { + "id": "knative-kamelets-topology-model-factory", + "priority": 100, + "resources": { + "integrations": { + "model": { "group": "camel.apache.org", "version": "v1", "kind": "Integration" }, + "opts": { "isList": true, "optional": true, "namespaced": true } + }, + "kameletbindings": { + "model": { "group": "camel.apache.org", "version": "v1alpha1", "kind": "KameletBinding" }, + "opts": { "isList": true, "optional": true, "namespaced": true } + }, + "domainmappings": { + "model": { "group": "serving.knative.dev", "version": "v1beta1", "kind": "DomainMapping" }, + "opts": { "isList": true, "optional": true, "namespaced": true } + }, + "kamelets": { + "model": { "group": "camel.apache.org", "version": "v1alpha1", "kind": "Kamelet" }, + "opts": { "isList": true, "optional": true, "namespaced": true } + } + }, + "workloadKeys": ["kameletbindings"], + "getDataModel": { + "$codeRef": "dataTransformer.getKnativeKameletsTopologyDataModel" + }, + "isResourceDepicted": { + "$codeRef": "isKnativeResource.isKnativeResource" + } + }, + "flags": { + "required": ["KNATIVE_EVENTING", "FLAG_CAMEL_KAMELETS"] + } } ] diff --git a/frontend/packages/knative-plugin/package.json b/frontend/packages/knative-plugin/package.json index 03ddc3edbec..3efe003e95a 100644 --- a/frontend/packages/knative-plugin/package.json +++ b/frontend/packages/knative-plugin/package.json @@ -14,7 +14,7 @@ "@console/topology": "0.0.0-fixed" }, "consolePlugin": { - "entry": "src/plugin.tsx", + "entry": "src/plugin.ts", "exposedModules": { "actions": "src/actions/providers.ts", "icons": "src/utils/icons.tsx", @@ -65,7 +65,9 @@ "fetchDynamicEventSourcesUtils": "src/utils/fetch-dynamic-eventsources-utils.ts", "EventSourceBreadcrumbs": "src/providers/useEventSourceDetailPageBreadcrumbs.ts", "ChannelBreadcrumbs": "src/providers/useChannelDetailPageBreadcrumbs.ts", - "BrokerBreadcrumbs": "src/providers/useBrokerDetailPageBreadcrumbs.ts" + "BrokerBreadcrumbs": "src/providers/useBrokerDetailPageBreadcrumbs.ts", + "getKnativeResources": "src/utils/get-knative-resources.ts", + "isKnativeResource": "src/topology/isKnativeResource.ts" } } } diff --git a/frontend/packages/knative-plugin/src/plugin.ts b/frontend/packages/knative-plugin/src/plugin.ts new file mode 100644 index 00000000000..ef8d3ac4f8a --- /dev/null +++ b/frontend/packages/knative-plugin/src/plugin.ts @@ -0,0 +1,2 @@ +// See console-extensions.json instead +export default []; diff --git a/frontend/packages/knative-plugin/src/plugin.tsx b/frontend/packages/knative-plugin/src/plugin.tsx deleted file mode 100644 index 0967881f109..00000000000 --- a/frontend/packages/knative-plugin/src/plugin.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import { topologyPlugin } from './topology/topology-plugin'; - -export default topologyPlugin; diff --git a/frontend/packages/knative-plugin/src/topology/topology-plugin.ts b/frontend/packages/knative-plugin/src/topology/topology-plugin.ts deleted file mode 100644 index 9cfe6fe4be2..00000000000 --- a/frontend/packages/knative-plugin/src/topology/topology-plugin.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { applyCodeRefSymbol } from '@console/dynamic-plugin-sdk/src/coderefs/coderef-resolver'; -import { Plugin } from '@console/plugin-sdk'; -import { TopologyDataModelFactory } from '@console/topology/src/extensions'; -import { - FLAG_CAMEL_KAMELETS, - FLAG_KNATIVE_EVENTING, - FLAG_KNATIVE_EVENTING_ENABLED, - FLAG_KNATIVE_SERVING, - FLAG_KNATIVE_SERVING_CONFIGURATION, - FLAG_KNATIVE_SERVING_REVISION, - FLAG_KNATIVE_SERVING_ROUTE, - FLAG_KNATIVE_SERVING_SERVICE, -} from '../const'; -import { - getKnativeEventingKameletsResources, - getKnativeEventingResources, - getKnativeServingResources, -} from '../utils/get-knative-resources'; - -const getKnativeServingTopologyDataModel = () => - import('./data-transformer' /* webpackChunkName: "serving-components" */).then( - (m) => m.getKnativeServingTopologyDataModel, - ); - -const getKnativeEventingTopologyDataModel = () => - import('./data-transformer' /* webpackChunkName: "eventing-components" */).then( - (m) => m.getKnativeEventingTopologyDataModel, - ); - -const getKnativeKameletsTopologyDataModel = () => - import('./data-transformer' /* webpackChunkName: "kamelets-components" */).then( - (m) => m.getKnativeKameletsTopologyDataModel, - ); - -const getIsKnativeResource = () => - import('./isKnativeResource' /* webpackChunkName: "knative-components" */).then( - (m) => m.isKnativeResource, - ); - -export type TopologyConsumedExtensions = TopologyDataModelFactory; - -export const topologyPlugin: Plugin = [ - { - type: 'Topology/DataModelFactory', - properties: { - id: 'knative-serving-topology-model-factory', - priority: 100, - resources: getKnativeServingResources, - workloadKeys: ['ksservices'], - getDataModel: applyCodeRefSymbol(getKnativeServingTopologyDataModel), - isResourceDepicted: applyCodeRefSymbol(getIsKnativeResource), - }, - flags: { - required: [ - FLAG_KNATIVE_SERVING_CONFIGURATION, - FLAG_KNATIVE_SERVING, - FLAG_KNATIVE_SERVING_REVISION, - FLAG_KNATIVE_SERVING_ROUTE, - FLAG_KNATIVE_SERVING_SERVICE, - ], - }, - }, - { - type: 'Topology/DataModelFactory', - properties: { - id: 'knative-eventing-topology-model-factory', - priority: 100, - resources: getKnativeEventingResources, - workloadKeys: ['eventingsubscription'], - getDataModel: applyCodeRefSymbol(getKnativeEventingTopologyDataModel), - isResourceDepicted: applyCodeRefSymbol(getIsKnativeResource), - }, - flags: { - required: [FLAG_KNATIVE_EVENTING, FLAG_KNATIVE_EVENTING_ENABLED], - }, - }, - { - type: 'Topology/DataModelFactory', - properties: { - id: 'knative-kamelets-topology-model-factory', - priority: 100, - resources: getKnativeEventingKameletsResources, - workloadKeys: ['kameletbindings'], - getDataModel: applyCodeRefSymbol(getKnativeKameletsTopologyDataModel), - isResourceDepicted: applyCodeRefSymbol(getIsKnativeResource), - }, - flags: { - required: [FLAG_KNATIVE_EVENTING, FLAG_CAMEL_KAMELETS], - }, - }, -]; diff --git a/frontend/packages/knative-plugin/src/utils/get-knative-resources.ts b/frontend/packages/knative-plugin/src/utils/get-knative-resources.ts index f906d105139..63ccbe0cc37 100644 --- a/frontend/packages/knative-plugin/src/utils/get-knative-resources.ts +++ b/frontend/packages/knative-plugin/src/utils/get-knative-resources.ts @@ -1,5 +1,5 @@ import * as _ from 'lodash'; -import { WatchK8sResources } from '@console/dynamic-plugin-sdk'; +import { WatchK8sResources, WatchK8sResourcesGeneric } from '@console/dynamic-plugin-sdk'; import { FirehoseResource } from '@console/internal/components/utils'; import { K8sResourceKind, PodKind, referenceForModel } from '@console/internal/module/k8s'; import { GLOBAL_OPERATOR_NS, KNATIVE_SERVING_LABEL } from '../const'; @@ -21,10 +21,7 @@ import { KafkaConnectionModel, } from '../models'; import { Traffic } from '../types'; -import { - getDynamicEventSourcesWatchers, - getDynamicEventingChannelWatchers, -} from './fetch-dynamic-eventsources-utils'; +import { fetchEventSourcesCrd, fetchChannelsCrd } from './fetch-dynamic-eventsources-utils'; export type KnativeItem = { revisions?: K8sResourceKind[]; @@ -438,31 +435,43 @@ export const getSinkableResources = (namespace: string): FirehoseResource[] => { : []; }; -export const getKnativeServingResources = (namespace: string) => { - return { - ...knativeServingResourcesRevisionWatchers(namespace), - ...knativeServingResourcesConfigurationsWatchers(namespace), - ...knativeServingResourcesRoutesWatchers(namespace), - ...knativeServingResourcesServicesWatchers(namespace), - ...knativeCamelDomainMappingResourceWatchers(namespace), - }; -}; - -export const getKnativeEventingResources = (namespace: string) => { - return { - ...knativeEventingResourcesSubscriptionWatchers(namespace), - ...getDynamicEventSourcesWatchers(namespace), - ...getDynamicEventingChannelWatchers(namespace), - ...knativeEventingBrokerResourceWatchers(namespace), - ...knativeEventingTriggerResourceWatchers(namespace), - }; -}; +export const getKnativeEventingResources = async (): Promise => { + // Fetch dynamic event sources and channels at runtime + const eventSourceModels = await fetchEventSourcesCrd(); + const eventingChannels = await fetchChannelsCrd(); + + const dynamicEventSources = eventSourceModels.reduce((acc, model) => { + const ref = referenceForModel(model); + acc[ref] = { + model: { group: model.apiGroup, version: model.apiVersion, kind: model.kind }, + opts: { isList: true, optional: true, namespaced: true }, + }; + return acc; + }, {}); + + const dynamicChannels = eventingChannels.reduce((acc, model) => { + const ref = referenceForModel(model); + acc[ref] = { + model: { group: model.apiGroup, version: model.apiVersion, kind: model.kind }, + opts: { isList: true, optional: true, namespaced: true }, + }; + return acc; + }, {}); -export const getKnativeEventingKameletsResources = (namespace: string) => { return { - ...knativeCamelIntegrationsResourceWatchers(namespace), - ...knativeCamelKameletBindingResourceWatchers(namespace), - ...knativeCamelDomainMappingResourceWatchers(namespace), - ...knativeCamelKameletResourceWatchers(namespace), + eventingsubscription: { + model: { group: 'messaging.knative.dev', version: 'v1', kind: 'Subscription' }, + opts: { isList: true, optional: true, namespaced: true }, + }, + brokers: { + model: { group: 'eventing.knative.dev', version: 'v1', kind: 'Broker' }, + opts: { isList: true, optional: true, namespaced: true }, + }, + triggers: { + model: { group: 'eventing.knative.dev', version: 'v1', kind: 'Trigger' }, + opts: { isList: true, optional: true, namespaced: true }, + }, + ...dynamicEventSources, + ...dynamicChannels, }; }; diff --git a/frontend/packages/topology/src/components/dropdowns/ApplicationDropdown.tsx b/frontend/packages/topology/src/components/dropdowns/ApplicationDropdown.tsx index ce9e43a4c9e..c26a8055c88 100644 --- a/frontend/packages/topology/src/components/dropdowns/ApplicationDropdown.tsx +++ b/frontend/packages/topology/src/components/dropdowns/ApplicationDropdown.tsx @@ -1,16 +1,9 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; -import { - isTopologyDataModelFactory as isDynamicTopologyDataModelFactory, - TopologyDataModelFactory as DynamicTopologyDataModelFactory, -} from '@console/dynamic-plugin-sdk'; import { Firehose } from '@console/internal/components/utils'; -import { useExtensions } from '@console/plugin-sdk/src'; import { ResourceDropdown } from '@console/shared'; import { ResourceDropdownProps } from '../../../../console-shared/src/components/dropdown/ResourceDropdown'; -import { getNamespacedDynamicModelFactories } from '../../data-transforms/DataModelProvider'; import { getBaseWatchedResources } from '../../data-transforms/transform-utils'; -import { isTopologyDataModelFactory, TopologyDataModelFactory } from '../../extensions'; type ApplicationDropdownProps = Omit & { namespace?: string; @@ -18,32 +11,16 @@ type ApplicationDropdownProps = Omit = ({ namespace, ...props }) => { const { t } = useTranslation(); - const modelFactories = useExtensions(isTopologyDataModelFactory); - const dynamicModelFactories = useExtensions( - isDynamicTopologyDataModelFactory, - ); - - const namespacedDynamicFactories = React.useMemo( - () => getNamespacedDynamicModelFactories(dynamicModelFactories), - [dynamicModelFactories], - ); const resources = React.useMemo(() => { - let watchedBaseResources = getBaseWatchedResources(namespace); - [...modelFactories, ...namespacedDynamicFactories].forEach((modelFactory) => { - const factoryResources = modelFactory.properties.resources?.(namespace); - if (factoryResources) { - watchedBaseResources = { - ...factoryResources, - ...watchedBaseResources, - }; - } - }); + // Use only base watched resources since dynamic factories are handled separately + // and ApplicationDropdown primarily needs the base resources for application labels + const watchedBaseResources = getBaseWatchedResources(namespace); return Object.keys(watchedBaseResources).map((key) => ({ ...watchedBaseResources[key], prop: key, })); - }, [namespacedDynamicFactories, modelFactories, namespace]); + }, [namespace]); return ( diff --git a/frontend/packages/topology/src/data-transforms/DataModelExtension.tsx b/frontend/packages/topology/src/data-transforms/DataModelExtension.tsx index 0e2e4c7df65..74c35504743 100644 --- a/frontend/packages/topology/src/data-transforms/DataModelExtension.tsx +++ b/frontend/packages/topology/src/data-transforms/DataModelExtension.tsx @@ -1,79 +1,120 @@ -import * as React from 'react'; +import { useContext, useRef, useEffect, useMemo } from 'react'; +import { + WatchK8sResources, + WatchK8sResourcesGeneric, + TopologyDataModelFactory, + ResolvedExtension, +} from '@console/dynamic-plugin-sdk'; +import { + referenceForModel, + modelForGroupKind, + referenceForExtensionModel, +} from '@console/internal/module/k8s'; import { useDeepCompareMemoize } from '@console/shared'; -import { TopologyDataModelFactory } from '../extensions/topology'; import { ModelContext, ExtensibleModel, ModelExtensionContext } from './ModelContext'; interface DataModelExtensionProps { - dataModelFactory: TopologyDataModelFactory['properties']; + dataModelFactory: ResolvedExtension['properties']; + pluginID: string; } -const DataModelExtension: React.FC = ({ dataModelFactory }) => { - const dataModelContext = React.useContext(ModelContext); - const { id, priority, resources } = dataModelFactory; +const DataModelExtension: React.FC = ({ dataModelFactory, pluginID }) => { + const dataModelContext = useContext(ModelContext); + const { + id, + priority, + resources: rawResources, + getDataModel, + isResourceDepicted, + getDataModelReconciler, + } = dataModelFactory; const workloadKeys = useDeepCompareMemoize(dataModelFactory.workloadKeys); - const extensionContext = React.useRef({ + + // Convert WatchK8sResourcesGeneric to WatchK8sResources with namespace injection + const finalResources = useMemo | undefined>(() => { + if (!rawResources) { + return undefined; + } + + // Resources are already resolved by useResolvedExtensions, just need to convert format + const genericResources = rawResources as WatchK8sResourcesGeneric; + const converted: WatchK8sResources = {}; + + Object.entries(genericResources).forEach(([key, resource]) => { + const { model, opts } = resource; + + if (!model) { + // No model specified, just use opts with namespace + converted[key] = { namespace: dataModelContext.namespace, ...opts }; + return; + } + + // Convert model to kind reference + let kindReference: string | undefined; + + if (model.version && model.group) { + // Use referenceForExtensionModel for models with full GVK + kindReference = referenceForExtensionModel({ + group: model.group, + version: model.version, + kind: model.kind, + }); + } else { + // Fall back to internal model reference resolution + const internalModel = modelForGroupKind(model.group, model.kind); + if (!internalModel) { + // CRD not found - log warning and skip + // eslint-disable-next-line no-console + console.warn( + `Plugin "${pluginID}": Could not find model (CRD) for group "${model.group}" and kind "${model.kind}" to determine version. Please add a required flag to the extension to suppress this warning. The resource "${key}" will not be loaded and ignored in the topology view for now.`, + ); + return; + } + kindReference = referenceForModel(internalModel); + } + + converted[key] = { + namespace: dataModelContext.namespace, + kind: kindReference, + ...opts, + }; + }); + + return converted; + }, [rawResources, dataModelContext.namespace, pluginID]); + + const extensionContext = useRef({ priority, workloadKeys, - resources, + resources: undefined, }); - React.useEffect(() => { + useEffect(() => { const storedContext = dataModelContext.getExtension(id); if (!storedContext) { - extensionContext.current = { + // All CodeRefs are already resolved by useResolvedExtensions + const newContext: ModelExtensionContext = { priority, workloadKeys, - resources, + resources: finalResources, + // These are now direct functions, not CodeRefs that need resolution + dataModelGetter: getDataModel || (() => Promise.resolve({})), + dataModelDepicter: isResourceDepicted || (() => false), + dataModelReconciler: getDataModelReconciler || (() => {}), }; - dataModelContext.updateExtension(id, extensionContext.current); - - const { getDataModel, isResourceDepicted, getDataModelReconciler } = dataModelFactory; - if (getDataModel) { - getDataModel() - .then((getter) => { - extensionContext.current.dataModelGetter = getter; - dataModelContext.updateExtension(id, extensionContext.current); - }) - .catch(() => { - extensionContext.current.dataModelGetter = () => Promise.resolve({}); - dataModelContext.updateExtension(id, extensionContext.current); - }); - } else { - extensionContext.current.dataModelGetter = () => Promise.resolve({}); - dataModelContext.updateExtension(id, extensionContext.current); - } - - if (isResourceDepicted) { - isResourceDepicted() - .then((depicter) => { - extensionContext.current.dataModelDepicter = depicter; - dataModelContext.updateExtension(id, extensionContext.current); - }) - .catch(() => { - extensionContext.current.dataModelDepicter = () => false; - dataModelContext.updateExtension(id, extensionContext.current); - }); - } else { - extensionContext.current.dataModelDepicter = () => false; - dataModelContext.updateExtension(id, extensionContext.current); - } - - if (getDataModelReconciler) { - getDataModelReconciler() - .then((reconciler) => { - extensionContext.current.dataModelReconciler = reconciler; - dataModelContext.updateExtension(id, extensionContext.current); - }) - .catch(() => { - extensionContext.current.dataModelReconciler = () => {}; - dataModelContext.updateExtension(id, extensionContext.current); - }); - } else { - extensionContext.current.dataModelReconciler = () => {}; - dataModelContext.updateExtension(id, extensionContext.current); - } + extensionContext.current = newContext; + dataModelContext.updateExtension(id, newContext); } - }, [dataModelContext, dataModelFactory, id, priority, resources, workloadKeys]); + }, [ + dataModelContext, + id, + priority, + finalResources, + workloadKeys, + getDataModel, + isResourceDepicted, + getDataModelReconciler, + ]); return null; }; diff --git a/frontend/packages/topology/src/data-transforms/DataModelProvider.tsx b/frontend/packages/topology/src/data-transforms/DataModelProvider.tsx index 0d17f377c68..ea8a80f74f2 100644 --- a/frontend/packages/topology/src/data-transforms/DataModelProvider.tsx +++ b/frontend/packages/topology/src/data-transforms/DataModelProvider.tsx @@ -1,17 +1,9 @@ import * as React from 'react'; import { - ExtensionK8sGroupKindModel, isTopologyDataModelFactory as isDynamicTopologyDataModelFactory, TopologyDataModelFactory as DynamicTopologyDataModelFactory, - WatchK8sResource, + useResolvedExtensions, } from '@console/dynamic-plugin-sdk'; -import { - modelForGroupKind, - referenceForExtensionModel, - referenceForModel, -} from '@console/internal/module/k8s'; -import { LoadedExtension, useExtensions } from '@console/plugin-sdk/src'; -import { isTopologyDataModelFactory, TopologyDataModelFactory } from '../extensions/topology'; import DataModelExtension from './DataModelExtension'; import { ModelContext, ExtensibleModel } from './ModelContext'; import TopologyDataRetriever from './TopologyDataRetriever'; @@ -21,88 +13,28 @@ interface DataModelProviderProps { children?: React.ReactNode; } -const flattenResource = ( - namespace: string, - extension: LoadedExtension, - resourceKey: string, - model?: ExtensionK8sGroupKindModel, - opts = {} as Partial, -) => { - if (!model) { - return { namespace, ...opts }; - } - - if (model.version) { - const extensionReference = referenceForExtensionModel(model); // requires model.version - return { namespace, kind: extensionReference, ...opts }; - } - - // If can't find reference for an extention model, fall back to internal reference - const internalModel = modelForGroupKind(model.group, model.kind); // Return null for CRDs - if (!internalModel) { - // eslint-disable-next-line no-console - console.warn( - `Plugin "${extension.pluginID}": Could not find model (CRD) for group "${model.group}" and kind "${model.kind}" to determinate version. Please add a required flag to the extension to suppress this warning. The resource "${resourceKey}" will not be loaded and ignored in the topology view for now:`, - extension, - resourceKey, - model, - opts, - ); - return null; - } - const internalReference = referenceForModel(internalModel); - return { namespace, kind: internalReference, ...opts }; -}; - -export const getNamespacedDynamicModelFactories = ( - extensions: LoadedExtension[], -) => - extensions.map((extension) => { - return { - ...extension, - properties: { - ...extension.properties, - resources: (namespace: string) => - Object.entries(extension.properties.resources || {}).reduce((acc, [key, resource]) => { - const flattenedResource = flattenResource( - namespace, - extension, - key, - resource?.model, - resource?.opts, - ); - if (flattenedResource) { - acc[key] = flattenedResource; - } - return acc; - }, {}), - }, - }; - }); - -const DataModelProvider: React.FCC = ({ namespace, children }) => { +const DataModelProvider: React.FC = ({ namespace, children }) => { const [model, setModel] = React.useState(new ExtensibleModel(namespace)); React.useEffect(() => { setModel(new ExtensibleModel(namespace)); }, [namespace]); - const modelFactories = useExtensions(isTopologyDataModelFactory); - const dynamicModelFactories = useExtensions( - isDynamicTopologyDataModelFactory, - ); - - const namespacedDynamicFactories = React.useMemo( - () => getNamespacedDynamicModelFactories(dynamicModelFactories), - [dynamicModelFactories], - ); + // Use useResolvedExtensions to automatically resolve all CodeRefs in the extensions + const [dynamicModelFactories, dynamicResolved] = useResolvedExtensions< + DynamicTopologyDataModelFactory + >(isDynamicTopologyDataModelFactory); return ( - {namespace && ( + {namespace && dynamicResolved && ( <> - {[...namespacedDynamicFactories, ...modelFactories].map((factory) => ( - + {dynamicModelFactories.map((factory) => ( + ))} )} diff --git a/frontend/packages/topology/src/data-transforms/ModelContext.ts b/frontend/packages/topology/src/data-transforms/ModelContext.ts index 679ed16fc1f..ab9dd89ed58 100644 --- a/frontend/packages/topology/src/data-transforms/ModelContext.ts +++ b/frontend/packages/topology/src/data-transforms/ModelContext.ts @@ -18,8 +18,8 @@ import { export type ModelExtensionContext = { priority: number; - resources?: (namespace: string) => WatchK8sResources; workloadKeys?: string[]; + resources?: WatchK8sResources; dataModelGetter?: TopologyDataModelGetter; dataModelDepicter?: TopologyDataModelDepicted; dataModelReconciler?: TopologyDataModelReconciler; @@ -98,7 +98,7 @@ export class ExtensibleModel { const extensionKeys = Object.keys(this.extensions); this.watchedResources = extensionKeys.reduce((acc, key) => { if (this.extensions[key].resources) { - const resList = this.extensions[key].resources(this.namespace); + const resList = this.extensions[key].resources; Object.keys(resList).forEach((resKey) => { if (!acc[resKey]) { acc[resKey] = resList[resKey]; diff --git a/frontend/packages/topology/src/extensions/topology.ts b/frontend/packages/topology/src/extensions/topology.ts index 5c535777f36..7e4342136f0 100644 --- a/frontend/packages/topology/src/extensions/topology.ts +++ b/frontend/packages/topology/src/extensions/topology.ts @@ -1,14 +1,10 @@ import { TopologyQuadrant } from '@patternfly/react-topology'; -import { WatchK8sResources } from '@console/dynamic-plugin-sdk'; import { CodeRef } from '@console/dynamic-plugin-sdk/src/types'; import { Extension } from '@console/plugin-sdk/src/typings/base'; import { TopologyApplyDisplayOptions, - TopologyDataModelDepicted, - TopologyDataModelGetter, TopologyDisplayOption, ViewComponentFactory, - TopologyDataModelReconciler, TopologyDecoratorGetter, } from '../topology-types'; @@ -18,23 +14,6 @@ namespace ExtensionProperties { getFactory: CodeRef; } - export interface TopologyDataModelFactory { - /** Unique ID for the factory. */ - id: string; - /** Priority for the factory */ - priority: number; - /** Resources to be fetched from useK8sWatchResources hook. */ - resources?: (namespace: string) => WatchK8sResources; - /** Keys in resources containing workloads. */ - workloadKeys?: string[]; - /** Getter for the data model factory */ - getDataModel?: CodeRef; - /** Getter for function to determine if a resource is depicted by this model factory */ - isResourceDepicted?: CodeRef; - /** Getter for function to reconcile data model after all extensions' models have loaded */ - getDataModelReconciler?: CodeRef; - } - export interface TopologyDisplayFilters { // Getter for topology filters specific to the extension getTopologyFilters: CodeRef<() => TopologyDisplayOption[]>; @@ -55,11 +34,6 @@ export interface TopologyComponentFactory type: 'Topology/ComponentFactory'; } -export interface TopologyDataModelFactory - extends Extension { - type: 'Topology/DataModelFactory'; -} - export interface TopologyDisplayFilters extends Extension { type: 'Topology/DisplayFilters'; @@ -74,10 +48,6 @@ export const isTopologyComponentFactory = (e: Extension): e is TopologyComponent return e.type === 'Topology/ComponentFactory'; }; -export const isTopologyDataModelFactory = (e: Extension): e is TopologyDataModelFactory => { - return e.type === 'Topology/DataModelFactory'; -}; - export const isTopologyDisplayFilters = (e: Extension): e is TopologyDisplayFilters => { return e.type === 'Topology/DisplayFilters'; };