Skip to content

Commit

Permalink
ARTIF-467 UI relationship visualizations
Browse files Browse the repository at this point in the history
  • Loading branch information
brmeyer committed May 19, 2015
1 parent 2829a00 commit 2bf84e1
Show file tree
Hide file tree
Showing 16 changed files with 2,229 additions and 28 deletions.
2 changes: 2 additions & 0 deletions ui/src/main/java/org/artificer/ui/client/local/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@
"artifact-details.modify-classifiers" : "Modify Classifiers",
"artifact-details.tab.overview" : "Overview",
"artifact-details.tab.relationships" : "Relationships",
"artifact-details.tab.relationships-graph" : "Relationships Graph",
"artifact-details.tab.relationships-tree" : "Relationships Tree",
"artifact-details.tab.source" : "Source",
"artifact-details.type" : "Type:",
"artifact-details.uuid" : "UUID:",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.artificer.ui.client.local.pages;

import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
Expand All @@ -40,8 +41,13 @@
import org.artificer.ui.client.local.util.DataBindingDateConverter;
import org.artificer.ui.client.local.widgets.common.EditableInlineLabel;
import org.artificer.ui.client.shared.beans.ArtifactBean;
import org.artificer.ui.client.shared.beans.ArtifactRelationshipBean;
import org.artificer.ui.client.shared.beans.ArtifactRelationshipsIndexBean;
import org.artificer.ui.client.shared.beans.ArtifactSummaryBean;
import org.artificer.ui.client.shared.beans.NotificationBean;
import org.artificer.ui.client.shared.beans.RelationshipGraphBean;
import org.artificer.ui.client.shared.beans.RelationshipGraphNodeBean;
import org.artificer.ui.client.shared.beans.RelationshipTreeBean;
import org.jboss.errai.databinding.client.api.DataBinder;
import org.jboss.errai.databinding.client.api.InitialState;
import org.jboss.errai.databinding.client.api.PropertyChangeEvent;
Expand Down Expand Up @@ -162,6 +168,20 @@ public class ArtifactDetailsPage extends AbstractPage {
InlineLabel reverseRelationshipsHeader;
protected boolean relationshipsLoaded;

// Relationships Graph tab
@Inject @DataField("sramp-artifact-tabs-relationships-graph")
Anchor relationshipsGraphTabAnchor;
@Inject @DataField("relationships-graph-tab-progress")
HtmlSnippet relationshipsGraphTabProgress;
protected boolean relationshipsGraphLoaded;

// Relationships Graph tab
@Inject @DataField("sramp-artifact-tabs-relationships-tree")
Anchor relationshipsTreeTabAnchor;
@Inject @DataField("relationships-tree-tab-progress")
HtmlSnippet relationshipsTreeTabProgress;
protected boolean relationshipsTreeLoaded;

// Source tab
@Inject @DataField("sramp-artifact-tabs-source")
Anchor sourceTabAnchor;
Expand Down Expand Up @@ -231,6 +251,24 @@ public void onClick(ClickEvent event) {
relationshipsTabAnchor.setFocus(false);
}
});
relationshipsGraphTabAnchor.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
if (!relationshipsGraphLoaded) {
loadRelationshipsGraph(currentArtifact);
}
relationshipsGraphTabAnchor.setFocus(false);
}
});
relationshipsTreeTabAnchor.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
if (!relationshipsTreeLoaded) {
loadRelationshipsTree(currentArtifact);
}
relationshipsTreeTabAnchor.setFocus(false);
}
});
sourceTabAnchor.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
Expand Down Expand Up @@ -265,14 +303,14 @@ public void onError(Throwable error) {
@EventHandler("add-property-button")
protected void onAddProperty(ClickEvent event) {
AddCustomPropertyDialog dialog = addPropertyDialogFactory.get();
dialog.addValueChangeHandler(new ValueChangeHandler<Map.Entry<String,String>>() {
dialog.addValueChangeHandler(new ValueChangeHandler<Map.Entry<String, String>>() {
@Override
public void onValueChange(ValueChangeEvent<Entry<String, String>> event) {
Entry<String, String> value = event.getValue();
if (value != null) {
String propName = value.getKey();
String propValue = value.getValue();
Map<String, String> newProps = new HashMap<String,String>(artifact.getModel().getProperties());
Map<String, String> newProps = new HashMap<String, String>(artifact.getModel().getProperties());
newProps.put(propName, propValue);
customProperties.setValue(newProps, true);
}
Expand Down Expand Up @@ -306,6 +344,7 @@ public void onReturn(Void data) {
i18n.format("artifact-details.delete-success-msg", artifact.getModel().getName())); //$NON-NLS-1$
backToArtifacts.click();
}

@Override
public void onError(Throwable error) {
notificationService.completeProgressNotification(notificationBean.getUuid(),
Expand Down Expand Up @@ -343,7 +382,7 @@ public void onValueChange(ValueChangeEvent<List<String>> event) {
protected void loadRelationships(final ArtifactBean artifact) {
relationships.setVisible(false);
relationshipsTabProgress.setVisible(true);
artifactService.getRelationships(artifact.getUuid(), artifact.getType(), new IServiceInvocationHandler<ArtifactRelationshipsIndexBean>() {
artifactService.getRelationships(artifact.getUuid(), new IServiceInvocationHandler<ArtifactRelationshipsIndexBean>() {
@Override
public void onReturn(ArtifactRelationshipsIndexBean data) {
relationshipsHeader.setText("Targeted by " + artifact.getName());
Expand All @@ -356,13 +395,165 @@ public void onReturn(ArtifactRelationshipsIndexBean data) {
reverseRelationships.setVisible(true);
relationshipsLoaded = true;
}

@Override
public void onError(Throwable error) {
notificationService.sendErrorNotification(i18n.format("artifact-details.error-getting-relationships"), error); //$NON-NLS-1$
}
});
}

protected void loadRelationshipsGraph(final ArtifactBean artifact) {
relationshipsGraphTabProgress.setVisible(true);
artifactService.getRelationshipsGraph(artifact.getUuid(),
new IServiceInvocationHandler<RelationshipGraphBean>() {
@Override
public void onReturn(RelationshipGraphBean data) {
relationshipsGraphTabProgress.setVisible(false);
relationshipsGraphLoaded = true;

// The following seems a little ridiculous, but we need to separate the processing into
// multiple passes. It seems like the visualization JS package has issues if the nodes are not
// given in parent-first, children-second order. Further, we want to favor source->target
// relationships first, then fill in with reverse relationships only when they don't duplicate
// the former.

// 1.) Generate the primary artifact nodes.
for (RelationshipGraphNodeBean node : data.getGraph()) {
if (!node.getArtifact().isDerived()) {
// primary artifacts are the root nodes
addRelationshipsGraphNode(node.getArtifact(), null);
}
}

// 2.) Generate the derived artifact nodes.
for (RelationshipGraphNodeBean node : data.getGraph()) {
ArtifactRelationshipsIndexBean relIndex = node.getRelationships();
if (node.getArtifact().isDerived()) {
// derived artifacts have a parent -- find it using the 'relatedDocument' relationship
for (String relType : relIndex.getRelationships().keySet()) {
if (relType.equalsIgnoreCase("relatedDocument")) {
// should only be one...
String parent = relIndex.getRelationships().get(relType).getRelationships()
.get(0).getUuid();
addRelationshipsGraphNode(node.getArtifact(), parent);
}
}
}
}

// 3.) Generate source->target relationships.
List<String> processedRels = new ArrayList<>();
for (RelationshipGraphNodeBean node : data.getGraph()) {
ArtifactRelationshipsIndexBean relIndex = node.getRelationships();
for (String relType : relIndex.getRelationships().keySet()) {
if (!relType.equalsIgnoreCase("relatedDocument")) {
for (ArtifactRelationshipBean rel : relIndex.getRelationships().get(relType).getRelationships()) {
String processedKey = node.getArtifact().getUuid() + ":" + rel.getUuid();
if (!processedRels.contains(processedKey)) {
addRelationshipsGraphLink(node.getArtifact().getUuid(), rel.getUuid(), relType);
processedRels.add(processedKey);
}
}
}
}
}

// 4.) Generate target->source reverse relationships.
for (RelationshipGraphNodeBean node : data.getGraph()) {
ArtifactRelationshipsIndexBean relIndex = node.getRelationships();
for (String relType : relIndex.getReverseRelationships().keySet()) {
if (!relType.equalsIgnoreCase("relatedDocument")) {
for (ArtifactRelationshipBean rel : relIndex.getReverseRelationships().get(relType).getRelationships()) {
// Note that this is *backwards* here -- we're primarily concerned with
// duplicating a relationship we've already created above, but here they're
// reversed.
String processedKey = rel.getUuid() + ":" + node.getArtifact().getUuid();
if (!processedRels.contains(processedKey)) {
addRelationshipsGraphLink(node.getArtifact().getUuid(), rel.getUuid(), relType);
processedRels.add(processedKey);
}
}
}
}
}

buildRelationshipsGraph();
}

@Override
public void onError(Throwable error) {
notificationService.sendErrorNotification(i18n.format("artifact-details.error-getting-relationships"), error); //$NON-NLS-1$
}
});
}

private void addRelationshipsGraphNode(ArtifactSummaryBean artifact, String parent) {
addRelationshipsGraphNode(artifact.getType(), artifact.getUuid(), parent,
artifact.getName() + " (" + artifact.getType() + ")");
}

private native void addRelationshipsGraphNode(String type, String id, String parent, String name) /*-{
$wnd.addRelationshipsGraphNode(type, id, parent, name);
}-*/;

private native void addRelationshipsGraphLink(String source, String target, String relType) /*-{
$wnd.addRelationshipsGraphLink(source, target, 1, relType);
}-*/;

private native void buildRelationshipsGraph() /*-{
$wnd.buildRelationshipsGraph();
}-*/;

protected void loadRelationshipsTree(final ArtifactBean artifact) {
relationshipsTreeTabProgress.setVisible(true);
artifactService.getRelationshipsTree(artifact.getUuid(),
new IServiceInvocationHandler<RelationshipTreeBean>() {
@Override
public void onReturn(RelationshipTreeBean data) {
relationshipsTreeTabProgress.setVisible(false);
relationshipsTreeLoaded = true;

JavaScriptObject relationshipsTree = buildRelationshipsTreeNode(data);
buildRelationshipsTree(relationshipsTree);
}

@Override
public void onError(Throwable error) {
notificationService.sendErrorNotification(i18n.format("artifact-details.error-getting-relationships"), error); //$NON-NLS-1$
}
});
}

protected JavaScriptObject buildRelationshipsTreeNode(RelationshipTreeBean treeNode) {
// TODO: Use description for relationship info?
JavaScriptObject jsNode = buildRelationshipsTreeNode(treeNode.getArtifact().getName(), "");
if (treeNode.getChildren().size() > 0) {
initRelationshipsTreeNodeChildren(jsNode);
for (RelationshipTreeBean childNode : treeNode.getChildren()) {
JavaScriptObject jsChildNode = buildRelationshipsTreeNode(childNode);
addRelationshipsTreeNodeChild(jsNode, jsChildNode);
}
}
return jsNode;
}

private native JavaScriptObject buildRelationshipsTreeNode(String name, String description) /*-{
return {name:name, description:description};
}-*/;

private native void initRelationshipsTreeNodeChildren(JavaScriptObject jsNode) /*-{
jsNode.children = [];
}-*/;

private native void addRelationshipsTreeNodeChild(JavaScriptObject jsNode, JavaScriptObject jsChildNode) /*-{
jsNode.children.push(jsChildNode);
}-*/;

private native void buildRelationshipsTree(JavaScriptObject relationshipsTree) /*-{
$wnd.buildRelationshipsTree(relationshipsTree);
}-*/;

/**
* Loads the artifact's source (async) into the source tab's editor control.
* @param artifact
Expand All @@ -380,6 +571,7 @@ public void onReturn(String data) {
editorWrapper.removeAttribute("style"); //$NON-NLS-1$
sourceLoaded = true;
}

@Override
public void onError(Throwable error) {
notificationService.sendErrorNotification(i18n.format("Error getting artifact content."), error); //$NON-NLS-1$
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,21 @@
*/
package org.artificer.ui.client.local.services;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;

import org.artificer.ui.client.shared.services.IArtifactService;
import org.jboss.errai.common.client.api.Caller;
import org.jboss.errai.common.client.api.ErrorCallback;
import org.jboss.errai.common.client.api.RemoteCallback;
import org.artificer.ui.client.local.services.callback.DelegatingErrorCallback;
import org.artificer.ui.client.local.services.callback.DelegatingRemoteCallback;
import org.artificer.ui.client.local.services.callback.IServiceInvocationHandler;
import org.artificer.ui.client.shared.beans.ArtifactBean;
import org.artificer.ui.client.shared.beans.ArtifactRelationshipsIndexBean;
import org.artificer.ui.client.shared.beans.RelationshipGraphBean;
import org.artificer.ui.client.shared.beans.RelationshipTreeBean;
import org.artificer.ui.client.shared.exceptions.ArtificerUiException;
import org.artificer.ui.client.shared.services.IArtifactService;
import org.jboss.errai.common.client.api.Caller;
import org.jboss.errai.common.client.api.ErrorCallback;
import org.jboss.errai.common.client.api.RemoteCallback;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;

/**
* Client-side service for making Caller calls to the remote artifact service.
Expand Down Expand Up @@ -73,15 +75,33 @@ public void getDocumentContent(String uuid, String artifactType,
}
}

/**
* @see org.artificer.ui.client.shared.services.IArtifactService#getRelationships(String, String)
*/
public void getRelationships(String uuid, String artifactType,
IServiceInvocationHandler<ArtifactRelationshipsIndexBean> handler) {
RemoteCallback<ArtifactRelationshipsIndexBean> successCallback = new DelegatingRemoteCallback<ArtifactRelationshipsIndexBean>(handler);
public void getRelationships(String uuid, IServiceInvocationHandler<ArtifactRelationshipsIndexBean> handler) {
RemoteCallback<ArtifactRelationshipsIndexBean> successCallback = new DelegatingRemoteCallback<>(handler);
ErrorCallback<?> errorCallback = new DelegatingErrorCallback(handler);
try {
remoteArtifactService.call(successCallback, errorCallback).getRelationships(uuid);
} catch (ArtificerUiException e) {
errorCallback.error(null, e);
}
}

public void getRelationshipsGraph(String startUuid,
IServiceInvocationHandler<RelationshipGraphBean> handler) {
RemoteCallback<RelationshipGraphBean> successCallback = new DelegatingRemoteCallback<>(handler);
ErrorCallback<?> errorCallback = new DelegatingErrorCallback(handler);
try {
remoteArtifactService.call(successCallback, errorCallback).getRelationshipGraph(startUuid);
} catch (ArtificerUiException e) {
errorCallback.error(null, e);
}
}

public void getRelationshipsTree(String startUuid,
IServiceInvocationHandler<RelationshipTreeBean> handler) {
RemoteCallback<RelationshipTreeBean> successCallback = new DelegatingRemoteCallback<>(handler);
ErrorCallback<?> errorCallback = new DelegatingErrorCallback(handler);
try {
remoteArtifactService.call(successCallback, errorCallback).getRelationships(uuid, artifactType);
remoteArtifactService.call(successCallback, errorCallback).getRelationshipTree(startUuid);
} catch (ArtificerUiException e) {
errorCallback.error(null, e);
}
Expand Down
Loading

0 comments on commit 2bf84e1

Please sign in to comment.