add basic exploration feature

neo4j-4
Josha von Gizycki 5 years ago
parent 4590a345b1
commit d3efb3165e

@ -16,7 +16,7 @@
commons-codec]]
[ring/ring-json "0.4.0"]
[hiccup "1.0.5"]
[joshavg/formulare "0.4.0"]
[joshavg/formulare "0.5.0-SNAPSHOT"]
;; neo4j
[gorillalabs/neo4j-clj "2.0.1"

@ -1,5 +1,6 @@
@ci-blue: Highlight;
@ci-highlight: Highlight;
@error-color: #e00;
@warn-color: #eebc00;
@accent-color: #efefef;
@body-background-color: #fcfcfc;
@background-color: #eee;
@ -40,7 +41,7 @@ h1 {
h2 {
font-size: 1.3rem;
border-bottom: 1px solid @ci-blue;
border-bottom: 1px solid @ci-highlight;
}
em {
@ -57,7 +58,7 @@ img, svg {
}
.__icon {
color: @ci-blue;
color: @ci-highlight;
font-weight: bold;
}
@ -83,7 +84,7 @@ img, svg {
background-position: right;
background-origin: content-box;
background-repeat: no-repeat;
border-bottom: 1px solid @ci-blue;
border-bottom: 1px solid @ci-highlight;
.app-title {
grid-column: 1;
@ -114,7 +115,7 @@ img, svg {
h2::before {
content: "▤ ";
color: @ci-blue;
color: @ci-highlight;
}
h2 {
@ -194,21 +195,12 @@ form {
}
}
.flash--error {
.flash;
border-color: @error-color;
.flash__heading--error {
margin: 0;
background-color: @error-color;
color: white;
margin-top: -.7rem;
margin-left: -.7rem;
margin-right: -.7rem;
}
.delete-btn.__on-countdown {
font-weight: bold;
}
}
.flash {
.flash {
grid-column: 1 e("/") 3;
.thin-border;
border-left-width: @accent-border-width;
@ -221,10 +213,27 @@ form {
.flash__pred {
font-family: monospace;
}
}
.flash--error {
.flash;
border-color: @error-color;
.flash__heading--error {
background-color: @error-color;
color: white;
margin: -.7rem -.7rem 0;
}
}
.delete-btn.__on-countdown {
font-weight: bold;
.flash--warn {
.flash;
border-color: @warn-color;
.flash__heading--error {
background-color: @warn-color;
color: white;
margin: -.7rem -.7rem 0;
}
}
@ -325,7 +334,12 @@ table {
.single-tag {
border: 1px solid @ci-color;
border-left: .5rem solid @ci-blue;
border-left: .5rem solid @ci-highlight;
border-radius: .5rem;
padding: .1rem .4rem;
}
.vis-canvas {
border: 1px solid @ci-highlight;
height: 45rem;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1,82 @@
document.addEventListener('DOMContentLoaded', function () {
const canvas = document.querySelector('.vis-canvas')
const nodes = new vis.DataSet([])
const edges = new vis.DataSet([])
const network = new vis.Network(canvas, {
nodes: nodes,
edges: edges
}, {
manipulation: true
})
const instanceLabel = (inst, schema) => `${inst.name}\n${schema.name}`
const addNode = (toAdd) => {
try {
nodes.add({
id: toAdd.uuid,
label: instanceLabel(toAdd, toAdd.schema)
})
} catch(e) {
console.debug(e)
}
}
const addOutEdges = (fromId, toAdd) => {
toAdd.forEach((inst) => {
try {
edges.add({
from: fromId,
to: inst.uuid,
label: inst['link-name'],
arrows: {
to: {enabled: true}
}
})
} catch (e) {
console.debug(e)
}
})
}
const addLinkedNodes = (toAdd) => {
toAdd.forEach((inst) => {
try {
console.debug(inst.name)
nodes.add({
id: inst.uuid,
label: instanceLabel(inst, inst.schema)
})
} catch (e) {
console.debug(e)
}
})
}
const addInstance = (instance) => {
addNode(instance)
addLinkedNodes(instance['links-out'])
addOutEdges(instance.uuid, instance['links-out'])
}
fetch(canvas.getAttribute('data-instance-url'))
.then((resp) => resp.json())
.then((json) => {
addInstance(json)
})
const searchForm = document.querySelector('.search')
const searchUrl = searchForm.getAttribute('action')
searchForm.addEventListener('submit', (event) => {
event.preventDefault()
const term = searchForm.querySelector('[name=term]').value
fetch(searchUrl + encodeURIComponent(term))
.then((resp) => resp.json())
.then((json) => {
console.debug(json)
json.forEach((inst) => {
addInstance(inst)
})
})
})
})

@ -1,5 +1,6 @@
(ns wanijo.visualisation.domain
(:require [wanijo.framework.neo4j :as neo4j]))
(:require [wanijo.framework.neo4j :as neo4j]
[wanijo.instance.domain :as domain-instance]))
(neo4j/defquery all-instance-connections
"MATCH
@ -8,6 +9,28 @@
(source)<-[:link_from]-(link:link)-[:link_to]->(target:instance),
(target)-[:of]->(target_schema:schema)
RETURN source, schema, link, target, target_schema")
(defn all-instance-connections! []
(neo4j/exec-query! all-instance-connections {}))
(neo4j/defquery search
"MATCH (i:instance)
WHERE i.name =~ {term}
RETURN i
LIMIT 10")
(defn enrich-links [instance]
(update instance
:links-out
(fn [links-in]
(map #(assoc
(domain-instance/full-instance-by-uuid!
(-> % :target :uuid))
:link-name
(-> % :label :name))
links-in))))
(defn search! [term]
(->> (neo4j/exec-query! search {:term (str ".*" term ".*")})
(map #(domain-instance/full-instance-by-uuid! (-> % :i :uuid)))
(map enrich-links)))
(defn instance! [uuid]
(enrich-links (domain-instance/full-instance-by-uuid! uuid)))

@ -22,13 +22,29 @@
{:content-type "text/json"
:body (domain-schema/accessible-schemas! (-> req :session :uuid))})
(defn instance [uuid]
{:content-type "text/json"
:body (vis-domain/instance! uuid)})
(defn search [term]
{:content-type "text/json"
:body (map #(merge %
{:urls {:instance (path :vis-get-instance %)}})
(vis-domain/search! term))})
(defroutes routes
(GET (register! :vis-explore "/vis/explore/:instance-uuid")
[:as req]
(vis-view/index req))
[instance-uuid :as req]
(vis-view/index instance-uuid req))
(GET (register! :vis-all-instances "/vis/all-instances")
[:as req]
(all-instances req))
(GET (register! :vis-all-schemas "/api/vis/schemas")
[:as req]
(all-schemas req)))
(all-schemas req))
(GET (register! :vis-get-instance "/api/vis/instance/:uuid")
[uuid]
(instance uuid))
(GET (register! :vis-search-instance "/api/vis/search/:term")
[term]
(search term)))

@ -1,10 +1,22 @@
(ns wanijo.visualisation.view
(:require [hiccup.page :refer [include-js]]
(:require [hiccup.page :refer [include-js include-css]]
[hiccup.form :as hform]
[wanijo.framework.routing :refer [path]]
[wanijo.framework.view :as view]))
(defn index [req]
(defn index [instance-uuid req]
(view/layout!
:request req
:head (list (include-css "/css/vis-network.min.css")
(include-js "/js/vis-network.min.js"
"/js/vis.js"))
:content
[[:h1 "Visualisation"]
[:div#visualisation]]))
[[:h1 "Explore"]
[:form.search {:action (path :vis-search-instance {:term ""})}
(hform/label "term" "Search Instance")
(hform/text-field {:autofocus true} "term")
[:input {:style "display: none" :type "submit"}]]
[:div.vis-canvas {:data-instance-url (path :vis-get-instance
{:uuid instance-uuid})}]
[:p.flash--warn
"Changes to the visualisation will not (yet) mirror to the actual database"]]))

Loading…
Cancel
Save