Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ARTIF-467 UI relationship visualizations #517

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -93,6 +93,8 @@
"artifact-details.tab.overview" : "Overview",
"artifact-details.tab.relationships" : "Relationships",
"artifact-details.tab.comments" : "Comments",
"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 Down Expand Up @@ -47,8 +48,13 @@
import org.artificer.ui.client.local.widgets.common.EditableInlineLabel;
import org.artificer.ui.client.shared.beans.ArtifactBean;
import org.artificer.ui.client.shared.beans.ArtifactCommentBean;
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 @@ -190,6 +196,20 @@ public class ArtifactDetailsPage extends AbstractPage {
CommentsPanel comments;
protected boolean commentsLoaded;

// 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 @@ -260,6 +280,25 @@ public void onClick(ClickEvent event) {
}
});

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 @@ -304,14 +343,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 @@ -345,6 +384,7 @@ public void onReturn(Void data) {
i18n.format("artifact-details.delete-success-msg", artifact.getModel().getName()));
backToArtifacts.click();
}

@Override
public void onError(Throwable error) {
notificationService.completeProgressNotification(notificationBean.getUuid(),
Expand Down Expand Up @@ -443,19 +483,171 @@ public void reload() {
}

private void doLoadRelationships(final ArtifactBean artifact) {
artifactService.getRelationships(artifact.getUuid(), artifact.getType(), new IServiceInvocationHandler<ArtifactRelationshipsIndexBean>() {
artifactService.getRelationships(artifact.getUuid(), new IServiceInvocationHandler<ArtifactRelationshipsIndexBean>() {
@Override
public void onReturn(ArtifactRelationshipsIndexBean data) {
relationships.setValue(data.getRelationships());
reverseRelationships.setValue(data.getReverseRelationships());
}

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

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).getTargetUuid();
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.getTargetUuid();
if (!processedRels.contains(processedKey)) {
addRelationshipsGraphLink(node.getArtifact().getUuid(), rel.getTargetUuid(), 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.getTargetUuid() + ":" + node.getArtifact().getUuid();
if (!processedRels.contains(processedKey)) {
addRelationshipsGraphLink(node.getArtifact().getUuid(), rel.getTargetUuid(), 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);
}-*/;

/**
* Called when the user clicks the Add Relationship button.
* @param event
Expand Down Expand Up @@ -496,6 +688,7 @@ public void onReturn(String data) {
editorWrapper.removeAttribute("style");
sourceLoaded = true;
}

@Override
public void onError(Throwable error) {
notificationService.sendErrorNotification(i18n.format("Error getting artifact content."), error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import org.artificer.ui.client.shared.beans.ArtifactCommentBean;
import org.artificer.ui.client.shared.beans.ArtifactRelationshipsBean;
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;
Expand Down Expand Up @@ -75,15 +77,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