Skip to content

Commit fd4b92d

Browse files
authored
Merge pull request #187 from JuliaRobotics/twig/177_sentinels_wip
Additional functionality for graph sentinels
2 parents 8f5013c + 684acfe commit fd4b92d

File tree

10 files changed

+259
-83
lines changed

10 files changed

+259
-83
lines changed

Project.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ uuid = "b5cc3c7e-6572-11e9-2517-99fb8daf2f04"
33
version = "0.5.0"
44

55
[deps]
6+
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
67
Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
78
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
89
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
@@ -29,9 +30,9 @@ Requires = "0.5, 0.6, 0.7, 0.8, 0.9, 0.10, 1"
2930
julia = "0.7, 1"
3031

3132
[extras]
32-
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
3333
GraphPlot = "a2cc645c-3eea-5389-862e-a155d0052231"
3434
Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
35+
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
3536

3637
[targets]
3738
test = ["Test", "GraphPlot", "Pkg"]

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ docker pull neo4j
6969
To run the image with user `neo4j` and password `test`:
7070

7171
```bash
72-
docker run --publish=7474:7474 --publish=7687:7687 --env NEO4J_AUTH=neo4j/test neo4j
72+
docker run -d --publish=7474:7474 --publish=7687:7687 --env NEO4J_AUTH=neo4j/test neo4j
7373
```
7474

7575
> **Note** If you just installed docker and having permission issues, please see [this ask Ubuntu forum](https://askubuntu.com/questions/941816/permission-denied-when-running-docker-after-installing-it-as-a-snap).

src/CloudGraphsDFG/CloudGraphsDFG.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Neo4j
2+
using Base64
23

34
# Entities
45
include("entities/CloudGraphsDFG.jl")

src/CloudGraphsDFG/services/CGStructure.jl

Lines changed: 173 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,25 @@ export copySession!
33
# Please be careful with these
44
# With great power comes great "Oh crap, I deleted everything..."
55
export clearSession!!, clearRobot!!, clearUser!!
6-
export createSession, createRobot, createUser
6+
export createSession, createRobot, createUser, createDfgSessionIfNotExist
77
export existsSession, existsRobot, existsUser
88
export getSession, getRobot, getUser
99
export updateSession, updateRobot, updateUser
10-
export listSessions, listRobots, listUsers
10+
export lsSessions, lsRobots, lsUsers
11+
12+
global _invalidIds = ["USER", "ROBOT", "SESSION", "VARIABLE", "FACTOR", "ENVIRONMENT", "PPE", "BIGDATA"]
13+
global _validLabelRegex = r"^[a-zA-Z]\w*$"
14+
15+
function _isValid(id::Union{Symbol, String})::Bool
16+
if typeof(id) == Symbol
17+
id = String(id)
18+
end
19+
return all(t -> t != uppercase(id), _invalidIds) && match(_validLabelRegex, id) != nothing
20+
end
1121

1222
function _isValid(abstractNode::N)::Bool where N <: AbstractCGNode
13-
invalidIds = ["USER", "ROBOT", "SESSION", "VARIABLE", "FACTOR", "ENVIRONMENT"]
14-
return all(t -> t != uppercase(String(abstractNode.id)), invalidIds)
23+
id = String(abstractNode.id)
24+
return all(t -> t != uppercase(id), _invalidIds) && match(_validLabelRegex, id) != nothing
1525
end
1626

1727
# Fastest way I can think to convert the data into a dict
@@ -20,22 +30,43 @@ function _convertNodeToDict(abstractNode::N)::Dict{String, Any} where N <: Abstr
2030
cp = deepcopy(abstractNode)
2131
data = length(cp.data) != 0 ? JSON2.write(cp.data) : "{}"
2232
ser = JSON2.read(JSON2.write(abstractNode), Dict{String, Any})
23-
ser["data"] = data
33+
ser["data"] = base64encode(data)
2434
return ser
2535
end
2636

2737
#TODO: Refactor, #HACK :D (but it works!)
2838
function _convertDictToSession(dict::Dict{String, Any})::Session
29-
sessionData = JSON2.read(dict["data"], Dict{Symbol, String})
39+
data = JSON2.read(String(base64decode(dict["data"])), Dict{Symbol, String})
3040
session = Session(
3141
Symbol(dict["id"]),
3242
Symbol(dict["robotId"]),
3343
Symbol(dict["userId"]),
3444
dict["name"],
3545
dict["description"],
36-
sessionData)
46+
data)
3747
return session
3848
end
49+
#TODO: Refactor, #HACK :D (but it works!)
50+
function _convertDictToRobot(dict::Dict{String, Any})::Robot
51+
data = JSON2.read(String(base64decode(dict["data"])), Dict{Symbol, String})
52+
robot = Robot(
53+
Symbol(dict["id"]),
54+
Symbol(dict["userId"]),
55+
dict["name"],
56+
dict["description"],
57+
data)
58+
return robot
59+
end
60+
#TODO: Refactor, #HACK :D (but it works!)
61+
function _convertDictToUser(dict::Dict{String, Any})::User
62+
data = JSON2.read(String(base64decode(dict["data"])), Dict{Symbol, String})
63+
user = User(
64+
Symbol(dict["id"]),
65+
dict["name"],
66+
dict["description"],
67+
data)
68+
return user
69+
end
3970

