Nhogs/popoto

View on GitHub
src/cypherviewer/cypherviewer.js

Summary

Maintainability
F
1 wk
Test Coverage
F
7%
import * as d3 from "d3";
import dataModel from "../datamodel/dataModel";
import queryviewer from "../queryviewer/queryviewer";
import provider from "../provider/provider";
import query from "../query/query";
import graph from "../graph/graph";

var cypherviewer = {};
cypherviewer.containerId = "popoto-cypher";
cypherviewer.MATCH = "MATCH";
cypherviewer.RETURN = "RETURN";
cypherviewer.WHERE = "WHERE";
cypherviewer.QueryElementTypes = Object.freeze({
    KEYWORD: 0,
    NODE: 1,
    SEPARATOR: 2,
    SOURCE: 3,
    LINK: 4,
    TARGET: 5,
    RETURN: 6,
    WHERE: 7
});

/**
 * Create the Cypher viewer area.
 *
 */
cypherviewer.createQueryArea = function () {
    var id = "#" + cypherviewer.containerId;

    cypherviewer.querySpanElements = d3.select(id).append("p").attr("class", "ppt-query-constraint-elements").selectAll(".queryConstraintSpan");
};

/**
 * Update all the elements displayed on the cypher viewer based on current graph.
 */
cypherviewer.updateQuery = function () {

    // Remove all query span elements
    cypherviewer.querySpanElements = cypherviewer.querySpanElements.data([]);

    cypherviewer.querySpanElements.exit().remove();

    // Update data
    cypherviewer.querySpanElements = cypherviewer.querySpanElements.data(cypherviewer.generateData(dataModel.links, dataModel.nodes));

    // Remove old span (not needed as all have been cleaned before)
    // queryviewer.querySpanElements.exit().remove();

    // Add new span
    cypherviewer.querySpanElements = cypherviewer.querySpanElements.enter().append("span")
        .attr("id", function (d) {
            return "cypher-" + d.id;
        })
        .on("mouseover", cypherviewer.mouseOverSpan)
        .on("mouseout", cypherviewer.mouseOutSpan)
        .on("contextmenu", cypherviewer.rightClickSpan)
        .on("click", cypherviewer.clickSpan)
        .merge(cypherviewer.querySpanElements);

    // Update all spans:
    cypherviewer.querySpanElements.filter(function (d) {
        return d.type === cypherviewer.QueryElementTypes.KEYWORD;
    })
        .attr("class", "ppt-span")
        .text(function (d) {
            return " " + d.value + " ";
        });

    cypherviewer.querySpanElements.filter(function (d) {
        return d.type === cypherviewer.QueryElementTypes.SEPARATOR;
    })
        .attr("class", "ppt-span")
        .text(function (d) {
            return d.value + " ";
        });

    cypherviewer.querySpanElements.filter(function (d) {
        return d.type === cypherviewer.QueryElementTypes.NODE;
    })
        .attr("class", function (d) {
            if (d.node.value !== undefined && d.node.value.length > 0) {
                return "ppt-span-root-value";
            } else {
                return "ppt-span-root";
            }
        })
        .text(function (d) {
            return "(" + d.node.internalLabel + ":`" + d.node.label + "`)";
        });

    cypherviewer.querySpanElements.filter(function (d) {
        return d.type === cypherviewer.QueryElementTypes.SOURCE;
    })
        .attr("class", function (d) {
            if (d.node === dataModel.getRootNode()) {
                if (d.node.value !== undefined && d.node.value.length > 0) {
                    return "ppt-span-root-value";
                } else {
                    return "ppt-span-root";
                }
            } else {
                if (d.node.value !== undefined && d.node.value.length > 0) {
                    return "ppt-span-value";
                } else {
                    return "ppt-span-choose";
                }
            }
        })
        .text(function (d) {
            var sourceNode = d.node;
            return "(" + sourceNode.internalLabel + ":`" + sourceNode.label + "`)";
        });

    cypherviewer.querySpanElements.filter(function (d) {
        return d.type === cypherviewer.QueryElementTypes.LINK;
    })
        .attr("class", "ppt-span-link")
        .text(function (d) {
            if (d.link.target.isParentRelReverse === true) {
                return "<-[:`" + d.link.label + "`]-";
            } else {
                return "-[:`" + d.link.label + "`]-" + (query.USE_RELATION_DIRECTION ? ">" : "");
            }
        });

    cypherviewer.querySpanElements.filter(function (d) {
        return d.type === cypherviewer.QueryElementTypes.TARGET;
    })
        .attr("class", function (d) {
            if (d.node.value !== undefined && d.node.value.length > 0) {
                return "ppt-span-value";
            } else {
                return "ppt-span-choose";
            }
        })
        .text(function (d) {
            return "(" + d.node.internalLabel + ":`" + d.node.label + "`)";
        });

    cypherviewer.querySpanElements.filter(function (d) {
        return d.type === cypherviewer.QueryElementTypes.WHERE;
    })
        .attr("class", function (d) {
            if (d.node === dataModel.getRootNode()) {
                return "ppt-span-root-value";
            } else {
                return "ppt-span-value";
            }
        })
        .text(function (d) {
            var node = d.node;
            if (node.isNegative === true) {
                if (!node.hasOwnProperty("value") || node.value.length <= 0) {
                    return "(NOT (" + d.link.source.internalLabel + ":`" + d.link.source.label + "`)-[:`" + d.link.label + "`]->(:`" + d.link.target.label + "`))";
                } else {
                    var clauses = [];
                    var constAttr = provider.node.getConstraintAttribute(node.label);
                    node.value.forEach(function (value) {
                        clauses.push(
                            "(NOT (" + d.link.source.internalLabel + ":`" + d.link.source.label + "`)-[:`" + d.link.label + "`]->(:`" + d.link.target.label + "`{" + constAttr + ":" + value.attributes[constAttr] + "}))"
                        );
                    });

                    return clauses.join(" AND ");
                }
            } else {
                var constraintAttr = provider.node.getConstraintAttribute(node.label);

                var text = "";
                var separator = "";

                if (constraintAttr === query.NEO4J_INTERNAL_ID) {
                    text += node.internalLabel + ".id";
                } else {
                    text += node.internalLabel + "." + constraintAttr;
                }

                if (node.hasOwnProperty("value") && node.value.length > 1) {
                    text += " IN [";
                } else {
                    text += " = ";
                }

                node.value.forEach(function (value) {
                    text += separator;
                    separator = ", ";
                    if (constraintAttr === query.NEO4J_INTERNAL_ID) {
                        text += value.internalID;
                    } else {
                        var constraintValue = value.attributes[constraintAttr];
                        if (typeof constraintValue === "boolean" || typeof constraintValue === "number") {
                            text += constraintValue;
                        } else {
                            text += "\"" + constraintValue + "\"";
                        }
                    }
                });

                if (node.value.length > 1) {
                    text += "]";
                }

                return "(" + text + ")";
            }
        });

    cypherviewer.querySpanElements.filter(function (d) {
        return d.type === cypherviewer.QueryElementTypes.RETURN;
    })
        .attr("class", function (d) {
            if (d.node.value !== undefined && d.node.value.length > 0) {
                return "ppt-span-root-value";
            } else {
                return "ppt-span-root";
            }
        })
        .text(function (d) {
            return d.node.internalLabel;
        });

};

