How to get tree structure that has access rights on each node

bobsabates
Node

I have a tree structure like a folder structure so with a project with nested project without a depth limit, each node has access rights on them.

Here is my graph:

Here is my query:

MATCH (a:Account {name: "bob"})-[r:VIEWER | EDITOR]->(c:Project)

MATCH (c)<-[:IS_PARENT*]-(p)
WHERE (p)<-[:VIEWER | EDITOR]-(a)

WITH TYPE(r) as relation, p, collect(distinct c) AS children

RETURN {name: p.name, Children: [c in children | {name: c.name, access:relation}]}

Here is my result:

{
  "name": "project test",
  "Children": [
    {
      "access": "VIEWER",
      "name": "cohort"
    },
    {
      "access": "VIEWER",
      "name": "experience"
    }
  ]
}
{
  "name": "project test",
  "Children": [
    {
      "access": "EDITOR",
      "name": "protocol"
    },
    {
      "access": "EDITOR",
      "name": "nested child"
    }
  ]
}
{
  "name": "cohort",
  "Children": [
    {
      "access": "EDITOR",
      "name": "nested child"
    }
  ]
}

And this is what I want to get:

        {
          name: "Project 1",
          access: "VIEWER",
          children: [
            {
              name: "cohort",
              access: "VIEWER",
              children: [
                {
                  name: "nested",
                  access: "EDITOR",
                },
              ]
            },
            {
              name: "protocol",
              access: "EDITOR",
            },
            {
              name: "expererience",
              access: "VIEWER",
            }
          ]
        }

My problem is that the result is split in two results, and nested child isn't nested in cohort .

An other thing that is tricky is that I don't want to get a node if I don't have a relation with it.

For example here I removed the relation between bob and cohort :

So I must not get cohort in my result, like this:

 {
          name: "Project 1",
          access: "VIEWER",
          children: [
            {
              name: "nested child",
              access: "EDITOR",
            },
            {
              name: "protocol",
              access: "EDITOR",
            },
            {
              name: "expererience",
              access: "VIEWER",
            }
          ]
        }

Here is my data if you want to try:

MERGE (project:Project:RootProject {name: "project test"})
MERGE (child1:Project {name: "cohort"})
MERGE (child2:Project {name: "protocol"})
MERGE (child3:Project {name: "experience"})
MERGE (child4:Project {name: "nested child"})

MERGE (project)-[:IS_PARENT]->(child1)
MERGE (project)-[:IS_PARENT]->(child2)
MERGE (project)-[:IS_PARENT]->(child3)
MERGE (child1)-[:IS_PARENT]->(child4)

MERGE (bob:Account {name: "bob"})
 MERGE (bob)-[:EDITOR]->(child4)
 MERGE (bob)-[:EDITOR]->(child2)
 MERGE (bob)-[:VIEWER]->(child3)
MERGE (bob)-[:VIEWER]->(child1)
 MERGE (bob)-[:VIEWER]->(project)

I have tried a lot of things but I never get a good result.

5 REPLIES 5

filantrop
Node Clone

Hi,
To begin with you could try this procedure:

And there is an example here:

I will test if I can fix the filtering as soon as I have time.

filantrop
Node Clone

To accomplish this I've to create a temporarily CHILD relation.
Didn't find any apoc that can create a virtual paths from nodes. Which would be a better solution.

To rerun the solution you have to delete the child relations at the beginning ohterwise there will be doubles.

Firstly find all longest paths, and remove all nodes in the paths that don't have an outlink of IS_PARENT.
Then create child relations between them.

MATCH path=(a:Account {name: "bob"})-[:VIEWER | EDITOR]->(c:Project)-[:IS_PARENT *]->(dest:Project)
WHERE not exists((dest)-[:IS_PARENT]->())
with [n in nodes(path) where (n)<-[:VIEWER | EDITOR]-()|n] as nodes
call apoc.nodes.link(nodes,'CHILD')

Then you can run the following to get result that is near what you want.

MATCH path=(s)-[:CHILD *]->(d)
with collect(path) as paths
call apoc.convert.toTree(paths) yield value
return value

Clean up the CHILD relations

match ()-[C:CHILD]->() DELETE C

