Hover for SI graph shows table of properties

This commit is contained in:
Kris De Volder
2019-08-30 12:54:24 -07:00
parent 9aa237e887
commit 31308a73c1
16 changed files with 251 additions and 168 deletions

View File

@@ -35,6 +35,11 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>${commons-text-version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>

View File

@@ -13,6 +13,8 @@ package org.springframework.ide.vscode.commons.util;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import org.apache.commons.text.StringEscapeUtils;
/**
* Helper class to make it a little easier to create simple html page.
*
@@ -118,6 +120,12 @@ public class HtmlBuffer {
raw("</b>");
}
public void code(String string) {
raw("<code>");
text(string);
raw("</code>");
}
/**
* Escapes reserved HTML characters in the given string.
* <p>
@@ -127,30 +135,6 @@ public class HtmlBuffer {
* @return the string with escaped characters
*/
public static String convertToHTMLContent(String content) {
content= replace(content, '&', "&amp;"); //$NON-NLS-1$
content= replace(content, '"', "&quot;"); //$NON-NLS-1$
content= replace(content, '<', "&lt;"); //$NON-NLS-1$
return replace(content, '>', "&gt;"); //$NON-NLS-1$
return StringEscapeUtils.escapeHtml4(content);
}
private static String replace(String text, char c, String s) {
int previous= 0;
int current= text.indexOf(c, previous);
if (current == -1)
return text;
StringBuffer buffer= new StringBuffer();
while (current > -1) {
buffer.append(text.substring(previous, current));
buffer.append(s);
previous= current + 1;
current= text.indexOf(c, previous);
}
buffer.append(text.substring(previous));
return buffer.toString();
}
}

View File

@@ -104,23 +104,24 @@
</pluginRepositories>
<properties>
<!-- NOTE: Reactor version must match version used by the CF client -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit-version>4.12</junit-version>
<assertj-version>3.5.2</assertj-version>
<slf4j-version>1.7.25</slf4j-version>
<cloudfoundry-client-version>3.8.0.RELEASE</cloudfoundry-client-version>
<commons-codec-version>1.11</commons-codec-version>
<commons-io-version>2.4</commons-io-version>
<commons-text-version>1.7</commons-text-version>
<elk-version>0.6.0-SNAPSHOT</elk-version>
<guava-version>19.0</guava-version>
<mockito-version>1.10.19</mockito-version>
<jackson-2-version>2.5.0</jackson-2-version>
<jersey-2-version>2.10</jersey-2-version>
<lsp4j-version>0.7.2</lsp4j-version>
<!-- NOTE: Reactor version must match version used by the CF client -->
<cloudfoundry-client-version>3.8.0.RELEASE</cloudfoundry-client-version>
<reactor-version>3.1.5.RELEASE</reactor-version>
<reactor-netty>0.7.5.RELEASE</reactor-netty>
<commons-io-version>2.4</commons-io-version>
<commons-codec-version>1.11</commons-codec-version>
<slf4j-version>1.7.25</slf4j-version>
<sprotty-version>0.7.0-SNAPSHOT</sprotty-version>
<elk-version>0.6.0-SNAPSHOT</elk-version>
</properties>
<build>

View File

@@ -53,4 +53,22 @@
font-family: SansSerif;
font-size: 14pt;
text-anchor: middle;
}
#sprotty-popup {
max-width: 600px;
}
.sprotty-popup table {
font-size: 8pt;
font-family: 'Courier New', Courier, monospace;
word-wrap: break-word;
word-break: break-all;
}
.sprotty-popup table td {
vertical-align: top;
text-align: left;
border: 1px dotted gray;
white-space: pre-wrap;
}

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>sprotty Circles Example</title>
<title>Spring Integration Graph</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css">
<link rel="stylesheet" href="css/page.css">
<!-- support Microsoft browsers -->
@@ -13,10 +13,9 @@
<div class="container">
<div class="row" id="sprotty-app" client-id="spring-boot">
<div class="col-md-10">
<h1>sprotty Circles Example</h1>
<h1>Spring Integration Graph</h1>
<p>
<button id="refresh">Refresh</button>
<button id="scrambleNodes">Scramble nodes</button>
</p>
</div>
<div class="help col-md-2">
@@ -32,7 +31,6 @@
</div>
</div>
</div>
</div>
<script src="bundle.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
</body>