/**
 * Generate the data from graph to use in cypher query viewer.
 * The returned data is a list of elements representing the current query.
 * Example:
 *
 * MATCH
 * (person:`Person`),
 * (person:`Person`)-[:`FOLLOWS`]->(person1:`Person`{`name`:\"Jessica Thompson\"}),
 * (person1:`Person`)-[:`REVIEWED`]->(movie5:`Movie`{`title`:\"The Replacements\"})
 * RETURN person
 *
 * @param links
 * @returns {Array}
 */
cypherviewer.generateData = function (links) {
    var elmts = [], id = 0;
    var rootNode = dataModel.getRootNode();
    var relevantLinks = query.getRelevantLinks(rootNode, rootNode, links);
    var negativeLinks = relevantLinks.filter(function (rl) {
        return rl.target.isNegative === true && (!rl.target.hasOwnProperty("value") || rl.target.value.length <= 0)
    });

    elmts.push(
        {
            id: id++,
            type: cypherviewer.QueryElementTypes.KEYWORD,
            value: cypherviewer.MATCH
        }
    );

    if (rootNode) {
        elmts.push(
            {
                id: id++,
                type: cypherviewer.QueryElementTypes.NODE,
                node: rootNode
            }
        );
    }

    if (relevantLinks.length > 0 && relevantLinks.length > negativeLinks.length) {
        elmts.push(
            {
                id: id++,
                type: cypherviewer.QueryElementTypes.SEPARATOR,
                value: ","
            }
        );
    }

    for (var i = 0; i < relevantLinks.length; i++) {
        var relevantLink = relevantLinks[i];

        if (relevantLink.target.isNegative === true && (!relevantLink.target.hasOwnProperty("value") || relevantLink.target.value.length <= 0)) {
            // element not added here but in WHERE
        } else {
            elmts.push(
                {
                    id: id++,
                    type: cypherviewer.QueryElementTypes.SOURCE,
                    node: relevantLink.source
                }
            );

            elmts.push(
                {
                    id: id++,
                    type: cypherviewer.QueryElementTypes.LINK,
                    link: relevantLink
                }
            );

            elmts.push(
                {
                    id: id++,
                    type: cypherviewer.QueryElementTypes.TARGET,
                    node: relevantLink.target
                }
            );

            // Add separator except for last element
            if (i < (relevantLinks.length - 1)) {
                elmts.push(
                    {
                        id: id++,
                        type: cypherviewer.QueryElementTypes.SEPARATOR,
                        value: ","
                    }
                );
            }
        }
    }

    if ((rootNode && rootNode.value !== undefined && rootNode.value.length > 0) || (relevantLinks.length > 0)) {
        elmts.push(
            {
                id: id++,
                type: cypherviewer.QueryElementTypes.KEYWORD,
                value: cypherviewer.WHERE
            }
        );
    }

    if (rootNode && rootNode.value !== undefined && rootNode.value.length > 0) {
        elmts.push(
            {
                id: id++,
                type: cypherviewer.QueryElementTypes.WHERE,
                node: rootNode
            }
        );

        if (relevantLinks.length > 0) {
            elmts.push(
                {
                    id: id++,
                    type: cypherviewer.QueryElementTypes.SEPARATOR,
                    value: " AND "
                }
            );
        }
    }

    var needSeparator = false;
    for (var i = 0; i < relevantLinks.length; i++) {
        var relevantLink = relevantLinks[i];
        if (relevantLink.target.isNegative === true) {
            if (needSeparator) {
                elmts.push(
                    {
                        id: id++,
                        type: cypherviewer.QueryElementTypes.SEPARATOR,
                        value: " AND "
                    }
                );
            }
            elmts.push(
                {
                    id: id++,
                    type: cypherviewer.QueryElementTypes.WHERE,
                    node: relevantLink.target,
                    link: relevantLink
                }
            );
            needSeparator = true;
        } else {
            if (relevantLink.target.value !== undefined && relevantLink.target.value.length > 0) {

                if (needSeparator) {
                    elmts.push(
                        {
                            id: id++,
                            type: cypherviewer.QueryElementTypes.SEPARATOR,
                            value: " AND "
                        }
                    );
                }

                elmts.push(
                    {
                        id: id++,
                        type: cypherviewer.QueryElementTypes.WHERE,
                        node: relevantLink.target
                    }
                );

                needSeparator = true;
            }
        }
    }

    elmts.push(
        {
            id: id++,
            type: cypherviewer.QueryElementTypes.KEYWORD,
            value: cypherviewer.RETURN
        }
    );

    if (rootNode) {
        elmts.push(
            {
                id: id++,
                type: cypherviewer.QueryElementTypes.RETURN,
                node: rootNode
            }
        );
    }

    return elmts;
};

