diff --git a/headless-services/commons/commons-sprotty/src/main/java/org/springframework/ide/vscode/commons/sprotty/scan/DefaultDiagramServerManager.java b/headless-services/commons/commons-sprotty/src/main/java/org/springframework/ide/vscode/commons/sprotty/scan/DefaultDiagramServerManager.java index 9c8be5e7c..af3c708ae 100644 --- a/headless-services/commons/commons-sprotty/src/main/java/org/springframework/ide/vscode/commons/sprotty/scan/DefaultDiagramServerManager.java +++ b/headless-services/commons/commons-sprotty/src/main/java/org/springframework/ide/vscode/commons/sprotty/scan/DefaultDiagramServerManager.java @@ -1,5 +1,6 @@ package org.springframework.ide.vscode.commons.sprotty.scan; +import java.util.Optional; import java.util.concurrent.ExecutionException; import java.util.function.Consumer; @@ -8,6 +9,7 @@ import org.eclipse.sprotty.ActionMessage; import org.eclipse.sprotty.DefaultDiagramServer; import org.eclipse.sprotty.IDiagramServer; import org.eclipse.sprotty.ILayoutEngine; +import org.eclipse.sprotty.IPopupModelFactory; import org.eclipse.sprotty.RequestModelAction; import org.eclipse.sprotty.SGraph; import org.slf4j.Logger; @@ -40,6 +42,9 @@ public class DefaultDiagramServerManager implements DiagramServerManager { @Autowired private ILayoutEngine layoutEngine; + @Autowired + private Optional popups; + private Consumer remoteEndpoint; private IDiagramServer getDiagramServer(String clientId) { @@ -48,6 +53,7 @@ public class DefaultDiagramServerManager implements DiagramServerManager { DefaultDiagramServer diagramServer = new DefaultDiagramServer(clientId); diagramServer.setRemoteEndpoint(this::sendMessageToRemoteEndpoint); diagramServer.setLayoutEngine(layoutEngine); + diagramServer.setPopupModelFactory(popups.orElse(null)); return diagramServer; }); } catch (ExecutionException e) { diff --git a/headless-services/commons/commons-sprotty/src/main/java/org/springframework/ide/vscode/commons/sprotty/scan/DiagramServerConfiguration.java b/headless-services/commons/commons-sprotty/src/main/java/org/springframework/ide/vscode/commons/sprotty/scan/DiagramServerConfiguration.java index 64acf48ef..678e47bf5 100644 --- a/headless-services/commons/commons-sprotty/src/main/java/org/springframework/ide/vscode/commons/sprotty/scan/DiagramServerConfiguration.java +++ b/headless-services/commons/commons-sprotty/src/main/java/org/springframework/ide/vscode/commons/sprotty/scan/DiagramServerConfiguration.java @@ -1,8 +1,12 @@ package org.springframework.ide.vscode.commons.sprotty.scan; +import java.util.Optional; + import org.eclipse.elk.alg.layered.options.LayeredMetaDataProvider; import org.eclipse.sprotty.ILayoutEngine; +import org.eclipse.sprotty.SGraph; import org.eclipse.sprotty.layout.ElkLayoutEngine; +import org.eclipse.sprotty.layout.SprottyLayoutConfigurator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -10,9 +14,10 @@ import org.springframework.context.annotation.Configuration; public class DiagramServerConfiguration { @Bean - public ILayoutEngine layoutEngine() { + public ILayoutEngine layoutEngine(Optional configurator) { ElkLayoutEngine.initialize(new LayeredMetaDataProvider()); - return new ElkLayoutEngine(); + final ElkLayoutEngine engine = new ElkLayoutEngine(); + return (root) -> engine.layout((SGraph)root, configurator.orElse(null)); } } diff --git a/headless-services/sprotty-si-view/client/css/diagram.css b/headless-services/sprotty-si-view/client/css/diagram.css index 8d2bc681b..5f788467a 100644 --- a/headless-services/sprotty-si-view/client/css/diagram.css +++ b/headless-services/sprotty-si-view/client/css/diagram.css @@ -20,6 +20,16 @@ stroke-width: 2; } +.sprotty-port { + fill: #aaffaa; + stroke: #000000; + stroke-width: 1px; +} + +.sprotty-port.sprotty-error-port { + fill: #ff0000; +} + .sprotty-text { font-size: 16pt; text-anchor: middle; diff --git a/headless-services/sprotty-si-view/client/src/di.config.ts b/headless-services/sprotty-si-view/client/src/di.config.ts index fbfe0e60a..4808fcd06 100644 --- a/headless-services/sprotty-si-view/client/src/di.config.ts +++ b/headless-services/sprotty-si-view/client/src/di.config.ts @@ -40,12 +40,31 @@ import { graphModule, routingModule, modelSourceModule, + edgeLayoutModule, SLabel, SLabelView, SCompartment, - SCompartmentView, SEdge, RectangularNode + SCompartmentView, + SEdge, + RectangularNode, + HtmlRootView, + HtmlRoot, + PreRenderedView, + PreRenderedElement, + labelEditModule, edgeEditModule, RectangularPort } from "sprotty"; -import {IntegrationNodeView, CircleNodeView, EdgeView, ExampleGraphView, ChannelNodeView} from "./views"; +import { + IntegrationNodeView, + CircleNodeView, + EdgeView, + ExampleGraphView, + ChannelNodeView, + PortView, + ErrorPortView +} from "./views"; +import fadeModule from "sprotty/lib/features/fade/di.config"; +import buttonModule from "sprotty/lib/features/button/di.config"; +import expandModule from "sprotty/lib/features/expand/di.config"; export enum TransportMedium { None, @@ -56,7 +75,7 @@ export enum TransportMedium { export default (transport: TransportMedium, clientId: string) => { require("sprotty/css/sprotty.css"); require("../css/diagram.css"); - const circlegraphModule = new ContainerModule((bind, unbind, isBound, rebind) => { + const integrationGaphModule = new ContainerModule((bind, unbind, isBound, rebind) => { switch (transport) { case TransportMedium.Websocket: bind(TYPES.ModelSource).to(ExampleWebsocketDiagramServer).inSingletonScope(); @@ -77,16 +96,22 @@ export default (transport: TransportMedium, clientId: string) => { configureModelElement(context, 'node:label', SLabel, SLabelView); configureModelElement(context, 'compartment', SCompartment, SCompartmentView); configureModelElement(context, 'edge:straight', /*OrthogonalEgde*/ SEdge, EdgeView); + configureModelElement(context, 'html', HtmlRoot, HtmlRootView); + configureModelElement(context, 'pre-rendered', PreRenderedElement, PreRenderedView); + configureModelElement(context, 'input-port', RectangularPort, PortView); + configureModelElement(context, 'output-port', RectangularPort, PortView); + configureModelElement(context, 'error-port', RectangularPort, ErrorPortView); configureViewerOptions(context, { needsClientLayout: true, needsServerLayout: true, baseDiv: clientId, }); - console.log('di.config.ts : ' + clientId); }); const container = new Container(); - container.load(defaultModule, selectModule, moveModule, boundsModule, undoRedoModule, viewportModule, - exportModule, updateModule, graphModule, routingModule, modelSourceModule, circlegraphModule, hoverModule); + container.load(defaultModule, selectModule, moveModule, boundsModule, undoRedoModule, + viewportModule, fadeModule, hoverModule, exportModule, expandModule, buttonModule, + updateModule, graphModule, routingModule, edgeEditModule, edgeLayoutModule, labelEditModule, + modelSourceModule, integrationGaphModule); return container; }; diff --git a/headless-services/sprotty-si-view/client/src/views.tsx b/headless-services/sprotty-si-view/client/src/views.tsx index 597e322f5..32a943f88 100644 --- a/headless-services/sprotty-si-view/client/src/views.tsx +++ b/headless-services/sprotty-si-view/client/src/views.tsx @@ -123,6 +123,30 @@ export class ChannelNodeView extends RectangularNodeView { ; } } + +@injectable() +export class PortView implements IView { + render(node: Readonly, context: RenderingContext): VNode { + return + + {context.renderChildren(node)} + ; + } +} + +@injectable() +export class ErrorPortView implements IView { + render(node: Readonly, context: RenderingContext): VNode { + return + + {context.renderChildren(node)} + ; + } +} /* '+ '' + diff --git a/headless-services/sprotty-si-view/src/main/java/org/springframework/ide/si/view/GraphDataProvider.java b/headless-services/sprotty-si-view/src/main/java/org/springframework/ide/si/view/GraphDataProvider.java index 9d270382a..5d2f6922a 100644 --- a/headless-services/sprotty-si-view/src/main/java/org/springframework/ide/si/view/GraphDataProvider.java +++ b/headless-services/sprotty-si-view/src/main/java/org/springframework/ide/si/view/GraphDataProvider.java @@ -3,5 +3,5 @@ package org.springframework.ide.si.view; import org.springframework.ide.si.view.json.SpringIntegrationGraph; public interface GraphDataProvider { - SpringIntegrationGraph getGraph(); + SpringIntegrationGraph getGraph() throws Exception; } diff --git a/headless-services/sprotty-si-view/src/main/java/org/springframework/ide/si/view/IntegrationConfiguration.java b/headless-services/sprotty-si-view/src/main/java/org/springframework/ide/si/view/IntegrationConfiguration.java new file mode 100644 index 000000000..ac589ef40 --- /dev/null +++ b/headless-services/sprotty-si-view/src/main/java/org/springframework/ide/si/view/IntegrationConfiguration.java @@ -0,0 +1,40 @@ +package org.springframework.ide.si.view; + +import org.eclipse.elk.alg.layered.options.LayeredOptions; +import org.eclipse.elk.core.options.PortConstraints; +import org.eclipse.elk.core.options.PortSide; +import org.eclipse.elk.graph.properties.IPropertyHolder; +import org.eclipse.sprotty.layout.SprottyLayoutConfigurator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class IntegrationConfiguration { + + @Bean + public SprottyLayoutConfigurator integrationLayoutConfigurator() { + SprottyLayoutConfigurator configurator = new SprottyLayoutConfigurator(); + + IPropertyHolder holder = configurator.configureByType(SIDiagramGenerator.TYPE_INTEGRATION_GRAPH); + holder.setProperty(LayeredOptions.SPACING_COMPONENT_COMPONENT, 80.0); + holder.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 80.0); + + holder = configurator.configureByType("output-port"); + holder.setProperty(LayeredOptions.PORT_SIDE, PortSide.EAST); + + holder = configurator.configureByType("input-port"); + holder.setProperty(LayeredOptions.PORT_SIDE, PortSide.WEST); + + holder = configurator.configureByType("error-port"); + holder.setProperty(LayeredOptions.PORT_SIDE, PortSide.SOUTH); + + holder = configurator.configureByType("node:channel"); + holder.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + + holder = configurator.configureByType("node:integration_node"); + holder.setProperty(LayeredOptions.PORT_CONSTRAINTS, PortConstraints.FIXED_SIDE); + + return configurator; + } + +} diff --git a/headless-services/sprotty-si-view/src/main/java/org/springframework/ide/si/view/IntegrationPopupFactory.java b/headless-services/sprotty-si-view/src/main/java/org/springframework/ide/si/view/IntegrationPopupFactory.java new file mode 100644 index 000000000..39b701601 --- /dev/null +++ b/headless-services/sprotty-si-view/src/main/java/org/springframework/ide/si/view/IntegrationPopupFactory.java @@ -0,0 +1,35 @@ +package org.springframework.ide.si.view; + +import java.util.ArrayList; + +import org.eclipse.sprotty.IDiagramServer; +import org.eclipse.sprotty.IPopupModelFactory; +import org.eclipse.sprotty.PreRenderedElement; +import org.eclipse.sprotty.RequestPopupModelAction; +import org.eclipse.sprotty.SGraph; +import org.eclipse.sprotty.SModelElement; +import org.eclipse.sprotty.SModelRoot; +import org.springframework.stereotype.Component; + +@Component +public class IntegrationPopupFactory implements IPopupModelFactory { + + @Override + public SModelRoot createPopupModel(SModelElement element, RequestPopupModelAction request, IDiagramServer server) { + SGraph popup = new SGraph(); + popup.setType("html"); + popup.setId("popup"); + popup.setCanvasBounds(request.getBounds()); + + ArrayList children = new ArrayList<>(); + popup.setChildren(children); + + PreRenderedElement content = new PreRenderedElement(); + children.add(content); + content.setId("popup-title"); + content.setCode("
Hello World!
"); + + return popup; + } + +} diff --git a/headless-services/sprotty-si-view/src/main/java/org/springframework/ide/si/view/MockGraphData.java b/headless-services/sprotty-si-view/src/main/java/org/springframework/ide/si/view/MockGraphData.java index fc6df9303..49fc5b198 100644 --- a/headless-services/sprotty-si-view/src/main/java/org/springframework/ide/si/view/MockGraphData.java +++ b/headless-services/sprotty-si-view/src/main/java/org/springframework/ide/si/view/MockGraphData.java @@ -1,5 +1,8 @@ package org.springframework.ide.si.view; +import java.io.IOException; + +import org.apache.commons.io.IOUtils; import org.springframework.ide.si.view.json.SpringIntegrationGraph; import org.springframework.stereotype.Component; @@ -323,7 +326,12 @@ public class MockGraphData implements GraphDataProvider { "}"; @Override - public SpringIntegrationGraph getGraph() { + public SpringIntegrationGraph getGraph() throws IOException { +// Gson gson = new Gson().newBuilder().setPrettyPrinting().create(); +// JsonObject json = gson.fromJson(data, JsonObject.class); +// System.out.println(gson.toJson(json)); + + String data = IOUtils.toString(getClass().getResource("/static/sample.json")); return new Gson().fromJson(data, SpringIntegrationGraph.class); } } diff --git a/headless-services/sprotty-si-view/src/main/java/org/springframework/ide/si/view/SIDiagramGenerator.java b/headless-services/sprotty-si-view/src/main/java/org/springframework/ide/si/view/SIDiagramGenerator.java index dac77c3c5..6af681ca2 100644 --- a/headless-services/sprotty-si-view/src/main/java/org/springframework/ide/si/view/SIDiagramGenerator.java +++ b/headless-services/sprotty-si-view/src/main/java/org/springframework/ide/si/view/SIDiagramGenerator.java @@ -1,11 +1,13 @@ package org.springframework.ide.si.view; import java.util.ArrayList; -import java.util.HashSet; +import java.util.HashMap; import java.util.List; -import java.util.Set; +import java.util.Map; import org.eclipse.sprotty.Dimension; +import org.eclipse.sprotty.EdgePlacement; +import org.eclipse.sprotty.EdgePlacement.Side; import org.eclipse.sprotty.LayoutOptions; import org.eclipse.sprotty.Point; import org.eclipse.sprotty.RequestModelAction; @@ -16,6 +18,8 @@ import org.eclipse.sprotty.SLabel; import org.eclipse.sprotty.SModelElement; import org.eclipse.sprotty.SModelRoot; import org.eclipse.sprotty.SNode; +import org.eclipse.sprotty.SPort; +import org.eclipse.sprotty.SShapeElement; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -24,15 +28,26 @@ import org.springframework.ide.si.view.json.SpringIntegrationGraph; import org.springframework.ide.si.view.json.SpringIntegrationNode; import org.springframework.ide.vscode.commons.sprotty.api.DiagramGenerator; import org.springframework.ide.vscode.commons.util.Assert; +import org.springframework.ide.vscode.commons.util.StringUtil; import org.springframework.stereotype.Component; import com.google.common.collect.ImmutableSet; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; @Component public class SIDiagramGenerator implements DiagramGenerator { + static final String TYPE_INTEGRATION_GRAPH = "graph"; + + private static final double CHANNEL_HEIGHT = 40D; + + private static final double NODE_MIN_HEIGHT = 80D; + + private static final double MIN_WIDTH = 120D; + private static final Logger log = LoggerFactory.getLogger(SIDiagramGenerator.class); private static final ImmutableSet channelTypes = ImmutableSet.of( @@ -40,6 +55,8 @@ public class SIDiagramGenerator implements DiagramGenerator { "publish-subscribe-channel" ); + private String labelProperty = "stats.sendCount"; + @Autowired GraphDataProvider graphDataProvider; @@ -59,29 +76,36 @@ public class SIDiagramGenerator implements DiagramGenerator { private SGraph toSprottyGraph(SpringIntegrationGraph json) throws Exception { SGraph graph = new SGraph(); graph.setId("root"); - graph.setType("graph"); + graph.setType(TYPE_INTEGRATION_GRAPH); List children = new ArrayList<>(); graph.setChildren(children); - Set nodeIds = new HashSet<>(); + Map nodeIds = new HashMap<>(); for (SpringIntegrationNode node : json.getNodes()) { String id = node.getNodeId()+""; String name = node.getName(); - Assert.isLegal(nodeIds.add(id)); - SNode n = createNode(id, name, node.getComponentType()); - children.add(n); + Assert.isLegal(!nodeIds.containsKey(id)); + nodeIds.put(id, node); + String type = visualType(node.getComponentType()); + switch (type) { + case "channel": + children.add(createChannelNode(id, name)); + break; + default: + children.add(createIntegrationNode(id, name)); + } } int linkId = 0; for (SpringIntegrationEdge link : json.getLinks()) { String id = "l"+(linkId++); String sourceId = link.getFrom()+""; - ensureNode(nodeIds, children, sourceId); String targetId = link.getTo()+""; - ensureNode(nodeIds, children, targetId); - SEdge e = createEdge(id, sourceId, targetId); + + + SEdge e = createEdge(id, sourceId, targetId, getEdgeLabel(nodeIds.get(sourceId)), link.getType()); children.add(e); } // @@ -98,54 +122,106 @@ public class SIDiagramGenerator implements DiagramGenerator { return graph; } - private void ensureNode(Set nodeIds, List children, String sourceId) { - if (!nodeIds.contains(sourceId)) { - nodeIds.add(sourceId); - children.add(createNode(sourceId, "?"+sourceId, "unknown")); - } + private String getEdgeLabel(SpringIntegrationNode springIntegrationNode) { + JsonElement json = gson.toJsonTree(springIntegrationNode); + for (String prop : labelProperty.split("\\.")) { + if (json instanceof JsonObject) { + JsonObject obj = (JsonObject)json; + if (obj.has(prop)) { + json = obj.get(prop); + } else { + return null; + } + } else { + return null; + } + }; + return json.getAsString(); } private static SNode createNode(String id, String labelText, String type) { SNode node = new SNode(); node.setId(id); - node.setType("node:"+visualType(type)); + node.setType("node:"+type); node.setLayout("vbox"); node.setPosition(new Point(Math.random() * 1024, Math.random() * 768)); node.setSize(new Dimension(80, 80)); node.setChildren(new ArrayList<>()); + centerNode(node); SCompartment compartment = new SCompartment(); compartment.setId(id + "-comp"); compartment.setType("compartment"); - compartment.setLayout("hbox"); + compartment.setLayout("vbox"); compartment.setChildren(new ArrayList<>()); + centerNode(compartment); SLabel label = new SLabel(); label.setId(id + "-lanbel"); label.setType("node:label"); label.setText(labelText); + centerNode(label); compartment.getChildren().add(label); + + label = new SLabel(); + label.setId(id + "-lanbel1"); + label.setType("node:label"); + label.setText("s"); + centerNode(label); + + compartment.getChildren().add(label); + node.getChildren().add(compartment); + SPort outputPort = new SPort(); + outputPort.setType("output-port"); + outputPort.setId("output-port-" + id); + outputPort.setSize(new Dimension(10,10)); + node.getChildren().add(outputPort); + + SPort inputPort = new SPort(); + inputPort.setType("input-port"); + inputPort.setId("input-port-" + id); + inputPort.setSize(new Dimension(10,10)); + node.getChildren().add(inputPort); + return node; } + private static void centerNode(SShapeElement shape) { + LayoutOptions options = shape.getLayoutOptions(); + if (options == null) { + options = new LayoutOptions(); + shape.setLayoutOptions(options); + } + options.setHAlign("center"); + options.setVAlign("center"); + } + private static SNode createIntegrationNode(String id, String labelText) { SNode node = createNode(id, labelText, "integration_node"); LayoutOptions layoutOptions = new LayoutOptions(); - layoutOptions.setMinHeight(80D); - layoutOptions.setMinWidth(120D); + layoutOptions.setMinHeight(NODE_MIN_HEIGHT); + layoutOptions.setMinWidth(MIN_WIDTH); node.setLayoutOptions(layoutOptions); + + SPort errorPort = new SPort(); + errorPort.setType("error-port"); + errorPort.setId("error-port-" + id); + errorPort.setSize(new Dimension(10, 10)); + node.getChildren().add(errorPort); + return node; } private static SNode createChannelNode(String id, String labelText) { SNode node = createNode(id, labelText, "channel"); LayoutOptions layoutOptions = new LayoutOptions(); - layoutOptions.setMinHeight(80D); - layoutOptions.setMinWidth(120D); + layoutOptions.setMinHeight(CHANNEL_HEIGHT); + layoutOptions.setMinWidth(MIN_WIDTH); node.setLayoutOptions(layoutOptions); + return node; } @@ -157,12 +233,34 @@ public class SIDiagramGenerator implements DiagramGenerator { } } - private static SEdge createEdge(String id, String sourceId, String targetId) { + private static SEdge createEdge(String id, String sourceId, String targetId, String labelStr, String linkType) { SEdge edge = new SEdge(); edge.setId(id); edge.setType("edge:straight"); - edge.setSourceId(sourceId); - edge.setTargetId(targetId); + if ("error".equals(linkType)) { + edge.setSourceId("error-port-" + sourceId); + } else { + edge.setSourceId("output-port-" + sourceId); + } + edge.setTargetId("input-port-" + targetId); + + if (StringUtil.hasText(labelStr)) { + SLabel label = new SLabel(); + label.setType("node:label"); + EdgePlacement edgePlacement = new EdgePlacement(); + edgePlacement.setRotate(true); + edgePlacement.setPosition(0.0); + edgePlacement.setSide(Side.right); + label.setEdgePlacement(edgePlacement); + label.setId(id + "-label1"); + label.setText(labelStr); + + label.setPosition(new Point(20,30)); + ArrayList children = new ArrayList<>(); + children.add(label); + edge.setChildren(children); + } + return edge; }