View File

@@ -1,7 +1,30 @@
package org.springframework.ide.si.view;
import org.springframework.ide.si.view.json.SpringIntegrationGraph;
import java.net.URL;
import org.apache.commons.io.IOUtils;
import org.springframework.ide.si.view.json.SpringIntegrationGraphJson;
import org.springframework.util.Assert;
import com.google.gson.Gson;
@FunctionalInterface
public interface GraphDataProvider {
SpringIntegrationGraph getGraph() throws Exception;
SpringIntegrationGraphJson getGraph() throws Exception;
static GraphDataProvider fromClasspathResource(String path) {
if (!path.startsWith("/")) {
path = "/"+path;
}
URL url = GraphDataProvider.class.getResource(path);
Assert.notNull(url, "Resource not found: "+path);
return fromUrl(GraphDataProvider.class.getResource(path));
}
static GraphDataProvider fromUrl(URL resource) {
return () -> {
String jsonString = IOUtils.toString(resource);
return new Gson().fromJson(jsonString, SpringIntegrationGraphJson.class);
};
}
}

View File

@@ -1,40 +0,0 @@
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;
}
}

View File

@@ -1,35 +0,0 @@
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<SModelElement> children = new ArrayList<>();
popup.setChildren(children);
PreRenderedElement content = new PreRenderedElement();
children.add(content);
content.setId("popup-title");
content.setCode("<div class=\"sprotty-popup-title\">Hello World!</div>");
return popup;
}
}

View File

@@ -2,11 +2,10 @@ 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;
import org.springframework.ide.si.view.json.SpringIntegrationGraphJson;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
public class MockGraphData implements GraphDataProvider {
@@ -325,12 +324,10 @@ public class MockGraphData implements GraphDataProvider {
"}";
@Override
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);
public SpringIntegrationGraphJson getGraph() throws IOException {
Gson gson = new Gson().newBuilder().setPrettyPrinting().create();
JsonObject json = gson.fromJson(data, JsonObject.class);
System.out.println(gson.toJson(json));
return gson.fromJson(json, SpringIntegrationGraphJson.class);
}
}

View File