/**
 *
 */
cypherviewer.mouseOverSpan = function () {
    var hoveredSpan = d3.select(this).data()[0];
    if (hoveredSpan.node) {
        // Hover all spans with same node data
        cypherviewer.querySpanElements.filter(function (d) {
            return d.node === hoveredSpan.node;
        }).classed("hover", true);

        // Highlight node in graph
        var nodeElmt = graph.svg.selectAll("#" + graph.node.gID + " > g").filter(function (d) {
            return d === hoveredSpan.node;
        });
        nodeElmt.select(".ppt-g-node-background").selectAll("circle").transition().style("fill-opacity", 0.5);

        // Highlight query viewer
        if (queryviewer.isActive) {
            queryviewer.queryConstraintSpanElements.filter(function (d) {
                return d.ref === hoveredSpan.node;
            }).classed("hover", true);

            queryviewer.querySpanElements.filter(function (d) {
                return d.ref === hoveredSpan.node;
            }).classed("hover", true);
        }
    } else if (hoveredSpan.link) {
        d3.select(this).classed("hover", true);

        // Highlight link in graph
        var linkElmt = graph.svg.selectAll("#" + graph.link.gID + " > g").filter(function (d) {
            return d === hoveredSpan.link;
        });
        linkElmt.select("path").classed("ppt-link-hover", true);
        linkElmt.select("text").classed("ppt-link-hover", true);
    }
};