4071
function createUser(dfg::CloudGraphsDFG, user::User)::User
4172
Symbol(dfg.userId) != user.id && error("DFG user ID must match user's ID")
@@ -84,11 +115,127 @@ function createSession(dfg::CloudGraphsDFG, session::Session)::Session
84115
return session
85116
end
86117

87-
function listSessions(dfg::CloudGraphsDFG)::Vector{Session}
118+
"""
119+
$(SIGNATURES)
120+
Shortcut method to create the user, robot, and session if it doesn't already exist.
121+
"""
122+
function createDfgSessionIfNotExist(dfg::CloudGraphsDFG)::Session
123+
strip(dfg.userId) == "" && error("User ID is not populated in DFG.")
124+
strip(dfg.robotId) == "" && error("Robot ID is not populated in DFG.")
125+
strip(dfg.sessionId) == "" && error("Session ID is not populated in DFG.")
126+
user = User(Symbol(dfg.userId), dfg.userId, "Description for $(dfg.userId)", Dict{Symbol, String}())
127+
robot = Robot(Symbol(dfg.robotId), Symbol(dfg.userId), dfg.robotId, "Description for $(dfg.userId):$(dfg.robotId)", Dict{Symbol, String}())
128+
session = Session(Symbol(dfg.sessionId), Symbol(dfg.robotId), Symbol(dfg.userId), dfg.sessionId, "Description for $(dfg.userId):$(dfg.robotId):$(dfg.sessionId)", Dict{Symbol, String}())
129+
130+
_getNodeCount(dfg.neo4jInstance, [dfg.userId, "USER"]) == 0 && createUser(dfg, user)
131+
_getNodeCount(dfg.neo4jInstance, [dfg.userId, dfg.robotId, "ROBOT"]) == 0 && createRobot(dfg, robot)
132+
if _getNodeCount(dfg.neo4jInstance, [dfg.userId, dfg.robotId, dfg.sessionId, "SESSION"]) == 0
133+
return createSession(dfg, session)
134+
else
135+
return getSession(dfg)
136+
end
137+
end
138+
139+
"""
140+
$(SIGNATURES)
141+
List all sessions for the specified DFG's robot and user.
142+
Returns nothing if it isn't found.
143+
"""
144+
function lsSessions(dfg::CloudGraphsDFG)::Vector{Session}
88145
sessionNodes = _getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:SESSION:$(dfg.robotId):$(dfg.userId))")
89146
return map(s -> _convertDictToSession(Neo4j.getnodeproperties(s)), sessionNodes)
90147
end
91148