@@ -23,9 +23,9 @@ import org.eclipse.sprotty.SShapeElement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ide.si.view.json.SpringIntegrationEdge;
import org.springframework.ide.si.view.json.SpringIntegrationGraph;
import org.springframework.ide.si.view.json.SpringIntegrationNode;
import org.springframework.ide.si.view.json.SpringIntegrationEdgeJson;
import org.springframework.ide.si.view.json.SpringIntegrationGraphJson;
import org.springframework.ide.si.view.json.SpringIntegrationNodeJson;
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;
@@ -62,10 +62,21 @@ public class SIDiagramGenerator implements DiagramGenerator {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
Map<String, SpringIntegrationNodeJson> nodesById = new HashMap<>();
/**
* Fetch node data for given node id. This data is retrieved from the
* model produced the last time 'generateModel' was called.
*/
public synchronized SpringIntegrationNodeJson getNodeData(String nodeId) {
return nodesById.get(nodeId);
}
@Override
public SModelRoot generateModel(String clientId, RequestModelAction modelRequest) {
public synchronized SModelRoot generateModel(String clientId, RequestModelAction modelRequest) {
nodesById.clear();
try {
SpringIntegrationGraph jsonData = graphDataProvider.getGraph();
SpringIntegrationGraphJson jsonData = graphDataProvider.getGraph();
return toSprottyGraph(jsonData);
} catch (Exception e) {
log.error("", e);
@@ -73,7 +84,7 @@ public class SIDiagramGenerator implements DiagramGenerator {
}
}
private SGraph toSprottyGraph(SpringIntegrationGraph json) throws Exception {
private SGraph toSprottyGraph(SpringIntegrationGraphJson json) throws Exception {
SGraph graph = new SGraph();
graph.setId("root");
graph.setType(TYPE_INTEGRATION_GRAPH);
@@ -81,13 +92,11 @@ public class SIDiagramGenerator implements DiagramGenerator {
List<SModelElement> children = new ArrayList<>();
graph.setChildren(children);
Map<String, SpringIntegrationNode> nodeIds = new HashMap<>();
for (SpringIntegrationNode node : json.getNodes()) {
for (SpringIntegrationNodeJson node : json.getNodes()) {
String id = node.getNodeId()+"";
String name = node.getName();
Assert.isLegal(!nodeIds.containsKey(id));
nodeIds.put(id, node);
Assert.isLegal(!nodesById.containsKey(id));
nodesById.put(id, node);
String type = visualType(node.getComponentType());
switch (type) {
case "channel":
@@ -99,13 +108,13 @@ public class SIDiagramGenerator implements DiagramGenerator {
}
int linkId = 0;
for (SpringIntegrationEdge link : json.getLinks()) {
for (SpringIntegrationEdgeJson link : json.getLinks()) {
String id = "l"+(linkId++);
String sourceId = link.getFrom()+"";
String targetId = link.getTo()+"";
SEdge e = createEdge(id, sourceId, targetId, getEdgeLabel(nodeIds.get(sourceId)), link.getType());
SEdge e = createEdge(id, sourceId, targetId, getEdgeLabel(nodesById.get(sourceId)), link.getType());
children.add(e);
}
//
@@ -122,7 +131,7 @@ public class SIDiagramGenerator implements DiagramGenerator {
return graph;
}
private String getEdgeLabel(SpringIntegrationNode springIntegrationNode) {
private String getEdgeLabel(SpringIntegrationNodeJson springIntegrationNode) {
JsonElement json = gson.toJsonTree(springIntegrationNode);
for (String prop : labelProperty.split("\\.")) {
if (json instanceof JsonObject) {
@@ -161,15 +170,6 @@ public class SIDiagramGenerator implements DiagramGenerator {
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);
@@ -218,7 +218,7 @@ public class SIDiagramGenerator implements DiagramGenerator {
private static SNode createChannelNode(String id, String labelText) {
SNode node = createNode(id, labelText, "channel");
LayoutOptions layoutOptions = new LayoutOptions();
layoutOptions.setMinHeight(CHANNEL_HEIGHT);
// layoutOptions.setMinHeight(CHANNEL_HEIGHT);
layoutOptions.setMinWidth(MIN_WIDTH);
node.setLayoutOptions(layoutOptions);

View File

@@ -0,0 +1,106 @@
package org.springframework.ide.si.view;
import java.util.ArrayList;
import java.util.Map.Entry;
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.beans.factory.annotation.Autowired;
import org.springframework.ide.si.view.json.SpringIntegrationNodeJson;
import org.springframework.ide.vscode.commons.util.HtmlBuffer;
import org.springframework.stereotype.Component;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@Component
public class SpringIntegrationPopupFactory implements IPopupModelFactory {
@Autowired SIDiagramGenerator siDiagram;
@Override
public SModelRoot createPopupModel(SModelElement element, RequestPopupModelAction request, IDiagramServer server) {
SpringIntegrationNodeJson nodeData = siDiagram.getNodeData(request.getElementId());
if (nodeData!=null) {
SGraph popup = new SGraph();
popup.setType("html");
popup.setId("popup");
popup.setCanvasBounds(request.getBounds());
ArrayList<SModelElement> children = new ArrayList<>();
popup.setChildren(children);
addHtmlChild(children, "popup-title", createTitle(nodeData));
addHtmlChild(children, "popup-body", createBody(nodeData));
return popup;
}
return SModelRoot.EMPTY_ROOT;
}
private void addHtmlChild(ArrayList<SModelElement> children, String id, HtmlBuffer html) {
PreRenderedElement content = new PreRenderedElement();
content.setId(id);
content.setCode(html.toString());
children.add(content);
}
protected HtmlBuffer createTitle(SpringIntegrationNodeJson nodeData) {
HtmlBuffer html = new HtmlBuffer();
html.bold("Full Json Data");
return html;
}
protected HtmlBuffer createBody(SpringIntegrationNodeJson _nodeData) {
JsonObject nodeData = (JsonObject) new Gson().toJsonTree(_nodeData);
HtmlBuffer html = new HtmlBuffer();
// html.raw("<table width="100%">");
// html.raw("<tr>");
// html.raw("<td>a</td>");
// html.raw("<td>b</td>");
// html.raw("</tr>");
// html.raw("<tr>");
// html.raw("<td>cc</td>");
// html.raw("<td>de</td>");
// html.raw("</tr>");
// html.raw("</table>");
html.raw("<table width=\"100%\">");
for (Entry<String, JsonElement> e : nodeData.entrySet()) {
String key = e.getKey();
dumpProperty(html, key, nodeData.get(key));
}
html.raw("</table>");
return html;
}
private void dumpProperty(HtmlBuffer html, String key, JsonElement jsonElement) {
if (jsonElement.isJsonPrimitive()) {
String value = jsonElement.getAsString();
html.raw("<tr>");
html.raw("<td>");
html.text(key);
html.raw("</td>");
html.raw("<td>");
html.text(value);
html.raw("</td>");
html.raw("</tr>");
} else if (jsonElement.isJsonArray()) {
JsonArray array = (JsonArray) jsonElement;
for (int i = 0; i < array.size(); i++) {
dumpProperty(html, key+"["+i+"]", array.get(i));
}
} else if (jsonElement.isJsonObject()) {
for (Entry<String, JsonElement> e : jsonElement.getAsJsonObject().entrySet()) {
dumpProperty(html, key+"."+e.getKey(), e.getValue());
}
}
}
}

View File

@@ -1,5 +1,10 @@
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.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@@ -8,14 +13,36 @@ import org.springframework.context.annotation.Bean;
public class SprottySiViewApplication {
@Bean
GraphDataProvider mockGraphDataProvider() {
return new MockGraphData();
GraphDataProvider graphDataProvider() {
// return new MockGraphData();
return GraphDataProvider.fromClasspathResource("/sample.json");
}
@Bean
public SprottyLayoutConfigurator integrationLayoutConfigurator() {
SprottyLayoutConfigurator configurator = new SprottyLayoutConfigurator();
IPropertyHolder holder = configurator.configureByType(SIDiagramGenerator.TYPE_INTEGRATION_GRAPH);
holder.setProperty(LayeredOptions.SPACING_COMPONENT_COMPONENT, 40.0);
holder.setProperty(LayeredOptions.SPACING_NODE_NODE_BETWEEN_LAYERS, 40.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;
}
// @Bean
// GraphDataProvider graphDataProvider() {
//
// }
public static void main(String[] args) {
SpringApplication.run(SprottySiViewApplication.class, args);

View File

@@ -1,6 +1,6 @@
package org.springframework.ide.si.view.json;
public class SpringIntegrationEdge extends PrettyJson {
public class SpringIntegrationEdgeJson extends PrettyJson {
private int from;
private int to;

View File

@@ -1,13 +1,12 @@
package org.springframework.ide.si.view.json;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
public class SpringIntegrationGraph extends PrettyJson {
public class SpringIntegrationGraphJson extends PrettyJson {
private JsonObject contentDescriptor;
private SpringIntegrationNode[] nodes;
private SpringIntegrationEdge[] links;
private SpringIntegrationNodeJson[] nodes;
private SpringIntegrationEdgeJson[] links;
public JsonObject getContentDescriptor() {
return contentDescriptor;
@@ -15,16 +14,16 @@ public class SpringIntegrationGraph extends PrettyJson {
public void setContentDescriptor(JsonObject contentDescriptor) {
this.contentDescriptor = contentDescriptor;
}
public SpringIntegrationNode[] getNodes() {
public SpringIntegrationNodeJson[] getNodes() {
return nodes;
}
public void setNodes(SpringIntegrationNode[] nodes) {
public void setNodes(SpringIntegrationNodeJson[] nodes) {
this.nodes = nodes;
}
public SpringIntegrationEdge[] getLinks() {
public SpringIntegrationEdgeJson[] getLinks() {
return links;
}
public void setLinks(SpringIntegrationEdge[] links) {
public void setLinks(SpringIntegrationEdgeJson[] links) {
this.links = links;
}
}

View File

@@ -2,7 +2,7 @@ package org.springframework.ide.si.view.json;
import com.google.gson.JsonObject;
public class SpringIntegrationNode extends PrettyJson {
public class SpringIntegrationNodeJson extends PrettyJson {
private int nodeId;
private String name;

View File

@@ -281,6 +281,11 @@
}
],
"links": [
{
"from": 5,
"to": 4,
"type": "error"
},
{
"from": 5,
"to": 1,
@@ -305,11 +310,6 @@
"from": 4,
"to": 8,
"type": "input"
},
{
"from": 5,
"to": 4,
"type": "error"
}
]
}