cypherviewer.mouseOutSpan = function () {
    var hoveredSpan = d3.select(this).data()[0];
    if (hoveredSpan.node) {
        // Remove hover on all spans with same node data
        cypherviewer.querySpanElements.filter(function (d) {
            return d.node === hoveredSpan.node;
        }).classed("hover", false);

        // Remove highlight on node in graph
        var nodeElmt = graph.svg.selectAll("#" + graph.node.gID + " > g").filter(function (d) {
            return d === hoveredSpan.node;
        });
        nodeElmt.select(".ppt-g-node-background").selectAll("circle").transition().style("fill-opacity", 0);

        if (queryviewer.isActive) {
            queryviewer.queryConstraintSpanElements.filter(function (d) {
                return d.ref === hoveredSpan.node;
            }).classed("hover", false);

            queryviewer.querySpanElements.filter(function (d) {
                return d.ref === hoveredSpan.node;
            }).classed("hover", false);
        }
    } else if (hoveredSpan.link) {
        d3.select(this).classed("hover", false);

        // Remove highlight on link in graph
        var linkElmt = graph.svg.selectAll("#" + graph.link.gID + " > g").filter(function (d) {
            return d === hoveredSpan.link;
        });
        linkElmt.select("path").classed("ppt-link-hover", false);
        linkElmt.select("text").classed("ppt-link-hover", false);
    }
};

cypherviewer.clickSpan = function () {
    var clickedSpan = d3.select(this).data()[0];

    if (clickedSpan.node) {
        var nodeElmt = graph.svg.selectAll("#" + graph.node.gID + " > g").filter(function (d) {
            return d === clickedSpan.node;
        });

        nodeElmt.on("click").call(nodeElmt.node(), clickedSpan.node);
    }
};

cypherviewer.rightClickSpan = function () {
    var clickedSpan = d3.select(this).data()[0];

    if (clickedSpan.node) {
        var nodeElmt = graph.svg.selectAll("#" + graph.node.gID + " > g").filter(function (d) {
            return d === clickedSpan.node;
        });

        nodeElmt.on("contextmenu").call(nodeElmt.node(), clickedSpan.node);
    }
};

export default cypherviewer