149+
"""
150+
$(SIGNATURES)
151+
List all robots for the specified DFG's user.
152+
Returns nothing if it isn't found.
153+
"""
154+
function lsRobots(dfg::CloudGraphsDFG)::Vector{Robot}
155+
robotNodes = _getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:ROBOT:$(dfg.userId))")
156+
return map(s -> _convertDictToRobot(Neo4j.getnodeproperties(s)), robotNodes)
157+
end
158+
159+
"""
160+
$(SIGNATURES)
161+
List all users.
162+
Returns nothing if it isn't found.
163+
"""
164+
function lsUsers(dfg::CloudGraphsDFG)::Vector{User}
165+
userNodes = _getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:USER)")
166+
return map(s -> _convertDictToUser(Neo4j.getnodeproperties(s)), userNodes)
167+
end
168+
169+
"""
170+
$(SIGNATURES)
171+
Get a session specified by userId:robotId:sessionId.
172+
Returns nothing if it isn't found.
173+
"""
174+
function getSession(dfg::CloudGraphsDFG, userId::Symbol, robotId::Symbol, sessionId::Symbol)::Union{Session, Nothing}
175+
!_isValid(userId) && error("Can't receive session with user ID '$(userId)'.")
176+
!_isValid(robotId) && error("Can't receive session with robot ID '$(robotId)'.")
177+
!_isValid(sessionId) && error("Can't receive session with session ID '$(sessionId)'.")
178+
sessionNode = _getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:SESSION:$(sessionId):$(robotId):$(userId))")
179+
length(sessionNode) == 0 && return nothing
180+
length(sessionNode) > 1 && error("There look to be $(length(sessionNode)) sessions identified for $(sessionId):$(robotId):$(userId)")
181+
return _convertDictToSession(Neo4j.getnodeproperties(sessionNode[1]))
182+
end
183+
184+
"""
185+
$(SIGNATURES)
186+
Get the session specified by the DFG object.
187+
Returns nothing if it isn't found.
188+
"""
189+
function getSession(dfg::CloudGraphsDFG)::Union{Nothing, Session}
190+
return getSession(dfg, Symbol(dfg.userId), Symbol(dfg.robotId), Symbol(dfg.sessionId))
191+
end
192+
193+
"""
194+
$(SIGNATURES)
195+
Get a robot specified by userId:robotId.
196+
Returns nothing if it isn't found.
197+
"""
198+
function getRobot(dfg::CloudGraphsDFG, userId::Symbol, robotId::Symbol)::Union{Robot, Nothing}
199+
!_isValid(userId) && error("Can't receive session with user ID '$(userId)'.")
200+
!_isValid(robotId) && error("Can't receive session with robot ID '$(robotId)'.")
201+
robotNode = _getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:ROBOT:$(robotId):$(userId))")
202+
length(robotNode) == 0 && return nothing
203+
length(robotNode) > 1 && error("There look to be $(length(robotNode)) robots identified for $(robotId):$(userId)")
204+
return _convertDictToRobot(Neo4j.getnodeproperties(robotNode[1]))
205+
end
206+
207+
"""
208+
$(SIGNATURES)
209+
Get the robot specified by the DFG object.
210+
Returns nothing if it isn't found.
211+
"""
212+
function getRobot(dfg::CloudGraphsDFG)::Union{Nothing, Robot}
213+
return getRobot(dfg, Symbol(dfg.userId), Symbol(dfg.robotId))
214+
end
215+
216+
"""
217+
$(SIGNATURES)
218+
Get a user specified by userId.
219+
Returns nothing if it isn't found.
220+
"""
221+
function getUser(dfg::CloudGraphsDFG, userId::Symbol)::Union{User, Nothing}
222+
!_isValid(userId) && error("Can't receive session with user ID '$(userId)'.")
223+
userNode = _getNeoNodesFromCyphonQuery(dfg.neo4jInstance, "(node:USER:$(userId))")
224+
length(userNode) == 0 && return nothing
225+
length(userNode) > 1 && error("There look to be $(length(userNode)) robots identified for $(userId)")
226+
return _convertDictToUser(Neo4j.getnodeproperties(userNode[1]))
227+
end
228+
229+
"""
230+
$(SIGNATURES)
231+
Get the user specified by the DFG object.
232+
Returns nothing if it isn't found.
233+
"""
234+
function getUser(dfg::CloudGraphsDFG)::Union{Nothing, User}
235+
return getUser(dfg, Symbol(dfg.userId))
236+
end
237+
238+
92239
"""
93240
$(SIGNATURES)
94241
DANGER: Clears the whole session from the database.
@@ -156,18 +303,27 @@ DANGER: Copies the source to a new unique destination.
156303
copySession!(sourceDFG::CloudGraphsDFG)::CloudGraphsDFG = copySession!(sourceDFG, nothing)
157304

158305

159-
getUserData(dfg::CloudGraphsDFG)::Dict{Symbol, String} = _getNodeProperty(dfg.neo4jInstance, [dfg.userId, "USER"])
306+
function getUserData(dfg::CloudGraphsDFG)::Dict{Symbol, String}
307+
propVal = _getNodeProperty(dfg.neo4jInstance, [dfg.userId, "USER"], "data")
308+
return JSON2.read(String(base64decode(propVal)), Dict{Symbol, String})
309+
end
160310
function setUserData(dfg::CloudGraphsDFG, data::Dict{Symbol, String})::Bool
161-
error("Not implemented yet")
162-
return true
311+
count = _setNodeProperty(dfg.neo4jInstance, [dfg.userId, "USER"], "data", base64encode(JSON2.write(data)))
312+
return count == 1
313+
end
314+
function getRobotData(dfg::CloudGraphsDFG)::Dict{Symbol, String}
315+
propVal = _getNodeProperty(dfg.neo4jInstance, [dfg.userId, dfg.robotId, "ROBOT"], "data")
316+
return JSON2.read(String(base64decode(propVal)), Dict{Symbol, String})
163317
end
164-
getRobotData(dfg::CloudGraphsDFG)::Dict{Symbol, String} = _getNodeProperty(dfg.neo4jInstance, [dfg.userId, dfg.robotId, "ROBOT"])
165318
function setRobotData(dfg::CloudGraphsDFG, data::Dict{Symbol, String})::Bool
166-
error("Not implemented yet")
167-
return true
319+
count = _setNodeProperty(dfg.neo4jInstance, [dfg.userId, dfg.robotId, "ROBOT"], "data", base64encode(JSON2.write(data)))
320+
return count == 1
321+
end
322+
function getSessionData(dfg::CloudGraphsDFG)::Dict{Symbol, String}
323+
propVal = _getNodeProperty(dfg.neo4jInstance, [dfg.userId, dfg.robotId, dfg.sessionId, "SESSION"], "data")
324+
return JSON2.read(String(base64decode(propVal)), Dict{Symbol, String})
168325
end
169-
getSessionData(dfg::CloudGraphsDFG)::Dict{Symbol, String} = _getNodeProperty(dfg.neo4jInstance, [dfg.userId, dfg.robotId, dfg.sessionId, "SESSION"])
170326
function setSessionData(dfg::CloudGraphsDFG, data::Dict{Symbol, String})::Bool
171-
error("Not implemented yet")
172-
return true
327+
count = _setNodeProperty(dfg.neo4jInstance, [dfg.userId, dfg.robotId, dfg.sessionId, "SESSION"], "data", base64encode(JSON2.write(data)))
328+
return count == 1
173329
end

src/CloudGraphsDFG/services/CloudGraphsDFG.jl

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,7 @@ function isFullyConnected(dfg::CloudGraphsDFG)::Bool
571571
# Total connected nodes - thank you Neo4j for 0..* awesomeness!!
572572
query = """
573573
match (n:$(dfg.userId):$(dfg.robotId):$(dfg.sessionId):$(varIds[1]))-[FACTORGRAPH*]-(node:$(dfg.userId):$(dfg.robotId):$(dfg.sessionId))
574-
WHERE n:VARIABLE OR n:FACTOR OR node:VARIABLE OR node:FACTOR
574+
WHERE (n:VARIABLE OR n:FACTOR OR node:VARIABLE OR node:FACTOR) and not (node:SESSION)
575575
WITH collect(n)+collect(node) as nodelist
576576
unwind nodelist as nodes
577577
return count(distinct nodes)"""
@@ -665,11 +665,9 @@ Note: By default orphaned factors (where the subgraph does not contain all the r
665665
function getSubgraphAroundNode(dfg::CloudGraphsDFG, node::DFGNode, distance::Int64=1, includeOrphanFactors::Bool=false, addToDFG::AbstractDFG=_getDuplicatedEmptyDFG(dfg))::AbstractDFG
666666
distance < 1 && error("getSubgraphAroundNode() only works for distance > 0")
667667

668-
# Making a copy session if not specified
669-
#moved to parameter addToDFG::AbstractDFG=_getDuplicatedEmptyDFG(dfg)
670-
671668
# Thank you Neo4j for 0..* awesomeness!!
672-
neighborList = _getLabelsFromCyphonQuery(dfg.neo4jInstance, "(n:$(dfg.userId):$(dfg.robotId):$(dfg.sessionId):$(node.label))-[FACTORGRAPH*0..$distance]-(node:$(dfg.userId):$(dfg.robotId):$(dfg.sessionId))")
669+
neighborList = _getLabelsFromCyphonQuery(dfg.neo4jInstance,
670+
"(n:$(dfg.userId):$(dfg.robotId):$(dfg.sessionId):$(node.label))-[FACTORGRAPH*0..$distance]-(node:$(dfg.userId):$(dfg.robotId):$(dfg.sessionId)) WHERE (n:VARIABLE OR n:FACTOR OR node:VARIABLE OR node:FACTOR) and not (node:SESSION)")
673671

674672
# Copy the section of graph we want
675673
_copyIntoGraph!(dfg, addToDFG, neighborList, includeOrphanFactors)

src/CloudGraphsDFG/services/CommonFunctions.jl

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,28 @@ function _getLabelsFromCyphonQuery(neo4jInstance::Neo4jInstance, matchCondition:
4747
return Symbol.(nodeIds)
4848
end
4949

50+
5051
"""
5152
$(SIGNATURES)
5253
Get a node property - returns nothing if not found
5354
"""
5455
function _getNodeProperty(neo4jInstance::Neo4jInstance, nodeLabels::Vector{String}, property::String)
5556
query = "match (n:$(join(nodeLabels, ":"))) return n.$property"
5657
result = DistributedFactorGraphs._queryNeo4j(neo4jInstance, query)
58+
length(result.results[1]["data"]) != 1 && return 0
59+
length(result.results[1]["data"][1]["row"]) != 1 && return 0
60+
return result.results[1]["data"][1]["row"][1]
61+
end
5762

63+
"""
64+
$(SIGNATURES)
65+
Set a node property - returns count of changed nodes.
66+
"""
67+
function _setNodeProperty(neo4jInstance::Neo4jInstance, nodeLabels::Vector{String}, property::String, value::String)
68+
query = "match (n:$(join(nodeLabels, ":"))) set n.$property = \"$value\" return count(n)"
69+
result = DistributedFactorGraphs._queryNeo4j(neo4jInstance, query)
70+
length(result.results[1]["data"]) != 1 && return 0
71+
length(result.results[1]["data"][1]["row"]) != 1 && return 0
5872
return result.results[1]["data"][1]["row"][1]
5973
end
6074

@@ -73,7 +87,8 @@ function _getNodeCount(neo4jInstance::Neo4jInstance, nodeLabels::Vector{String})
7387
query = "match (n:$(join(nodeLabels, ":"))) return count(n)"
7488
result = DistributedFactorGraphs._queryNeo4j(neo4jInstance, query)
7589
length(result.results[1]["data"]) != 1 && return 0
76-
return parse(Int, result.results[1]["data"][1]["row"][1])
90+
length(result.results[1]["data"][1]["row"]) != 1 && return 0
91+
return result.results[1]["data"][1]["row"][1]
7792
end
7893

7994
"""

0 commit comments

Comments
 (0)