@filantrop
Hi thanks you for your answer it helps a lot, It is nearly exactly what i want.
I have 3 questions:

  1. When i add a new depth (cohort) -> (nested child) -> (new child)
    i get multiple results, event if the first one is good, can i do something to only get one result? because with each depth i will get more results

What i get :

{
  "_type": "Project:RootProject",
  "name": "project test",
  "_id": 12,
  "child": [
    {
      "_type": "Project",
      "name": "cohort",
      "_id": 14,
      "child": [
        {
          "_type": "Project",
          "name": "nested child",
          "_id": 20,
          "child": [
            {
              "_type": "Project",
              "name": "sous projet",
              "_id": 34
            }
          ]
        }
      ]
    },
    {
      "_type": "Project",
      "name": "protocol",
      "_id": 16
    },
    {
      "_type": "Project",
      "name": "experience",
      "_id": 18
    }
  ]
}


//------------------Result 2-------------------------
{
  "_type": "Project",
  "name": "cohort",
  "_id": 14,
  "child": [
    {
      "_type": "Project",
      "name": "nested child",
      "_id": 20,
      "child": [
        {
          "_type": "Project",
          "name": "sous projet",
          "_id": 34
        }
      ]
    }
  ]
}

//-----------------Result 3--------------------------
{
  "_type": "Project",
  "name": "nested child",
  "_id": 20,
  "child": [
    {
      "_type": "Project",
      "name": "sous projet",
      "_id": 34
    }
  ]
}

My second question is:
Is there a way to add the type of relation bob has with each child in the same query ?

"Children": [
    {
      "access": "EDITOR",
      "name": "protocol"
    },
    {
      "access": "EDITOR",
      "name": "nested child"
    }
  ]

And my last question:
If i remove the relation between bob and the RootProject, the query doesn't return a correct result, how can we fix this ?

glilienfield
Ninja
Ninja

I wasn't able to get exactly what you want. Your structure will require an iterative algorithm to get the child nesting you want. I was able to get the following output, which gives you the child nodes along each path. If you are going to process this output in a program, such as java, you could parse it and rearrange the paths into nested children. How this helps a little. I didn't bother trying to simply the query, since it is not what you wanted.

Query:
MATCH (a:Account {name: "bob"})-[r:VIEWER | EDITOR]->(c:Project)
MATCH path=(rootNode)-[:IS_PARENT*]->(c)
MATCH (rootNode)<-[q:VIEWER | EDITOR]-(a)
WHERE NOT exists(()-[:IS_PARENT]->(rootNode))
WITH rootNode, type(q) as rootType, type(r) as nodeType, path
CALL {
WITH path, nodeType
UNWIND tail(nodes(path)) as node
WITH {access:nodeType, name:node.name} as grouped
return collect(grouped) as groupedNodesOnPath
}
WITH rootNode, rootType, collect(groupedNodesOnPath) as children
RETURN rootNode{.name, access:rootType, children:children}

Output:
{
"access": "VIEWER",
"children": [
[
{
"access": "VIEWER",
"name": "cohort"
}
],
[
{
"access": "VIEWER",
"name": "experience"
}
],
[
{
"access": "EDITOR",
"name": "protocol"
}
],
[
{
"access": "EDITOR",
"name": "cohort"
},
{
"access": "EDITOR",
"name": "nested child"
}
]
],
"name": "project test"
}

glilienfield
Ninja
Ninja

Question 1: If I am understanding your statement, you are getting multiple paths from your query, which you don't want. These are resulting from each of the following match clause;

MATCH path=(rootNode)-[:IS_PARENT*]->(c)

This results in a path for each of the nodes that has an outgoing 'IS_PARENT' relationship. I assumed in my query that you did not want that, but wanted the path from the root of the graph. I accomplished that by added the following where clause:

WHERE NOT exists(()-[:IS_PARENT]->(rootNode))

This eliminates those nodes that have an incoming 'IS_PARENT' relationship, which indicate that are not at the root of the graph. This should eliminate the multiple paths, and return just one result starting from the root of the graph. In your case, that is the 'project test' node.

Question 2. Isn't that what the 'access' attribute is for each child? If I misunderstand, clarify and I will try to help.