diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/api/topology-types.ts b/frontend/packages/console-dynamic-plugin-sdk/src/api/topology-types.ts index bcebb572b81..f4fe56656cf 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/api/topology-types.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/api/topology-types.ts @@ -63,6 +63,10 @@ export type CreateConnectionGetter = ( target?: Node, ) => CreateConnection; +export type RelationshipProviderProvides = (source: Node, target: Node) => Promise; + +export type RelationshipProviderCreate = (source: Node, target: Node) => Promise; + export enum TopologyDisplayFilterType { show = 'show', expand = 'expand', 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 477d50c39e5..7dc534530f1 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/extensions/topology.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/extensions/topology.ts @@ -1,5 +1,7 @@ import { CreateConnectionGetter, + RelationshipProviderCreate, + RelationshipProviderProvides, TopologyApplyDisplayOptions, TopologyDataModelDepicted, TopologyDataModelGetter, @@ -77,6 +79,21 @@ export type TopologyDecoratorProvider = ExtensionDeclaration< } >; +/** Topology relationship provider connector extension */ +export type TopologyRelationshipProvider = ExtensionDeclaration< + 'console.topology/relationship/provider', + { + // use to determine if a connection can be created between the source and target node + provides: CodeRef; + // tooltip to show when connector operation is hovering over the drop target ex: "Create a Visual Connector" + tooltip: string; + // callback to execute when connector is drop over target node to create a connection + create: CodeRef; + // priority for relationship, higher will be preferred in case of multiple + priority: number; + } +>; + // Type Guards export const isTopologyComponentFactory = (e: Extension): e is TopologyComponentFactory => @@ -93,3 +110,6 @@ export const isTopologyDisplayFilters = (e: Extension): e is TopologyDisplayFilt export const isTopologyDecoratorProvider = (e: Extension): e is TopologyDecoratorProvider => e.type === 'console.topology/decorator/provider'; + +export const isTopologyRelationshipProvider = (e: Extension): e is TopologyRelationshipProvider => + e.type === 'console.topology/relationship/provider'; diff --git a/frontend/packages/console-dynamic-plugin-sdk/src/schema/console-extensions.ts b/frontend/packages/console-dynamic-plugin-sdk/src/schema/console-extensions.ts index eaf0aa177de..1cd33c9f80b 100644 --- a/frontend/packages/console-dynamic-plugin-sdk/src/schema/console-extensions.ts +++ b/frontend/packages/console-dynamic-plugin-sdk/src/schema/console-extensions.ts @@ -43,6 +43,7 @@ import { TopologyDataModelFactory, TopologyDecoratorProvider, TopologyDisplayFilters, + TopologyRelationshipProvider, } from '../extensions/topology'; import { SupportedTopologyDetailsExtensions } from '../extensions/topology-details'; import { YAMLTemplate } from '../extensions/yaml-templates'; @@ -92,6 +93,7 @@ export type SupportedExtension = | TopologyDataModelFactory | TopologyDisplayFilters | TopologyDecoratorProvider + | TopologyRelationshipProvider | CreateResource; /** diff --git a/frontend/packages/dev-console/console-extensions.json b/frontend/packages/dev-console/console-extensions.json index 2e5c8436529..c2c78c492af 100644 --- a/frontend/packages/dev-console/console-extensions.json +++ b/frontend/packages/dev-console/console-extensions.json @@ -462,7 +462,7 @@ "type": "console.topology/component/factory", "properties": { "getFactory": { - "$codeRef": "topology.componentFactory" + "$codeRef": "topology.getDevConsoleComponentFactory" } } }, @@ -482,16 +482,24 @@ }, "workloadKeys": ["bindables"], "getDataModel": { - "$codeRef": "topology.getDataModel" + "$codeRef": "topology.getBindableDevConsoleTopologyDataModel" } } }, { - "type": "console.topology/create/connector", + "type": "console.topology/relationship/provider", "properties": { - "getCreateConnector": { - "$codeRef": "topology.createConnector" - } + "provides": { + "$codeRef": "topology.providerProvidesServiceBinding" + }, + "tooltip": "%devconsole~Create Service Binding%", + "create": { + "$codeRef": "topology.providerCreateServiceBinding" + }, + "priority": 100 + }, + "flags": { + "required": ["ALLOW_SERVICE_BINDING"] } } ] diff --git a/frontend/packages/dev-console/locales/en/devconsole.json b/frontend/packages/dev-console/locales/en/devconsole.json index 5ee57fdc677..7abb3188634 100644 --- a/frontend/packages/dev-console/locales/en/devconsole.json +++ b/frontend/packages/dev-console/locales/en/devconsole.json @@ -37,6 +37,7 @@ "Search": "Search", "Project": "Project", "Builds": "Builds", + "Create Service Binding": "Create Service Binding", "Container Image": "Container Image", "From Catalog": "From Catalog", "Helm Charts": "Helm Charts", diff --git a/frontend/packages/dev-console/package.json b/frontend/packages/dev-console/package.json index 0e2a9db4aad..fa77b30ac3c 100644 --- a/frontend/packages/dev-console/package.json +++ b/frontend/packages/dev-console/package.json @@ -27,7 +27,7 @@ "icons": "src/utils/icons.tsx", "catalog": "src/components/catalog", "fileUpload": "src/components/jar-file-upload/index.ts", - "topology": "src/components/topology/topology-plugin.ts" + "topology": "src/components/topology" } } } diff --git a/frontend/packages/dev-console/src/components/topology/components/BindableNode.tsx b/frontend/packages/dev-console/src/components/topology/components/BindableNode.tsx index 0391e85dcfd..e7e3d4ef15a 100644 --- a/frontend/packages/dev-console/src/components/topology/components/BindableNode.tsx +++ b/frontend/packages/dev-console/src/components/topology/components/BindableNode.tsx @@ -8,18 +8,12 @@ import { WithDragNodeProps, WithSelectionProps, } from '@patternfly/react-topology'; -import { connect } from 'react-redux'; import * as openshiftImg from '@console/internal/imgs/logos/openshift.svg'; import { modelFor, referenceFor, referenceForModel } from '@console/internal/module/k8s'; -import { RootState } from '@console/internal/redux'; -import { obsOrKafkaConnectionDropTargetSpec } from '@console/rhoas-plugin/src/topology/components/rhoasComponentUtils'; import { calculateRadius } from '@console/shared'; import { TrapezoidBaseNode } from '@console/topology/src/components/graph-view/components/nodes'; -import { getServiceBindingStatus, getTopologyResourceObject } from '@console/topology/src/utils'; - -interface StateProps { - serviceBinding: boolean; -} +import { getTopologyResourceObject } from '@console/topology/src/utils'; +import { getRelationshipProvider } from '@console/topology/src/utils/relationship-provider-utils'; type BindableNodeProps = { element: Node; @@ -27,36 +21,31 @@ type BindableNodeProps = { } & WithSelectionProps & WithDragNodeProps & WithContextMenuProps & - WithCreateConnectorProps & - StateProps; + WithCreateConnectorProps; const BindableNode: React.FC = ({ element, selected, onSelect, - serviceBinding, tooltipLabel, ...props }) => { + const spec = React.useMemo(() => getRelationshipProvider(), []); const { width, height } = element.getBounds(); const size = Math.min(width, height); const iconRadius = Math.min(width, height) * 0.25; const { radius } = calculateRadius(size); - const spec = React.useMemo(() => obsOrKafkaConnectionDropTargetSpec(serviceBinding), [ - serviceBinding, - ]); const [dndDropProps, dndDropRef] = useDndDrop(spec, { element, ...props }); const resourceObj = getTopologyResourceObject(element.getData()); const resourceModel = modelFor(referenceFor(resourceObj)); - // const kindResource = referenceForModel(resourceModel); + const iconData = element.getData()?.data?.icon || openshiftImg; - // const defaultIcon = getImageForIconClass(`icon-openshift`); return ( = ({ ); }; -const mapStateToProps = (state: RootState): StateProps => { - return { - serviceBinding: getServiceBindingStatus(state), - }; -}; - -export default connect(mapStateToProps)(observer(BindableNode)); +export default observer(BindableNode); diff --git a/frontend/packages/dev-console/src/components/topology/components/devConsoleComponetFactory.ts b/frontend/packages/dev-console/src/components/topology/components/devConsoleComponetFactory.ts index d5f3ea79f7d..4dc2cf7a6fe 100644 --- a/frontend/packages/dev-console/src/components/topology/components/devConsoleComponetFactory.ts +++ b/frontend/packages/dev-console/src/components/topology/components/devConsoleComponetFactory.ts @@ -16,7 +16,7 @@ import { withEditReviewAccess } from '@console/topology/src/utils'; import BindableNode from './BindableNode'; import { TYPE_BINDABLE_NODE } from './const'; -export const getDevConsoleComponentFactory = ( +const getDevConsoleComponentFactory = ( kind, type, ): React.ComponentType<{ element: GraphElement }> | undefined => { @@ -38,3 +38,5 @@ export const getDevConsoleComponentFactory = ( return undefined; } }; + +export default getDevConsoleComponentFactory; diff --git a/frontend/packages/dev-console/src/components/topology/createConnector.ts b/frontend/packages/dev-console/src/components/topology/createConnector.ts deleted file mode 100644 index d675ed02193..00000000000 --- a/frontend/packages/dev-console/src/components/topology/createConnector.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Node } from '@patternfly/react-topology'; -import { createServiceBinding } from '@console/topology/src/operators/actions/serviceBindings'; -import { TYPE_BINDABLE_NODE } from './components/const'; - -const createServiceBindingConnection = (source: Node, target: Node) => { - const sourceResource = source.getData().resource || source.getData().resources?.obj; - const targetResource = target.getData().resource || target.getData().resources?.obj; - - return createServiceBinding(sourceResource, targetResource).then(() => null); -}; - -export const getCreateConnector = (createHints: string[], source: Node, target: Node) => { - if ( - createHints && - createHints.includes('createServiceBinding') && - target.getType() === TYPE_BINDABLE_NODE - ) { - return createServiceBindingConnection; - } - return null; -}; diff --git a/frontend/packages/dev-console/src/components/topology/dev-console-data-transformer.ts b/frontend/packages/dev-console/src/components/topology/dev-console-data-transformer.ts index 990dc445956..313f8bcac4c 100644 --- a/frontend/packages/dev-console/src/components/topology/dev-console-data-transformer.ts +++ b/frontend/packages/dev-console/src/components/topology/dev-console-data-transformer.ts @@ -1,8 +1,20 @@ -import { Model, NodeModel } from '@patternfly/react-topology'; +import { EdgeModel, Model, NodeModel } from '@patternfly/react-topology'; import { K8sResourceKind } from '@console/internal/module/k8s'; -import { OverviewItem } from '@console/shared/src'; -import { NODE_WIDTH, NODE_HEIGHT, NODE_PADDING } from '@console/topology/src/const'; +import { ClusterServiceVersionKind } from '@console/operator-lifecycle-manager/src/types'; +import { + getDefaultOperatorIcon, + getImageForCSVIcon, + getOperatorBackedServiceKindMap, + OverviewItem, +} from '@console/shared/src'; +import { + NODE_WIDTH, + NODE_HEIGHT, + NODE_PADDING, + TYPE_SERVICE_BINDING, +} from '@console/topology/src/const'; import { getTopologyNodeItem } from '@console/topology/src/data-transforms/transform-utils'; +import { edgesFromServiceBinding } from '@console/topology/src/operators/operators-data-transformer'; import { TopologyDataObject, TopologyDataResources } from '@console/topology/src/topology-types'; import { TYPE_BINDABLE_NODE } from './components/const'; @@ -23,42 +35,94 @@ export const createOverviewItem = (obj: K8sResourceKind): OverviewItem { +export const getTopologyBindableNode = ( + bindables: K8sResourceKind[], + typeNode: string, + resources: TopologyDataResources, +): NodeModel[] => { const nodes = []; for (const obj of bindables) { + const resKindMap = getOperatorBackedServiceKindMap( + resources?.clusterServiceVersions?.data as ClusterServiceVersionKind[], + ); + const csvData = resKindMap?.[obj.kind]; const data: TopologyDataObject = { id: obj.metadata.uid, name: obj.metadata.name, - type: TYPE_BINDABLE_NODE, + type: typeNode, resource: obj, // resources is poorly named, should be overviewItem, eventually going away. resources: createOverviewItem(obj), data: { resource: obj, + icon: getImageForCSVIcon(csvData?.spec?.icon?.[0]) || getDefaultOperatorIcon(), }, }; - nodes.push(getTopologyNodeItem(obj, TYPE_BINDABLE_NODE, data, BINDABLE_PROPS)); + nodes.push(getTopologyNodeItem(obj, typeNode, data, BINDABLE_PROPS)); } return nodes; }; -export const getBindableDevConsoleTopologyDataModel = ( +export const getBindableServiceBindingEdges = ( + dc: K8sResourceKind, + rhoasNodes: NodeModel[], + sbrs: K8sResourceKind[], +): EdgeModel[] => { + const edges = []; + if (!sbrs?.length || !rhoasNodes?.length) { + return edges; + } + + edgesFromServiceBinding(dc, sbrs).forEach((sbr) => { + sbr.spec.services?.forEach((bss) => { + if (bss) { + const targetNode = rhoasNodes.find( + (node) => + node.data.resource.kind === bss.kind && node.data.resource.metadata.name === bss.name, + ); + if (targetNode) { + const target = targetNode.data.resource.metadata.uid; + const source = dc.metadata.uid; + if (source && target) { + edges.push({ + id: `${source}_${target}`, + type: TYPE_SERVICE_BINDING, + source, + target, + resource: sbr, + data: { sbr }, + }); + } + } + } + }); + }); + + return edges; +}; + +const getBindableDevConsoleTopologyDataModel = ( namespace: string, resources: TopologyDataResources, workloads: K8sResourceKind[], ): Promise => { if (!resources.bindables?.data) return Promise.resolve({ nodes: [], edges: [] }); + const serviceBindingRequests = resources.serviceBindingRequests?.data; const bindableDataModel = { - nodes: getTopologyBindableNode(resources.bindables.data), + nodes: getTopologyBindableNode(resources.bindables.data, TYPE_BINDABLE_NODE, resources), edges: [], }; - if (bindableDataModel.nodes?.length) { - workloads.forEach(() => { - bindableDataModel.edges.push(...[]); + if (bindableDataModel.nodes?.length && serviceBindingRequests?.length) { + workloads.forEach((dc) => { + bindableDataModel.edges.push( + ...getBindableServiceBindingEdges(dc, bindableDataModel.nodes, serviceBindingRequests), + ); }); } return Promise.resolve(bindableDataModel); }; + +export default getBindableDevConsoleTopologyDataModel; diff --git a/frontend/packages/dev-console/src/components/topology/index.ts b/frontend/packages/dev-console/src/components/topology/index.ts new file mode 100644 index 00000000000..19aedf3a85c --- /dev/null +++ b/frontend/packages/dev-console/src/components/topology/index.ts @@ -0,0 +1,6 @@ +export { default as getDevConsoleComponentFactory } from './components/devConsoleComponetFactory'; +export { default as getBindableDevConsoleTopologyDataModel } from './dev-console-data-transformer'; +export { + providerProvidesServiceBinding, + providerCreateServiceBinding, +} from './relationship-provider'; diff --git a/frontend/packages/dev-console/src/components/topology/relationship-provider.ts b/frontend/packages/dev-console/src/components/topology/relationship-provider.ts new file mode 100644 index 00000000000..974f5fc1a57 --- /dev/null +++ b/frontend/packages/dev-console/src/components/topology/relationship-provider.ts @@ -0,0 +1,23 @@ +import { Node } from '@patternfly/react-topology'; +import { TYPE_WORKLOAD } from '@console/topology/src/const'; +import { createServiceBinding } from '@console/topology/src/operators/actions/serviceBindings'; +import { getResource } from '@console/topology/src/utils'; + +export const providerProvidesServiceBinding = (source: Node, target: Node) => { + if (!source || !target) return false; + const sourceObj = getResource(source); + const targetObj = getResource(target); + return ( + sourceObj && + targetObj && + sourceObj !== targetObj && + source.getData()?.type === TYPE_WORKLOAD && + targetObj.metadata?.labels?.['app.kubernetes.io/component'] === 'external-service' + ); +}; + +export const providerCreateServiceBinding = (source: Node, target: Node) => { + const sourceResource = getResource(source); + const targetResource = getResource(target); + return createServiceBinding(sourceResource, targetResource).then(() => null); +}; diff --git a/frontend/packages/dev-console/src/components/topology/topology-plugin.ts b/frontend/packages/dev-console/src/components/topology/topology-plugin.ts deleted file mode 100644 index cada5e82a4a..00000000000 --- a/frontend/packages/dev-console/src/components/topology/topology-plugin.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { getDevConsoleComponentFactory } from './components/devConsoleComponetFactory'; -import { getCreateConnector } from './createConnector'; -import { getBindableDevConsoleTopologyDataModel } from './dev-console-data-transformer'; - -export const componentFactory = getDevConsoleComponentFactory; -export const getDataModel = getBindableDevConsoleTopologyDataModel; -export const createConnector = getCreateConnector; diff --git a/frontend/packages/knative-plugin/console-extensions.json b/frontend/packages/knative-plugin/console-extensions.json index dc7c2b91c2a..b9799c1aacd 100644 --- a/frontend/packages/knative-plugin/console-extensions.json +++ b/frontend/packages/knative-plugin/console-extensions.json @@ -190,5 +190,21 @@ "flags": { "required": ["KNATIVE_SERVING_SERVICE"] } + }, + { + "type": "console.topology/relationship/provider", + "properties": { + "provides": { + "$codeRef": "topology.providerProvidesServiceBinding" + }, + "tooltip": "%knative-plugin~Create Service Binding%", + "create": { + "$codeRef": "topology.providerCreateServiceBinding" + }, + "priority": 200 + }, + "flags": { + "required": ["ALLOW_SERVICE_BINDING"] + } } ] diff --git a/frontend/packages/knative-plugin/locales/en/knative-plugin.json b/frontend/packages/knative-plugin/locales/en/knative-plugin.json index 2b46cdea82d..3efaa0e658d 100644 --- a/frontend/packages/knative-plugin/locales/en/knative-plugin.json +++ b/frontend/packages/knative-plugin/locales/en/knative-plugin.json @@ -12,6 +12,7 @@ "Provider": "Provider", "Serving": "Serving", "Eventing": "Eventing", + "Create Service Binding": "Create Service Binding", "Add Subscription": "Add Subscription", "Add Trigger": "Add Trigger", "Delete Revision": "Delete Revision", diff --git a/frontend/packages/knative-plugin/package.json b/frontend/packages/knative-plugin/package.json index 404644f50e0..8a7fe84a238 100644 --- a/frontend/packages/knative-plugin/package.json +++ b/frontend/packages/knative-plugin/package.json @@ -24,7 +24,8 @@ "actions": "src/actions", "icons": "src/utils/icons.ts", "catalog": "src/catalog", - "yamlTemplates": "src/yaml-templates.ts" + "yamlTemplates": "src/yaml-templates.ts", + "topology": "src/topology/topology.ts" } } } diff --git a/frontend/packages/knative-plugin/src/topology/relationship-provider.ts b/frontend/packages/knative-plugin/src/topology/relationship-provider.ts new file mode 100644 index 00000000000..101d4445d24 --- /dev/null +++ b/frontend/packages/knative-plugin/src/topology/relationship-provider.ts @@ -0,0 +1,23 @@ +import { Node } from '@patternfly/react-topology'; +import { createServiceBinding } from '@console/topology/src/operators/actions/serviceBindings'; +import { getResource } from '@console/topology/src/utils'; +import { TYPE_KNATIVE_SERVICE } from './const'; + +export const providerProvidesServiceBinding = (source: Node, target: Node) => { + if (!source || !target) return false; + const sourceObj = getResource(source); + const targetObj = getResource(target); + return ( + sourceObj && + targetObj && + sourceObj !== targetObj && + source.getData()?.type === TYPE_KNATIVE_SERVICE && + targetObj.metadata?.labels?.['app.kubernetes.io/component'] === 'external-service' + ); +}; + +export const providerCreateServiceBinding = (source: Node, target: Node) => { + const sourceResource = getResource(source); + const targetResource = getResource(target); + return createServiceBinding(sourceResource, targetResource).then(() => null); +}; diff --git a/frontend/packages/knative-plugin/src/topology/topology.ts b/frontend/packages/knative-plugin/src/topology/topology.ts new file mode 100644 index 00000000000..67c31dc15dd --- /dev/null +++ b/frontend/packages/knative-plugin/src/topology/topology.ts @@ -0,0 +1,4 @@ +export { + providerProvidesServiceBinding, + providerCreateServiceBinding, +} from './relationship-provider'; diff --git a/frontend/packages/rhoas-plugin/console-extensions.json b/frontend/packages/rhoas-plugin/console-extensions.json index 904acac08d2..43c5d1b5e84 100644 --- a/frontend/packages/rhoas-plugin/console-extensions.json +++ b/frontend/packages/rhoas-plugin/console-extensions.json @@ -1,4 +1,43 @@ [ + { + "type": "console.resource-metadata", + "properties": { + "model": { + "group": "rhoas.redhat.com", + "version": "v1alpha1", + "kind": "CloudServicesRequest" + }, + "label": "%rhoas-plugin~Cloud Services Request%", + "labelPlural": "%rhoas-plugin~Cloud Services Requests%", + "abbr": "CSCR" + } + }, + { + "type": "console.resource-metadata", + "properties": { + "model": { + "group": "rhoas.redhat.com", + "version": "v1alpha1", + "kind": "KafkaConnection" + }, + "label": "%rhoas-plugin~Kafka Connection%", + "labelPlural": "%rhoas-plugin~Kafka Connections%", + "abbr": "AKC" + } + }, + { + "type": "console.resource-metadata", + "properties": { + "model": { + "group": "rhoas.redhat.com", + "version": "v1alpha1", + "kind": "CloudServiceAccountRequest" + }, + "label": "%rhoas-plugin~Cloud service Account Request%", + "labelPlural": "%rhoas-plugin~Cloud Service Account Requests%", + "abbr": "CSCR" + } + }, { "type": "console.flag/model", "properties": { @@ -10,6 +49,17 @@ } } }, + { + "type": "console.page/route", + "properties": { + "exact": true, + "path": ["/rhoas/ns/:ns/:service"], + "component": { "$codeRef": "rhoasComponents.ServiceListPage" } + }, + "flags": { + "required": ["RHOAS_FLAG"] + } + }, { "type": "dev-console.add/action", "flags": { @@ -52,5 +102,59 @@ "flags": { "required": ["RHOAS_FLAG"] } + }, + { + "type": "console.topology/component/factory", + "properties": { + "getFactory": { + "$codeRef": "topology.getRhoasComponentFactory" + } + }, + "flags": { + "required": ["RHOAS_FLAG"] + } + }, + { + "type": "console.topology/data/factory", + "properties": { + "id": "rhoas-topology-model-factory", + "priority": 400, + "resources": { + "kafkaConnections": { + "model": { + "kind": "KafkaConnection", + "group": "rhoas.redhat.com", + "version": "v1alpha1" + }, + "opts": { + "isList": true, + "optional": true + } + } + }, + "workloadKeys": ["kafkaConnections"], + "getDataModel": { + "$codeRef": "topology.getRhoasTopologyDataModel" + } + }, + "flags": { + "required": ["RHOAS_FLAG"] + } + }, + { + "type": "console.topology/relationship/provider", + "properties": { + "provides": { + "$codeRef": "topology.providerProvidesKafkaConnection" + }, + "tooltip": "%rhoas-plugin~Create a Kafka connector%", + "create": { + "$codeRef": "topology.providerCreateKafkaConnection" + }, + "priority": 300 + }, + "flags": { + "required": ["RHOAS_FLAG"] + } } ] diff --git a/frontend/packages/rhoas-plugin/locales/en/rhoas-plugin.json b/frontend/packages/rhoas-plugin/locales/en/rhoas-plugin.json index e9bd0a680b7..6446c42de97 100644 --- a/frontend/packages/rhoas-plugin/locales/en/rhoas-plugin.json +++ b/frontend/packages/rhoas-plugin/locales/en/rhoas-plugin.json @@ -1,7 +1,14 @@ { + "Cloud Services Request": "Cloud Services Request", + "Cloud Services Requests": "Cloud Services Requests", + "Kafka Connection": "Kafka Connection", + "Kafka Connections": "Kafka Connections", + "Cloud service Account Request": "Cloud service Account Request", + "Cloud Service Account Requests": "Cloud Service Account Requests", "Managed Services": "Managed Services", "Discover managed services to simplify deployments and reduce operational overhead & complexities": "Discover managed services to simplify deployments and reduce operational overhead & complexities", "Browse managed services to connect applications and microservices to support services to create a full solution.": "Browse managed services to connect applications and microservices to support services to create a full solution.", + "Create a Kafka connector": "Create a Kafka connector", "Unlock with token": "Unlock with token", "Unlocked": "Unlocked", "Red Hat OpenShift Application Services include services like Red Hat OpenShift Streams for Apache Kafka": "Red Hat OpenShift Application Services include include services like Red Hat OpenShift Streams for Apache Kafka", @@ -44,21 +51,14 @@ "No results match the filter criteria.": "No results match the filter criteria.", "Clear filters": "Clear filters", "List of Kafka Instances": "List of Kafka Instances", - "Cloud Services Request": "Cloud Services Request", - "Cloud Services Requests": "Cloud Services Requests", - "Kafka Connection": "Kafka Connection", - "Kafka Connections": "Kafka Connections", - "Cloud service Account Request": "Cloud service Account Request", - "Cloud Service Account Requests": "Cloud Service Account Requests", "Bootstrap Server": "Bootstrap Server", "URL": "URL", "Secret": "Secret", "No Secret": "No Secret", - "Create a Kafka connector": "Create a Kafka connector", - "Create a binding connector": "Create a binding connector", "No data": "No data", "Cloud Service": "Cloud Service", "This resource represents service that exist outside your cluster": "This resource represents service that exist outside your cluster", "Details": "Details", - "Resources": "Resources" + "Resources": "Resources", + "Error moving event source kafka connector": "Error moving event source kafka connector" } \ No newline at end of file diff --git a/frontend/packages/rhoas-plugin/package.json b/frontend/packages/rhoas-plugin/package.json index eb4a74604b5..4f0360c3fc4 100644 --- a/frontend/packages/rhoas-plugin/package.json +++ b/frontend/packages/rhoas-plugin/package.json @@ -13,7 +13,9 @@ "entry": "src/plugin.ts", "exposedModules": { "constants": "src/const.ts", - "catalog": "src/catalog" + "catalog": "src/catalog", + "topology": "src/topology", + "rhoasComponents": "src/components" } } } diff --git a/frontend/packages/rhoas-plugin/src/components/index.ts b/frontend/packages/rhoas-plugin/src/components/index.ts new file mode 100644 index 00000000000..b22e844b891 --- /dev/null +++ b/frontend/packages/rhoas-plugin/src/components/index.ts @@ -0,0 +1 @@ +export { default as ServiceListPage } from './service-list/ServiceListPage'; diff --git a/frontend/packages/rhoas-plugin/src/plugin.ts b/frontend/packages/rhoas-plugin/src/plugin.ts index fb028d1a741..c5b96afa254 100644 --- a/frontend/packages/rhoas-plugin/src/plugin.ts +++ b/frontend/packages/rhoas-plugin/src/plugin.ts @@ -1,16 +1,8 @@ import * as _ from 'lodash'; -import { AddAction } from '@console/dynamic-plugin-sdk'; -import { ModelDefinition, ModelFeatureFlag, RoutePage, Plugin } from '@console/plugin-sdk'; -import { FLAG_RHOAS } from './const'; +import { ModelDefinition, Plugin } from '@console/plugin-sdk'; import * as models from './models'; -import { rhoasTopologyPlugin, TopologyConsumedExtensions } from './topology/rhoas-topology-plugin'; -type ConsumedExtensions = - | ModelDefinition - | ModelFeatureFlag - | RoutePage - | AddAction - | TopologyConsumedExtensions; +type ConsumedExtensions = ModelDefinition; const plugin: Plugin = [ { @@ -19,23 +11,6 @@ const plugin: Plugin = [ models: _.values(models), }, }, - { - type: 'Page/Route', - properties: { - exact: true, - path: ['/rhoas/ns/:ns/:service'], - loader: async () => - ( - await import( - './components/service-list/ServiceListPage' /* webpackChunkName: "services-kafka-plugin-releases-kafka-page" */ - ) - ).default, - }, - flags: { - required: [FLAG_RHOAS], - }, - }, - ...rhoasTopologyPlugin, ]; export default plugin; diff --git a/frontend/packages/rhoas-plugin/src/topology/components/KafkaNode.scss b/frontend/packages/rhoas-plugin/src/topology/components/KafkaNode.scss deleted file mode 100644 index c7bae4157b1..00000000000 --- a/frontend/packages/rhoas-plugin/src/topology/components/KafkaNode.scss +++ /dev/null @@ -1,4 +0,0 @@ -// This can be removed in the future when resource labels will have explicit css rules -.co-m-resource-kafka-connection { - background-color: --pf-global--palette--red-300; -} diff --git a/frontend/packages/rhoas-plugin/src/topology/components/KafkaNode.tsx b/frontend/packages/rhoas-plugin/src/topology/components/KafkaNode.tsx deleted file mode 100644 index 09ab58039f5..00000000000 --- a/frontend/packages/rhoas-plugin/src/topology/components/KafkaNode.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import * as React from 'react'; -import { - observer, - Node, - useDndDrop, - WithContextMenuProps, - WithCreateConnectorProps, - WithDragNodeProps, - WithSelectionProps, -} from '@patternfly/react-topology'; -import { connect } from 'react-redux'; -import { referenceForModel } from '@console/internal/module/k8s'; -import { RootState } from '@console/internal/redux'; -import { calculateRadius } from '@console/shared'; -import { TrapezoidBaseNode } from '@console/topology/src/components/graph-view/components/nodes'; -import { getServiceBindingStatus } from '@console/topology/src/utils'; -import { kafkaIcon } from '../../const'; -import { KafkaConnectionModel } from '../../models'; -import { obsOrKafkaConnectionDropTargetSpec } from './rhoasComponentUtils'; - -import './KafkaNode.scss'; - -interface StateProps { - serviceBinding: boolean; -} - -type KafkaNodeProps = { - element: Node; - tooltipLabel?: string; -} & WithSelectionProps & - WithDragNodeProps & - WithContextMenuProps & - WithCreateConnectorProps & - StateProps; - -const KafkaNode: React.FC = ({ - element, - selected, - onSelect, - serviceBinding, - tooltipLabel, - ...props -}) => { - const { width, height } = element.getBounds(); - const size = Math.min(width, height); - const iconRadius = Math.min(width, height) * 0.25; - const { radius } = calculateRadius(size); - const spec = React.useMemo(() => obsOrKafkaConnectionDropTargetSpec(serviceBinding), [ - serviceBinding, - ]); - const [dndDropProps, dndDropRef] = useDndDrop(spec, { element, ...props }); - - return ( - - ); -}; - -const mapStateToProps = (state: RootState): StateProps => { - return { - serviceBinding: getServiceBindingStatus(state), - }; -}; - -export default connect(mapStateToProps)(observer(KafkaNode)); diff --git a/frontend/packages/rhoas-plugin/src/topology/components/rhoasComponentFactory.ts b/frontend/packages/rhoas-plugin/src/topology/components/rhoasComponentFactory.ts index 5e9842c909d..bae17c19b2d 100644 --- a/frontend/packages/rhoas-plugin/src/topology/components/rhoasComponentFactory.ts +++ b/frontend/packages/rhoas-plugin/src/topology/components/rhoasComponentFactory.ts @@ -5,6 +5,7 @@ import { withSelection, withCreateConnector, } from '@patternfly/react-topology'; +import BindableNode from '@console/dev-console/src/components/topology/components/BindableNode'; import { createConnectorCallback, nodeDragSourceSpec, @@ -14,9 +15,8 @@ import { } from '@console/topology/src/components/graph-view'; import { withEditReviewAccess } from '@console/topology/src/utils'; import { TYPE_MANAGED_KAFKA_CONNECTION } from './const'; -import KafkaNode from './KafkaNode'; -export const getRhoasComponentFactory = ( +const getRhoasComponentFactory = ( kind, type, ): React.ComponentType<{ element: GraphElement }> | undefined => { @@ -30,7 +30,7 @@ export const getRhoasComponentFactory = ( withEditReviewAccess('patch')( withDragNode(nodeDragSourceSpec(type))( withSelection({ controlled: true })( - withContextMenu(noRegroupWorkloadContextMenu)(KafkaNode), + withContextMenu(noRegroupWorkloadContextMenu)(BindableNode), ), ), ), @@ -39,3 +39,5 @@ export const getRhoasComponentFactory = ( return undefined; } }; + +export default getRhoasComponentFactory; diff --git a/frontend/packages/rhoas-plugin/src/topology/components/rhoasComponentUtils.ts b/frontend/packages/rhoas-plugin/src/topology/components/rhoasComponentUtils.ts deleted file mode 100644 index 6940fd7bcf2..00000000000 --- a/frontend/packages/rhoas-plugin/src/topology/components/rhoasComponentUtils.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { - CREATE_CONNECTOR_DROP_TYPE, - DropTargetSpec, - GraphElement, - isEdge, -} from '@patternfly/react-topology'; -import i18next from 'i18next'; -import { - CREATE_EV_SRC_KAFKA_CONNECTOR_OPERATION, - MOVE_EV_SRC_KAFKA_CONNECTOR_OPERATION, - nodesEdgeIsDragging, -} from '@console/knative-plugin/src/topology/components/knativeComponentUtils'; -import { TYPE_KAFKA_CONNECTION_LINK } from '@console/knative-plugin/src/topology/const'; -import { - canDropEdgeOnNode, - EDGE_DRAG_TYPE, - highlightNode, - NodeComponentProps, -} from '@console/topology/src/components/graph-view'; - -const getKafkaConnectionTooltip = (monitor): string => { - return monitor.getOperation()?.type === MOVE_EV_SRC_KAFKA_CONNECTOR_OPERATION || - monitor.getOperation()?.type === CREATE_EV_SRC_KAFKA_CONNECTOR_OPERATION - ? i18next.t('rhoas-plugin~Create a Kafka connector') - : i18next.t('rhoas-plugin~Create a binding connector'); -}; - -export const obsOrKafkaConnectionDropTargetSpec = ( - serviceBinding: boolean, -): DropTargetSpec< - GraphElement, - any, - { canDrop: boolean; dropTarget: boolean; edgeDragging: boolean }, - NodeComponentProps -> => ({ - accept: [EDGE_DRAG_TYPE, CREATE_CONNECTOR_DROP_TYPE], - canDrop: (item, monitor, props) => { - if (isEdge(item)) { - return canDropEdgeOnNode(monitor.getOperation()?.type, item, props.element); - } - if (item === props.element) { - return false; - } - return ( - !props.element.getTargetEdges().find((e) => e.getSource() === item) || - item.getType() === TYPE_KAFKA_CONNECTION_LINK - ); - }, - collect: (monitor, props) => { - return { - canDrop: - (serviceBinding && highlightNode(monitor, props.element)) || - monitor.getOperation()?.type === MOVE_EV_SRC_KAFKA_CONNECTOR_OPERATION || - monitor.getOperation()?.type === CREATE_EV_SRC_KAFKA_CONNECTOR_OPERATION, - dropTarget: monitor.isOver({ shallow: true }), - edgeDragging: nodesEdgeIsDragging(monitor, props), - tooltipLabel: getKafkaConnectionTooltip(monitor), - }; - }, - dropHint: (item, monitor) => - monitor.getOperation()?.type === MOVE_EV_SRC_KAFKA_CONNECTOR_OPERATION || - monitor.getOperation()?.type === CREATE_EV_SRC_KAFKA_CONNECTOR_OPERATION - ? 'createKafkaConnection' - : 'createServiceBinding', -}); diff --git a/frontend/packages/rhoas-plugin/src/topology/createConnector.ts b/frontend/packages/rhoas-plugin/src/topology/createConnector.ts deleted file mode 100644 index b36a6227e81..00000000000 --- a/frontend/packages/rhoas-plugin/src/topology/createConnector.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Node } from '@patternfly/react-topology'; -import { createServiceBinding } from '@console/topology/src/operators/actions/serviceBindings'; -import { TYPE_MANAGED_KAFKA_CONNECTION } from './components/const'; - -const createServiceBindingConnection = (source: Node, target: Node) => { - const sourceResource = source.getData().resource || source.getData().resources?.obj; - const targetResource = target.getData().resource || target.getData().resources?.obj; - - return createServiceBinding(sourceResource, targetResource).then(() => null); -}; - -export const getCreateConnector = (createHints: string[], source: Node, target: Node) => { - if ( - createHints && - createHints.includes('createServiceBinding') && - target.getType() === TYPE_MANAGED_KAFKA_CONNECTION - ) { - return createServiceBindingConnection; - } - return null; -}; diff --git a/frontend/packages/rhoas-plugin/src/topology/index.ts b/frontend/packages/rhoas-plugin/src/topology/index.ts index b9d08b45a43..d81c2730d79 100644 --- a/frontend/packages/rhoas-plugin/src/topology/index.ts +++ b/frontend/packages/rhoas-plugin/src/topology/index.ts @@ -1,9 +1,6 @@ -export const getRhoasTopologyDataModel = () => - import( - './rhoas-data-transformer' /* webpackChunkName: "rhoas-topology-data-transformer" */ - ).then((m) => m.getRhoasTopologyDataModel()); - -export const getRhoasComponentFactory = () => - import( - './components/rhoasComponentFactory' /* webpackChunkName: "rhoas-topology-component-factory" */ - ).then((m) => m.getRhoasComponentFactory); +export { default as getRhoasComponentFactory } from './components/rhoasComponentFactory'; +export { default as getRhoasTopologyDataModel } from './rhoas-data-transformer'; +export { + providerProvidesKafkaConnection, + providerCreateKafkaConnection, +} from './relationship-provider'; diff --git a/frontend/packages/rhoas-plugin/src/topology/relationship-provider.ts b/frontend/packages/rhoas-plugin/src/topology/relationship-provider.ts new file mode 100644 index 00000000000..181f732c3b3 --- /dev/null +++ b/frontend/packages/rhoas-plugin/src/topology/relationship-provider.ts @@ -0,0 +1,31 @@ +import { Node } from '@patternfly/react-topology'; +import i18next from 'i18next'; +import { errorModal } from '@console/internal/components/modals'; +import { EventSourceKafkaModel } from '@console/knative-plugin/src/models'; +import { createEventSourceKafkaConnection } from '@console/knative-plugin/src/topology/knative-topology-utils'; +import { getResource } from '@console/topology/src/utils'; +import { KafkaConnectionModel } from '../models/rhoas'; + +export const providerProvidesKafkaConnection = (source: Node, target: Node) => { + if (!source || !target) return false; + const sourceObj = getResource(source); + const targetObj = getResource(target); + return ( + sourceObj && + targetObj && + sourceObj !== targetObj && + sourceObj.kind === EventSourceKafkaModel.kind && + targetObj.kind === KafkaConnectionModel.kind + ); +}; + +export const providerCreateKafkaConnection = (source: Node, target: Node) => + createEventSourceKafkaConnection(source, target) + .then(() => null) + .catch((error) => { + errorModal({ + title: i18next.t('rhoas-plugin~Error moving event source kafka connector'), + error: error.message, + showIcon: true, + }); + }); diff --git a/frontend/packages/rhoas-plugin/src/topology/rhoas-data-transformer.ts b/frontend/packages/rhoas-plugin/src/topology/rhoas-data-transformer.ts index aef7e132c41..ac408a06ce4 100644 --- a/frontend/packages/rhoas-plugin/src/topology/rhoas-data-transformer.ts +++ b/frontend/packages/rhoas-plugin/src/topology/rhoas-data-transformer.ts @@ -1,115 +1,34 @@ -import { EdgeModel, Model, NodeModel } from '@patternfly/react-topology'; -import { apiVersionForModel, K8sResourceKind } from '@console/internal/module/k8s'; -import { OverviewItem } from '@console/shared/src'; -import { TYPE_SERVICE_BINDING } from '@console/topology/src/const'; -import { getTopologyNodeItem } from '@console/topology/src/data-transforms/transform-utils'; -import { edgesFromServiceBinding } from '@console/topology/src/operators/operators-data-transformer'; -import { TopologyDataObject, TopologyDataResources } from '@console/topology/src/topology-types'; -import { KafkaConnectionModel } from '../models'; +import { Model } from '@patternfly/react-topology'; import { - KAFKA_WIDTH, - KAFKA_HEIGHT, - KAFKA_PADDING, - TYPE_MANAGED_KAFKA_CONNECTION, -} from './components/const'; - -const KAFKA_PROPS = { - width: KAFKA_WIDTH, - height: KAFKA_HEIGHT, - group: false, - visible: true, - style: { - padding: KAFKA_PADDING, - }, -}; - -export const createOverviewItem = (obj: K8sResourceKind): OverviewItem => { - if (!obj.apiVersion) { - obj.apiVersion = apiVersionForModel(KafkaConnectionModel); - } - if (!obj.kind) { - obj.kind = KafkaConnectionModel.kind; - } - - return { - isOperatorBackedService: true, - obj, - }; -}; - -export const getTopologyRhoasNodes = (kafkaConnections: K8sResourceKind[]): NodeModel[] => { - const nodes = []; - for (const obj of kafkaConnections) { - const data: TopologyDataObject = { - id: obj.metadata.uid, - name: obj.metadata.name, - type: TYPE_MANAGED_KAFKA_CONNECTION, - resource: obj, - // resources is poorly named, should be overviewItem, eventually going away. - resources: createOverviewItem(obj), - data: { - resource: obj, - }, - }; - nodes.push(getTopologyNodeItem(obj, TYPE_MANAGED_KAFKA_CONNECTION, data, KAFKA_PROPS)); - } - - return nodes; -}; - -export const getRhoasServiceBindingEdges = ( - dc: K8sResourceKind, - rhoasNodes: NodeModel[], - sbrs: K8sResourceKind[], -): EdgeModel[] => { - const edges = []; - if (!sbrs?.length || !rhoasNodes?.length) { - return edges; - } - - edgesFromServiceBinding(dc, sbrs).forEach((sbr) => { - sbr.spec.services?.forEach((bss) => { - if (bss) { - const targetNode = rhoasNodes.find( - (node) => - node.data.resource.kind === bss.kind && node.data.resource.metadata.name === bss.name, - ); - if (targetNode) { - const target = targetNode.data.resource.metadata.uid; - const source = dc.metadata.uid; - if (source && target) { - edges.push({ - id: `${source}_${target}`, - type: TYPE_SERVICE_BINDING, - source, - target, - resource: sbr, - data: { sbr }, - }); - } - } - } - }); - }); - - return edges; -}; -export const getRhoasTopologyDataModel = () => ( + getBindableServiceBindingEdges, + getTopologyBindableNode, +} from '@console/dev-console/src/components/topology/dev-console-data-transformer'; +import { K8sResourceKind } from '@console/internal/module/k8s'; +import { TopologyDataResources } from '@console/topology/src/topology-types'; +import { TYPE_MANAGED_KAFKA_CONNECTION } from './components/const'; + +const getRhoasTopologyDataModel = ( namespace: string, resources: TopologyDataResources, workloads: K8sResourceKind[], ): Promise => { const serviceBindingRequests = resources?.serviceBindingRequests?.data; const rhoasDataModel: Model = { - nodes: getTopologyRhoasNodes(resources.kafkaConnections.data), + nodes: getTopologyBindableNode( + resources.kafkaConnections.data, + TYPE_MANAGED_KAFKA_CONNECTION, + resources, + ), edges: [], }; if (rhoasDataModel.nodes?.length) { workloads.forEach((dc) => { rhoasDataModel.edges.push( - ...getRhoasServiceBindingEdges(dc, rhoasDataModel.nodes, serviceBindingRequests), + ...getBindableServiceBindingEdges(dc, rhoasDataModel.nodes, serviceBindingRequests), ); }); } return Promise.resolve(rhoasDataModel); }; + +export default getRhoasTopologyDataModel; diff --git a/frontend/packages/rhoas-plugin/src/topology/rhoas-topology-plugin.tsx b/frontend/packages/rhoas-plugin/src/topology/rhoas-topology-plugin.tsx deleted file mode 100644 index 24391a864c2..00000000000 --- a/frontend/packages/rhoas-plugin/src/topology/rhoas-topology-plugin.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { applyCodeRefSymbol } from '@console/dynamic-plugin-sdk/src/coderefs/coderef-resolver'; -import { Plugin } from '@console/plugin-sdk'; -import { ALLOW_SERVICE_BINDING_FLAG } from '@console/topology/src/const'; -import { - TopologyComponentFactory, - TopologyCreateConnector, - TopologyDataModelFactory, -} from '@console/topology/src/extensions/topology'; -import { FLAG_RHOAS } from '../const'; -import { getRhoasWatchedResources } from './rhoasResources'; -import { getRhoasComponentFactory, getRhoasTopologyDataModel } from './index'; - -export type TopologyConsumedExtensions = - | TopologyComponentFactory - | TopologyDataModelFactory - | TopologyCreateConnector; - -export const rhoasTopologyPlugin: Plugin = [ - { - type: 'Topology/ComponentFactory', - properties: { - getFactory: applyCodeRefSymbol(getRhoasComponentFactory), - }, - flags: { - required: [FLAG_RHOAS], - }, - }, - { - type: 'Topology/DataModelFactory', - properties: { - id: 'rhoas-topology-model-factory', - priority: 400, - getDataModel: applyCodeRefSymbol(getRhoasTopologyDataModel), - resources: getRhoasWatchedResources, - workloadKeys: ['kafkaConnections'], - }, - flags: { - required: [FLAG_RHOAS], - }, - }, - { - type: 'Topology/CreateConnector', - properties: { - getCreateConnector: applyCodeRefSymbol(() => - import('./createConnector' /* webpackChunkName: "rhoas-create-connector" */).then( - (m) => m.getCreateConnector, - ), - ), - }, - flags: { - required: [ALLOW_SERVICE_BINDING_FLAG, FLAG_RHOAS], - }, - }, -]; diff --git a/frontend/packages/rhoas-plugin/src/topology/rhoasResources.ts b/frontend/packages/rhoas-plugin/src/topology/rhoasResources.ts deleted file mode 100644 index 15ac84a4061..00000000000 --- a/frontend/packages/rhoas-plugin/src/topology/rhoasResources.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { referenceForModel } from '@console/internal/module/k8s'; -import { KafkaConnectionModel } from '../models'; - -export const getRhoasWatchedResources = (namespace: string) => { - return { - kafkaConnections: { - isList: true, - kind: referenceForModel(KafkaConnectionModel), - namespace, - optional: true, - }, - }; -}; diff --git a/frontend/packages/rhoas-plugin/src/utils/resourceCreators.ts b/frontend/packages/rhoas-plugin/src/utils/resourceCreators.ts index 6be89fcac2f..6cb82580272 100644 --- a/frontend/packages/rhoas-plugin/src/utils/resourceCreators.ts +++ b/frontend/packages/rhoas-plugin/src/utils/resourceCreators.ts @@ -183,6 +183,9 @@ export const createKafkaConnection = async ( metadata: { name: kafkaName, namespace: currentNamespace, + labels: { + 'app.kubernetes.io/component': 'external-service', + }, }, spec: { kafkaId, diff --git a/frontend/packages/topology/src/components/graph-view/components/componentUtils.ts b/frontend/packages/topology/src/components/graph-view/components/componentUtils.ts index 58ae01b567c..cda7bd1e3bd 100644 --- a/frontend/packages/topology/src/components/graph-view/components/componentUtils.ts +++ b/frontend/packages/topology/src/components/graph-view/components/componentUtils.ts @@ -351,11 +351,16 @@ const createConnectorCallback = () => ( if (source === target) { return null; } + const relationshipProviders = target.getGraph()?.getData()?.relationshipProviderExtensions; + const curRelProvider = relationshipProviders?.find(({ uid }) => dropHints.includes(uid)); + if (curRelProvider) { + return curRelProvider.properties.create(source, target); + } + const createConnectors = target.getGraph()?.getData()?.createConnectorExtensions; if (isGraph(target) || !createConnectors) { return Promise.resolve(createVisualConnector(source, target)); } - const creator = createConnectors.find((getter) => !!getter(dropHints, source, target)); if (creator) { return creator(dropHints, source, target)(source, target); diff --git a/frontend/packages/topology/src/components/page/TopologyView.tsx b/frontend/packages/topology/src/components/page/TopologyView.tsx index 2abf850c76f..ef00c4d6736 100644 --- a/frontend/packages/topology/src/components/page/TopologyView.tsx +++ b/frontend/packages/topology/src/components/page/TopologyView.tsx @@ -18,6 +18,8 @@ import { TopologyCreateConnector as DynamicTopologyCreateConnector, TopologyDecoratorProvider as DynamicTopologyDecoratorProvider, TopologyDisplayFilters as DynamicTopologyDisplayFilters, + TopologyRelationshipProvider, + isTopologyRelationshipProvider, } from '@console/dynamic-plugin-sdk'; import { selectOverviewDetailsTab } from '@console/internal/actions/ui'; import { @@ -142,6 +144,9 @@ export const ConnectedTopologyView: React.FC = ({ const [dynamicExtensionDecorators, dynamicExtensionDecoratorsResolved] = useResolvedExtensions< DynamicTopologyDecoratorProvider >(isDynamicTopologyDecoratorProvider); + const [relationshipProvider] = useResolvedExtensions( + isTopologyRelationshipProvider, + ); const [topologyDecorators, setTopologyDecorators] = React.useState<{ [key: string]: TopologyDecorator[]; @@ -178,6 +183,7 @@ export const ConnectedTopologyView: React.FC = ({ ) : [], decorators: topologyDecorators, + relationshipProviderExtensions: relationshipProvider, }), [ createConnectors, @@ -187,6 +193,7 @@ export const ConnectedTopologyView: React.FC = ({ dynamicCreateConnectorsResolved, eventSourceEnabled, namespace, + relationshipProvider, topologyDecorators, ], ); diff --git a/frontend/packages/topology/src/utils/relationship-provider-utils.ts b/frontend/packages/topology/src/utils/relationship-provider-utils.ts new file mode 100644 index 00000000000..5fcbe4f6358 --- /dev/null +++ b/frontend/packages/topology/src/utils/relationship-provider-utils.ts @@ -0,0 +1,80 @@ +import { + CREATE_CONNECTOR_DROP_TYPE, + DropTargetSpec, + GraphElement, + isEdge, + isNode, +} from '@patternfly/react-topology'; +import { + canDropEdgeOnNode, + EDGE_DRAG_TYPE, + NodeComponentProps, + nodesEdgeIsDragging, +} from '../components/graph-view'; +import { OdcBaseEdge, OdcBaseNode } from '../elements'; + +export const getRelationshipProvider = (): DropTargetSpec< + GraphElement, + any, + { canDrop: boolean; dropTarget: boolean; edgeDragging: boolean; tooltipLabel: string }, + NodeComponentProps +> => { + const getSourceNode = (monitor) => + monitor.getItem() instanceof OdcBaseEdge ? monitor.getItem().getSource() : monitor.getItem(); + + const isEdgeConnected = (monitor, targetNode) => { + const sourceNode = getSourceNode(monitor); + return ( + sourceNode instanceof OdcBaseNode && + sourceNode.getSourceEdges().find((e) => e.getTarget() === targetNode) + ); + }; + + const getRelExtension = (monitor, props) => { + const sourceNode = getSourceNode(monitor); + const targetNode = props.element; + + const topologyRelationshipExtensions = targetNode.getGraph()?.getData() + ?.relationshipProviderExtensions; + const relationshipExtension = + sourceNode instanceof OdcBaseNode && + targetNode instanceof OdcBaseNode && + isNode(sourceNode) && + isNode(targetNode) + ? topologyRelationshipExtensions?.filter(({ properties: { provides } }) => + provides(sourceNode, targetNode), + ) + : []; + return ( + relationshipExtension.length > 0 && + relationshipExtension.sort((a, b) => b.properties?.priority - a.properties?.priority)[0] + ); + }; + + return { + accept: [EDGE_DRAG_TYPE, CREATE_CONNECTOR_DROP_TYPE], + canDrop: (item, monitor, props) => { + if (isEdge(item)) { + return canDropEdgeOnNode(monitor.getOperation()?.type, item, props.element); + } + if (item === props.element) { + return false; + } + const relationshipExtension = getRelExtension(monitor, props); + return !!relationshipExtension && !isEdgeConnected(monitor, props.element); + }, + collect: (monitor, props) => { + const relationshipExtension = getRelExtension(monitor, props); + return { + canDrop: !!relationshipExtension && !isEdgeConnected(monitor, props.element), + dropTarget: monitor.isOver({ shallow: true }), + edgeDragging: nodesEdgeIsDragging(monitor, props), + tooltipLabel: relationshipExtension?.properties?.tooltip, + }; + }, + dropHint: (item, monitor, props) => { + const relationshipExtension = getRelExtension(monitor, props); + return relationshipExtension?.uid; + }, + }; +};